diff --git a/src/htmlminifier.js b/src/htmlminifier.js index 6c2d6dd..5093a79 100644 --- a/src/htmlminifier.js +++ b/src/htmlminifier.js @@ -904,14 +904,37 @@ async function minifyHTML(value, options, partialMarkup) { }); const ids = []; - new CleanCSS().minify(wrapCSS(text, type)).warnings.forEach(function (warning) { - const match = uidPattern.exec(warning); - if (match) { - const id = uidAttr + match[2] + uidAttr; - text = text.replace(id, ignoreCSS(id)); - ids.push(id); + // Loop removing a single ID at a time from the warnings, a + // warning might contain multiple IDs in the context, but we only + // handle the first match on each attempt. + while (true) { + const minifyTest = new CleanCSS().minify(wrapCSS(text, type)); + if (minifyTest.warnings.length === 0) { + // There are no warnings. + break; } - }); + minifyTest.warnings.forEach(function (warning) { + // It is very important to reset the RegExp before searching + // as it's re-used each time. + uidPattern.lastIndex = 0; + const match = uidPattern.exec(warning); + if (match) { + const id = uidAttr + match[2] + uidAttr; + // Only substitute each ID once, if this has come up + // multiple times, then we need to abort. + if (ids.indexOf(id) !== -1) { + uidPattern.lastIndex = 0; + } else { + text = text.replace(id, ignoreCSS(id)); + ids.push(id); + } + } + }); + if (uidPattern.lastIndex === 0) { + // There was a warning that didn't match the pattern. + break; + } + } return fn(text, type).then(chunk => { ids.forEach(function (id) { diff --git a/tests/minifier.spec.js b/tests/minifier.spec.js index 67b61a6..2fa6463 100644 --- a/tests/minifier.spec.js +++ b/tests/minifier.spec.js @@ -3594,3 +3594,14 @@ test('minify Content-Security-Policy', async () => { input = ''; expect(await minify(input)).toBe(input); }); + +test('minify CSS multiple ignore in single warning', async () => { + const input = ''; + const output = ''; + expect(await minify(input, { + ignoreCustomFragments: [/\{%[\s\S]*?%\}/], + minifyCSS: { + level: 0 + } + })).toBe(output); +});