single-file-core 1.0.7 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -212,11 +212,11 @@ async function processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTes
212
212
  removedNodes.forEach(node => ruleData.block.children.remove(node));
213
213
  const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
214
214
  if (srcDeclaration) {
215
- await Promise.all(fontInfo.map(async (source, sourceIndex) => {
215
+ await Promise.all(fontInfo.map(async source => {
216
216
  if (fontTests.has(source.src)) {
217
217
  source.valid = fontTests.get(source.src);
218
218
  } else {
219
- if (FontFace) {
219
+ if (FontFace && source.fontUrl) {
220
220
  const fontFace = new FontFace("test-font", source.src);
221
221
  try {
222
222
  let timeout;
@@ -225,9 +225,10 @@ async function processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTes
225
225
  new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
226
226
  ]);
227
227
  } catch (error) {
228
- const declarationFontURLs = fontDeclarations.get(srcDeclaration.data);
229
- if (declarationFontURLs) {
230
- const fontURL = declarationFontURLs[declarationFontURLs.length - sourceIndex - 1];
228
+ const urlNodes = cssTree.findAll(srcDeclaration.data, node => node.type == "Url");
229
+ const declarationFontURLs = Array.from(fontDeclarations).find(([node]) => urlNodes.includes(node) && node.value == source.fontUrl)[1];
230
+ if (declarationFontURLs.length) {
231
+ const fontURL = declarationFontURLs[0];
231
232
  if (fontURL) {
232
233
  const fontFace = new FontFace("test-font", "url(" + fontURL + ")");
233
234
  try {
@@ -276,7 +277,7 @@ async function processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTes
276
277
  stats.fonts.discarded -= fontInfo.length;
277
278
  fontInfo.reverse();
278
279
  try {
279
- srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value" });
280
+ srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value", parseCustomProperty: true });
280
281
  }
281
282
  catch (error) {
282
283
  // ignored
@@ -333,6 +334,7 @@ function createFontsDetailsInfo() {
333
334
  }
334
335
 
335
336
  function getURL(urlFunction) {
337
+ urlFunction = urlFunction.replace(/url\(-sf-url-original\\\(\\"(.*?)\\"\\\)\\ /g, "");
336
338
  const urlMatch = urlFunction.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
337
339
  urlFunction.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
338
340
  urlFunction.match(REGEXP_URL_NO_QUOTES_FN);
@@ -220,13 +220,19 @@ function getFontFamilyNames(declarations) {
220
220
  if (fontFamilyName) {
221
221
  let familyName = "";
222
222
  if (fontFamilyName.data.value.children) {
223
+ let previousType;
223
224
  fontFamilyName.data.value.children.forEach(node => {
224
225
  if (node.type == "Operator" && node.value == "," && familyName) {
225
226
  fontFamilyNames.push(helper.normalizeFontFamily(familyName));
226
227
  familyName = "";
228
+ previousType = null;
227
229
  } else {
230
+ if (previousType == "Identifier" && node.type == "Identifier") {
231
+ familyName += " ";
232
+ }
228
233
  familyName += cssTree.generate(node);
229
234
  }
235
+ previousType = node.type;
230
236
  });
231
237
  } else {
232
238
  fontFamilyName = cssTree.generate(fontFamilyName.data.value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -15,4 +15,4 @@
15
15
  "url": "https://github.com/gildas-lormeau/single-file-core/issues"
16
16
  },
17
17
  "homepage": "https://github.com/gildas-lormeau/single-file-core#readme"
18
- }
18
+ }
@@ -1024,12 +1024,9 @@ class Processor {
1024
1024
 
1025
1025
  resolveStyleAttributeURLs() {
1026
1026
  this.doc.querySelectorAll("[style]").forEach(element => {
1027
- let styleContent = element.getAttribute("style");
1028
- if (this.options.compressCSS) {
1029
- styleContent = util.compressCSS(styleContent);
1030
- }
1031
- styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.workStyleElement, this.options.saveOriginalURLs);
1032
- const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
1027
+ const styleContent = element.getAttribute("style");
1028
+ const declarationList = cssTree.parse(styleContent, { context: "declarationList", parseCustomProperty: true });
1029
+ ProcessorHelper.resolveStylesheetURLs(declarationList, this.baseURI, this.workStyleElement);
1033
1030
  this.styles.set(element, declarationList);
1034
1031
  });
1035
1032
  }
@@ -1066,16 +1063,7 @@ class Processor {
1066
1063
  let stylesheet;
1067
1064
  stylesheets.set(element, stylesheetInfo);
1068
1065
  if (!options.blockStylesheets) {
1069
- let stylesheetContent = await getStylesheetContent(element, baseURI, options, workStyleElement);
1070
- if (!matchCharsetEquals(stylesheetContent, options.charset)) {
1071
- options = Object.assign({}, options, { charset: getCharset(stylesheetContent) });
1072
- stylesheetContent = await getStylesheetContent(element, baseURI, options, workStyleElement);
1073
- }
1074
- try {
1075
- stylesheet = cssTree.parse(removeCssComments(stylesheetContent));
1076
- } catch (error) {
1077
- // ignored
1078
- }
1066
+ stylesheet = await getStylesheet(element, baseURI, options, workStyleElement);
1079
1067
  }
1080
1068
  if (stylesheet && stylesheet.children) {
1081
1069
  if (options.compressCSS) {
@@ -1087,16 +1075,17 @@ class Processor {
1087
1075
  }
1088
1076
  }
1089
1077
 
1090
- async function getStylesheetContent(element, baseURI, options, workStyleElement) {
1091
- let content;
1078
+ async function getStylesheet(element, baseURI, options, workStyleElement) {
1079
+ let stylesheet;
1092
1080
  if (!options.blockStylesheets) {
1093
1081
  if (element.tagName == "LINK") {
1094
- content = await ProcessorHelper.resolveLinkStylesheetURLs(element.href, baseURI, options, workStyleElement);
1082
+ stylesheet = await ProcessorHelper.resolveLinkStylesheetURLs(element.href, baseURI, options, workStyleElement);
1095
1083
  } else {
1096
- content = await ProcessorHelper.resolveImportURLs(element.textContent, baseURI, options, workStyleElement);
1084
+ stylesheet = cssTree.parse(element.textContent, { context: "stylesheet", parseCustomProperty: true });
1085
+ await ProcessorHelper.resolveImportURLs(stylesheet, baseURI, options, workStyleElement);
1097
1086
  }
1098
1087
  }
1099
- return content || "";
1088
+ return stylesheet;
1100
1089
  }
1101
1090
  }
1102
1091
 
@@ -1108,7 +1097,7 @@ class Processor {
1108
1097
  frameElement.setAttribute("data", "data:text/html,");
1109
1098
  } else {
1110
1099
  const src = frameElement.getAttribute("src");
1111
- if (this.options.saveOriginalURLs && !isDataURL(src)) {
1100
+ if (this.options.saveOriginalURLs && src && !isDataURL(src)) {
1112
1101
  frameElement.setAttribute("data-sf-original-src", src);
1113
1102
  }
1114
1103
  frameElement.removeAttribute("src");
@@ -1275,8 +1264,8 @@ class Processor {
1275
1264
  }
1276
1265
 
1277
1266
  async processStyleAttributes() {
1278
- return Promise.all([...this.styles].map(([, declarationList]) =>
1279
- ProcessorHelper.processStyle(declarationList.children.toArray(), this.baseURI, this.options, this.cssVariables, this.batchRequest)
1267
+ return Promise.all([...this.styles].map(([, stylesheet]) =>
1268
+ ProcessorHelper.processStyle(stylesheet, this.baseURI, this.options, this.cssVariables, this.batchRequest)
1280
1269
  ));
1281
1270
  }
1282
1271
 
@@ -1426,11 +1415,7 @@ class Processor {
1426
1415
  const stylesheetInfo = this.stylesheets.get(styleElement);
1427
1416
  if (stylesheetInfo) {
1428
1417
  this.stylesheets.delete(styleElement);
1429
- let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
1430
- if (this.options.saveOriginalURLs) {
1431
- stylesheetContent = replaceOriginalURLs(stylesheetContent);
1432
- }
1433
- styleElement.textContent = stylesheetContent;
1418
+ styleElement.textContent = generateStylesheetContent(stylesheetInfo.stylesheet, this.options);
1434
1419
  if (stylesheetInfo.mediaText) {
1435
1420
  styleElement.media = stylesheetInfo.mediaText;
1436
1421
  }
@@ -1446,12 +1431,7 @@ class Processor {
1446
1431
  if (stylesheetInfo.mediaText) {
1447
1432
  styleElement.media = stylesheetInfo.mediaText;
1448
1433
  }
1449
- let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
1450
- if (this.options.saveOriginalURLs) {
1451
- stylesheetContent = replaceOriginalURLs(stylesheetContent);
1452
- styleElement.setAttribute("data-sf-original-href", linkElement.getAttribute("data-sf-original-href"));
1453
- }
1454
- styleElement.textContent = stylesheetContent;
1434
+ styleElement.textContent = generateStylesheetContent(stylesheetInfo.stylesheet, this.options);
1455
1435
  linkElement.parentElement.replaceChild(styleElement, linkElement);
1456
1436
  } else {
1457
1437
  linkElement.remove();
@@ -1461,14 +1441,10 @@ class Processor {
1461
1441
 
1462
1442
  replaceStyleAttributes() {
1463
1443
  this.doc.querySelectorAll("[style]").forEach(element => {
1464
- const declarations = this.styles.get(element);
1465
- if (declarations) {
1444
+ const declarationList = this.styles.get(element);
1445
+ if (declarationList) {
1466
1446
  this.styles.delete(element);
1467
- let styleContent = cssTree.generate(declarations);
1468
- if (this.options.saveOriginalURLs) {
1469
- styleContent = replaceOriginalURLs(styleContent);
1470
- }
1471
- element.setAttribute("style", styleContent);
1447
+ element.setAttribute("style", generateStylesheetContent(declarationList, this.options));
1472
1448
  } else {
1473
1449
  element.setAttribute("style", "");
1474
1450
  }
@@ -1708,52 +1684,40 @@ class ProcessorHelper {
1708
1684
  removedRules.forEach(cssRule => stylesheet.children.remove(cssRule));
1709
1685
  }
1710
1686
 
1711
- static async resolveImportURLs(stylesheetContent, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
1712
- stylesheetContent = ProcessorHelper.resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, options.saveOriginalURLs);
1713
- const imports = getImportFunctions(stylesheetContent);
1714
- await Promise.all(imports.map(async cssImport => {
1715
- const match = matchImport(cssImport);
1716
- if (match) {
1717
- const regExpCssImport = getRegExp(cssImport);
1718
- let resourceURL = normalizeURL(match.resourceURL);
1719
- if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1720
- try {
1721
- resourceURL = util.resolveURL(match.resourceURL, baseURI);
1722
- } catch (error) {
1723
- // ignored
1687
+ static async resolveImportURLs(stylesheet, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
1688
+ ProcessorHelper.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet);
1689
+ const imports = getImportFunctions(stylesheet);
1690
+ await Promise.all(imports.map(async node => {
1691
+ const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
1692
+ let resourceURL = normalizeURL(urlNode.value);
1693
+ if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1694
+ urlNode.value = util.EMPTY_RESOURCE;
1695
+ try {
1696
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1697
+ } catch (error) {
1698
+ // ignored
1699
+ }
1700
+ if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1701
+ const content = await getStylesheetContent(resourceURL);
1702
+ resourceURL = content.resourceURL;
1703
+ content.data = getUpdatedResourceContent(resourceURL, content, options);
1704
+ if (content.data && content.data.match(/^<!doctype /i)) {
1705
+ content.data = "";
1724
1706
  }
1725
- if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1726
- const content = await getStylesheetContent(resourceURL);
1727
- resourceURL = content.resourceURL;
1728
- content.data = getUpdatedResourceContent(resourceURL, content, options);
1729
- if (content.data && content.data.match(/^<!doctype /i)) {
1730
- content.data = "";
1731
- }
1732
- let importedStylesheetContent = removeCssComments(content.data);
1733
- if (options.compressCSS) {
1734
- importedStylesheetContent = util.compressCSS(importedStylesheetContent);
1735
- }
1736
- importedStylesheetContent = wrapMediaQuery(importedStylesheetContent, match.media);
1737
- if (stylesheetContent.includes(cssImport)) {
1738
- const ancestorStyleSheets = new Set(importedStyleSheets);
1739
- ancestorStyleSheets.add(resourceURL);
1740
- importedStylesheetContent = await ProcessorHelper.resolveImportURLs(importedStylesheetContent, resourceURL, options, workStylesheet, ancestorStyleSheets);
1741
- workStylesheet.textContent = importedStylesheetContent;
1742
- if ((workStylesheet.sheet && workStylesheet.sheet.cssRules.length) || (!workStylesheet.sheet && importedStylesheetContent)) {
1743
- stylesheetContent = stylesheetContent.replace(regExpCssImport, importedStylesheetContent);
1744
- } else {
1745
- stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
1746
- }
1747
- }
1748
- } else {
1749
- stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
1707
+ const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
1708
+ if (mediaQueryListNode) {
1709
+ content.data = wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
1710
+ }
1711
+ const stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1712
+ const ancestorStyleSheets = new Set(importedStyleSheets);
1713
+ ancestorStyleSheets.add(resourceURL);
1714
+ await ProcessorHelper.resolveImportURLs(stylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
1715
+ for (let keyName of Object.keys(stylesheet.children.head.data)) {
1716
+ node[keyName] = stylesheet.children.head.data[keyName];
1750
1717
  }
1751
- } else {
1752
- stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
1753
1718
  }
1754
1719
  }
1755
1720
  }));
1756
- return stylesheetContent;
1757
1721
 
1758
1722
  async function getStylesheetContent(resourceURL) {
1759
1723
  const content = await util.getContent(resourceURL, {
@@ -1790,13 +1754,10 @@ class ProcessorHelper {
1790
1754
  }
1791
1755
  }
1792
1756
 
1793
- static resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, saveOriginalURLs) {
1794
- const urlFunctions = getUrlFunctions(stylesheetContent, true);
1795
- if (saveOriginalURLs) {
1796
- stylesheetContent = addOriginalURLs(stylesheetContent);
1797
- }
1798
- urlFunctions.map(urlFunction => {
1799
- const originalResourceURL = matchURL(urlFunction);
1757
+ static resolveStylesheetURLs(stylesheet, baseURI, workStylesheet) {
1758
+ const urls = getUrlFunctions(stylesheet);
1759
+ urls.map(urlNode => {
1760
+ const originalResourceURL = urlNode.value;
1800
1761
  let resourceURL = normalizeURL(originalResourceURL);
1801
1762
  workStylesheet.textContent = "tmp { content:\"" + resourceURL + "\"}";
1802
1763
  if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
@@ -1812,25 +1773,14 @@ class ProcessorHelper {
1812
1773
  // ignored
1813
1774
  }
1814
1775
  }
1815
- if (testValidURL(resolvedURL) && originalResourceURL != resolvedURL && stylesheetContent.includes(urlFunction)) {
1816
- try {
1817
- stylesheetContent = stylesheetContent.replace(getRegExp(urlFunction), originalResourceURL ? urlFunction.replace(originalResourceURL, resolvedURL) : "url(" + resolvedURL + ")");
1818
- } catch (error) {
1819
- // ignored
1820
- }
1776
+ if (testValidURL(resolvedURL)) {
1777
+ urlNode.value = resolvedURL;
1821
1778
  }
1822
1779
  } else {
1823
- let newUrlFunction;
1824
- if (originalResourceURL) {
1825
- newUrlFunction = urlFunction.replace(originalResourceURL, util.EMPTY_RESOURCE);
1826
- } else {
1827
- newUrlFunction = "url(" + util.EMPTY_RESOURCE + ")";
1828
- }
1829
- stylesheetContent = stylesheetContent.replace(getRegExp(urlFunction), newUrlFunction);
1780
+ urlNode.value = util.EMPTY_RESOURCE;
1830
1781
  }
1831
1782
  }
1832
1783
  });
1833
- return stylesheetContent;
1834
1784
  }
1835
1785
 
1836
1786
  static async resolveLinkStylesheetURLs(resourceURL, baseURI, options, workStylesheet) {
@@ -1858,12 +1808,9 @@ class ProcessorHelper {
1858
1808
  if (content.data && content.data.match(/^<!doctype /i)) {
1859
1809
  content.data = "";
1860
1810
  }
1861
- let stylesheetContent = removeCssComments(content.data);
1862
- if (options.compressCSS) {
1863
- stylesheetContent = util.compressCSS(stylesheetContent);
1864
- }
1865
- stylesheetContent = await ProcessorHelper.resolveImportURLs(stylesheetContent, resourceURL, options, workStylesheet);
1866
- return stylesheetContent;
1811
+ const stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1812
+ await ProcessorHelper.resolveImportURLs(stylesheet, resourceURL, options, workStylesheet);
1813
+ return stylesheet;
1867
1814
  }
1868
1815
  }
1869
1816
 
@@ -1876,7 +1823,7 @@ class ProcessorHelper {
1876
1823
  removedRules.push(cssRule);
1877
1824
  } else if (ruleData.block && ruleData.block.children) {
1878
1825
  if (ruleData.type == "Rule") {
1879
- promises.push(this.processStyle(ruleData.block.children.toArray(), baseURI, options, cssVariables, batchRequest));
1826
+ promises.push(this.processStyle(ruleData, baseURI, options, cssVariables, batchRequest));
1880
1827
  } else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports")) {
1881
1828
  promises.push(this.processStylesheet(ruleData.block.children, baseURI, options, cssVariables, batchRequest));
1882
1829
  } else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
@@ -1888,94 +1835,62 @@ class ProcessorHelper {
1888
1835
  await Promise.all(promises);
1889
1836
 
1890
1837
  async function processFontFaceRule(ruleData) {
1891
- await Promise.all(ruleData.block.children.toArray().map(async declaration => {
1892
- if (declaration.type == "Declaration" && declaration.value.children) {
1893
- const urlFunctions = getUrlFunctions(getCSSValue(declaration.value), true);
1894
- await Promise.all(urlFunctions.map(async urlFunction => {
1895
- const originalResourceURL = matchURL(urlFunction);
1896
- if (!options.blockFonts) {
1897
- const resourceURL = normalizeURL(originalResourceURL);
1898
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1899
- let { content } = await batchRequest.addURL(resourceURL,
1900
- { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1901
- let resourceURLs = options.fontDeclarations.get(declaration);
1902
- if (!resourceURLs) {
1903
- resourceURLs = [];
1904
- options.fontDeclarations.set(declaration, resourceURLs);
1905
- }
1906
- resourceURLs.push(resourceURL);
1907
- replaceURLs(declaration, originalResourceURL, content);
1908
- }
1838
+ const urls = getUrlFunctions(ruleData);
1839
+ await Promise.all(urls.map(async urlNode => {
1840
+ const originalResourceURL = urlNode.value;
1841
+ if (!options.blockFonts) {
1842
+ const resourceURL = normalizeURL(originalResourceURL);
1843
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1844
+ let { content } = await batchRequest.addURL(resourceURL,
1845
+ { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1846
+ let resourceURLs = options.fontDeclarations.get(urlNode);
1847
+ if (!resourceURLs) {
1848
+ resourceURLs = [];
1849
+ options.fontDeclarations.set(urlNode, resourceURLs);
1850
+ }
1851
+ resourceURLs.push(resourceURL);
1852
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1853
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1909
1854
  } else {
1910
- replaceURLs(declaration, originalResourceURL, util.EMPTY_RESOURCE);
1855
+ urlNode.value = content;
1911
1856
  }
1912
- }));
1857
+ }
1858
+ } else {
1859
+ urlNode.value = util.EMPTY_RESOURCE;
1913
1860
  }
1914
1861
  }));
1915
-
1916
- function replaceURLs(declaration, oldURL, newURL) {
1917
- declaration.value.children.forEach(token => {
1918
- if (token.type == "Url" && util.removeQuotes(getCSSValue(token.value)) == oldURL) {
1919
- token.value = newURL;
1920
- }
1921
- });
1922
- }
1923
1862
  }
1924
1863
  }
1925
1864
 
1926
- static async processStyle(declarations, baseURI, options, cssVariables, batchRequest) {
1927
- await Promise.all(declarations.map(async declaration => {
1928
- if (declaration.value && !declaration.value.children && declaration.value.type == "Raw") {
1929
- try {
1930
- declaration.value = cssTree.parse(declaration.value.value, { context: "value" });
1931
- } catch (error) {
1932
- // ignored
1933
- }
1934
- }
1935
- if (declaration.type == "Declaration" && declaration.value.children) {
1936
- const urlFunctions = getUrlFunctions(getCSSValue(declaration.value));
1937
- await Promise.all(urlFunctions.map(async urlFunction => {
1938
- const originalResourceURL = matchURL(urlFunction);
1939
- if (!options.blockImages) {
1940
- const resourceURL = normalizeURL(originalResourceURL);
1941
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1942
- let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
1943
- { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1944
- let variableDefined;
1945
- const tokens = [];
1946
- findURLToken(originalResourceURL, declaration.value.children, (token, parent, rootFunction) => {
1947
- if (!originalResourceURL.startsWith("#")) {
1948
- if (duplicate && options.groupDuplicateImages && rootFunction && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
1949
- const value = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" }).children.head;
1950
- tokens.push({ parent, token, value });
1951
- variableDefined = true;
1952
- } else {
1953
- token.data.value = content;
1954
- }
1955
- }
1956
- });
1957
- if (variableDefined) {
1958
- cssVariables.set(indexResource, { content, url: originalResourceURL });
1959
- tokens.forEach(({ parent, token, value }) => parent.replace(token, value));
1865
+ static async processStyle(ruleData, baseURI, options, cssVariables, batchRequest) {
1866
+ const urls = getUrlFunctions(ruleData);
1867
+ await Promise.all(urls.map(async urlNode => {
1868
+ const originalResourceURL = urlNode.value;
1869
+ if (!options.blockImages) {
1870
+ const resourceURL = normalizeURL(originalResourceURL);
1871
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1872
+ let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
1873
+ { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1874
+ if (!originalResourceURL.startsWith("#")) {
1875
+ if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
1876
+ const varNode = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" });
1877
+ for (let keyName of Object.keys(varNode.children.head.data)) {
1878
+ urlNode[keyName] = varNode.children.head.data[keyName];
1879
+ }
1880
+ cssVariables.set(indexResource, { content, url: originalResourceURL });
1881
+ } else {
1882
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1883
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1884
+ } else {
1885
+ urlNode.value = content;
1960
1886
  }
1961
1887
  }
1962
- } else {
1963
- findURLToken(originalResourceURL, declaration.value.children, token => token.data.value = util.EMPTY_RESOURCE);
1964
1888
  }
1965
- }));
1966
- }
1967
- }));
1968
-
1969
- function findURLToken(url, children, callback, depth = 0) {
1970
- for (let token = children.head; token; token = token.next) {
1971
- if (token.data.children) {
1972
- findURLToken(url, token.data.children, callback, depth + 1);
1973
- }
1974
- if (token.data.type == "Url" && util.removeQuotes(getCSSValue(token.data.value)) == url) {
1975
- callback(token, children, depth == 0);
1976
1889
  }
1890
+ } else {
1891
+ urlNode.value = util.EMPTY_RESOURCE;
1977
1892
  }
1978
- }
1893
+ }));
1979
1894
  }
1980
1895
 
1981
1896
  static async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, cssVariables, styles, batchRequest, processDuplicates, removeElementIfMissing) {
@@ -2033,7 +1948,7 @@ class ProcessorHelper {
2033
1948
  if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
2034
1949
  if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
2035
1950
  cssVariables.set(indexResource, { content, url: originURL });
2036
- const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList" });
1951
+ const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
2037
1952
  styles.set(resourceElement, declarationList);
2038
1953
  } else {
2039
1954
  resourceElement.setAttribute(attributeName, content);
@@ -2188,18 +2103,6 @@ const HTTP_URI_PREFIX = /^https?:\/\//;
2188
2103
  const FILE_URI_PREFIX = /^file:\/\//;
2189
2104
  const EMPTY_URL = /^https?:\/\/+\s*$/;
2190
2105
  const NOT_EMPTY_URL = /^(https?:\/\/|file:\/\/|blob:).+/;
2191
- const REGEXP_URL_FN = /(url\s*\(\s*'(.*?)'\s*\))|(url\s*\(\s*"(.*?)"\s*\))|(url\s*\(\s*(.*?)\s*\))/gi;
2192
- const REGEXP_URL_SIMPLE_QUOTES_FN = /^url\s*\(\s*'(.*?)'\s*\)$/i;
2193
- const REGEXP_URL_DOUBLE_QUOTES_FN = /^url\s*\(\s*"(.*?)"\s*\)$/i;
2194
- const REGEXP_URL_NO_QUOTES_FN = /^url\s*\(\s*(.*?)\s*\)$/i;
2195
- const REGEXP_IMPORT_FN = /(@import\s*url\s*\(\s*'(.*?)'\s*\)\s*(.*?)(;|$|}))|(@import\s*url\s*\(\s*"(.*?)"\s*\)\s*(.*?)(;|$|}))|(@import\s*url\s*\(\s*(.*?)\s*\)\s*(.*?)(;|$|}))|(@import\s*'(.*?)'\s*(.*?)(;|$|}))|(@import\s*"(.*?)"\s*(.*?)(;|$|}))|(@import\s*(.*?)\s*(.*?)(;|$|}))/gi;
2196
- const REGEXP_IMPORT_URL_SIMPLE_QUOTES_FN = /@import\s*url\s*\(\s*'(.*?)'\s*\)\s*(.*?)(;|$|})/i;
2197
- const REGEXP_IMPORT_URL_DOUBLE_QUOTES_FN = /@import\s*url\s*\(\s*"(.*?)"\s*\)\s*(.*?)(;|$|})/i;
2198
- const REGEXP_IMPORT_URL_NO_QUOTES_FN = /@import\s*url\s*\(\s*(.*?)\s*\)\s*(.*?)(;|$|})/i;
2199
- const REGEXP_IMPORT_SIMPLE_QUOTES_FN = /@import\s*'(.*?)'\s*(.*?)(;|$|})/i;
2200
- const REGEXP_IMPORT_DOUBLE_QUOTES_FN = /@import\s*"(.*?)"\s*(.*?)(;|$|})/i;
2201
- const REGEXP_IMPORT_NO_QUOTES_FN = /@import\s*(.*?)\s*(.*?)(;|$|})/i;
2202
- const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
2203
2106
 
2204
2107
  function getUpdatedResourceContent(resourceURL, content, options) {
2205
2108
  if (options.rootDocument && options.updatedResources[resourceURL]) {
@@ -2218,20 +2121,6 @@ function normalizeURL(url) {
2218
2121
  }
2219
2122
  }
2220
2123
 
2221
- function getCSSValue(value) {
2222
- if (typeof value == "string") {
2223
- return value;
2224
- } else {
2225
- let result = "";
2226
- try {
2227
- result = cssTree.generate(value);
2228
- } catch (error) {
2229
- // ignored
2230
- }
2231
- return result;
2232
- }
2233
- }
2234
-
2235
2124
  function matchCharsetEquals(stylesheetContent, charset = UTF8_CHARSET) {
2236
2125
  const stylesheetCharset = getCharset(stylesheetContent);
2237
2126
  if (stylesheetCharset) {
@@ -2318,21 +2207,23 @@ function getLastSegment(url, replacementCharacter) {
2318
2207
  return lastSegment;
2319
2208
  }
2320
2209
 
2321
- function getRegExp(string) {
2322
- return new RegExp(string.replace(REGEXP_ESCAPE, "\\$1"), "gi");
2210
+ function getUrlFunctions(declarationList) {
2211
+ return cssTree.findAll(declarationList, node => node.type == "Url");
2323
2212
  }
2324
2213
 
2325
- function getUrlFunctions(stylesheetContent, unique) {
2326
- const result = stylesheetContent.match(REGEXP_URL_FN) || [];
2327
- if (unique) {
2328
- return [...new Set(result)];
2329
- } else {
2330
- return result;
2331
- }
2214
+ function getImportFunctions(declarationList) {
2215
+ return cssTree.findAll(declarationList, node => node.type == "Atrule" && node.name == "import");
2332
2216
  }
2333
2217
 
2334
- function getImportFunctions(stylesheetContent) {
2335
- return stylesheetContent.match(REGEXP_IMPORT_FN) || [];
2218
+ function generateStylesheetContent(stylesheet, options) {
2219
+ let stylesheetContent = cssTree.generate(stylesheet);
2220
+ if (options.compressCSS) {
2221
+ stylesheetContent = util.compressCSS(stylesheetContent);
2222
+ }
2223
+ if (options.saveOriginalURLs) {
2224
+ stylesheetContent = replaceOriginalURLs(stylesheetContent);
2225
+ }
2226
+ return stylesheetContent;
2336
2227
  }
2337
2228
 
2338
2229
  function findShortcutIcon(shortcutIcons) {
@@ -2341,30 +2232,12 @@ function findShortcutIcon(shortcutIcons) {
2341
2232
  return shortcutIcons[0];
2342
2233
  }
2343
2234
 
2344
- function matchURL(stylesheetContent) {
2345
- const match = stylesheetContent.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
2346
- stylesheetContent.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
2347
- stylesheetContent.match(REGEXP_URL_NO_QUOTES_FN);
2348
- return match && match[1];
2349
- }
2350
-
2351
- function addOriginalURLs(stylesheetContent) {
2352
- return stylesheetContent.replace(REGEXP_URL_FN, function (match, _0, url, _1, url2, _2, url3) {
2353
- url = url || url2 || url3;
2354
- if (isDataURL(url)) {
2355
- return match;
2356
- } else {
2357
- return "-sf-url-original(" + JSON.stringify(url) + ") " + match;
2358
- }
2359
- });
2360
- }
2361
-
2362
2235
  function isDataURL(url) {
2363
2236
  return url && (url.startsWith(DATA_URI_PREFIX) || url.startsWith(BLOB_URI_PREFIX));
2364
2237
  }
2365
2238
 
2366
2239
  function replaceOriginalURLs(stylesheetContent) {
2367
- return stylesheetContent.replace(/-sf-url-original\("(.*?)"\)/g, "/* original URL: $1 */");
2240
+ return stylesheetContent.replace(/url\(-sf-url-original\\\(\\"(.*?)\\"\\\)\\ /g, "/* original URL: $1 */url(");
2368
2241
  }
2369
2242
 
2370
2243
  function testIgnoredPath(resourceURL) {
@@ -2379,35 +2252,6 @@ function testValidURL(resourceURL) {
2379
2252
  return testValidPath(resourceURL) && (resourceURL.match(HTTP_URI_PREFIX) || resourceURL.match(FILE_URI_PREFIX) || resourceURL.startsWith(BLOB_URI_PREFIX)) && resourceURL.match(NOT_EMPTY_URL);
2380
2253
  }
2381
2254
 
2382
- function matchImport(stylesheetContent) {
2383
- const match = stylesheetContent.match(REGEXP_IMPORT_URL_SIMPLE_QUOTES_FN) ||
2384
- stylesheetContent.match(REGEXP_IMPORT_URL_DOUBLE_QUOTES_FN) ||
2385
- stylesheetContent.match(REGEXP_IMPORT_URL_NO_QUOTES_FN) ||
2386
- stylesheetContent.match(REGEXP_IMPORT_SIMPLE_QUOTES_FN) ||
2387
- stylesheetContent.match(REGEXP_IMPORT_DOUBLE_QUOTES_FN) ||
2388
- stylesheetContent.match(REGEXP_IMPORT_NO_QUOTES_FN);
2389
- if (match) {
2390
- const [, resourceURL, media] = match;
2391
- return { resourceURL, media };
2392
- }
2393
- }
2394
-
2395
- function removeCssComments(stylesheetContent) {
2396
- try {
2397
- return stylesheetContent.replace(/\/\*(.|[\r\n])*?\*\//g, "");
2398
- } catch (error) {
2399
- let start, end;
2400
- do {
2401
- start = stylesheetContent.indexOf("/*");
2402
- end = stylesheetContent.indexOf("*/", start + 2);
2403
- if (start != -1 && end != -1) {
2404
- stylesheetContent = stylesheetContent.substring(0, start) + stylesheetContent.substr(end + 2);
2405
- }
2406
- } while (start != -1 && end != -1);
2407
- return stylesheetContent;
2408
- }
2409
- }
2410
-
2411
2255
  function wrapMediaQuery(stylesheetContent, mediaQuery) {
2412
2256
  if (mediaQuery) {
2413
2257
  return "@media " + mediaQuery + "{ " + stylesheetContent + " }";
@@ -273,7 +273,11 @@ function getResourcesInfo(win, doc, element, options, data, elementHidden, compu
273
273
  element.setAttribute(IMAGE_ATTRIBUTE_NAME, data.images.length - 1);
274
274
  data.markedElements.push(element);
275
275
  element.removeAttribute(LAZY_SRC_ATTRIBUTE_NAME);
276
- computedStyle = computedStyle || win.getComputedStyle(element);
276
+ try {
277
+ computedStyle = computedStyle || win.getComputedStyle(element);
278
+ } catch (error) {
279
+ // ignored
280
+ }
277
281
  if (computedStyle) {
278
282
  imageData.size = getSize(win, element, computedStyle);
279
283
  const boxShadow = computedStyle.getPropertyValue("box-shadow");