single-file-core 1.2.24 → 1.2.26

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.
@@ -24,10 +24,6 @@
24
24
  /* global globalThis */
25
25
 
26
26
  import * as cssTree from "./../../vendor/css-tree.js";
27
- import {
28
- normalizeFontFamily,
29
- getFontWeight
30
- } from "./../helper.js";
31
27
 
32
28
  const JSON = globalThis.JSON;
33
29
  const FontFace = globalThis.FontFace;
@@ -45,32 +41,8 @@ const NOSCRIPT_TAG_FOUND = /<noscript/gi;
45
41
  const CANVAS_TAG_FOUND = /<canvas/gi;
46
42
  const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
47
43
  const SINGLE_FILE_VARIABLE_MAX_SIZE = 512 * 1024;
48
-
49
- const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
50
- const REGEXP_URL_DOUBLE_QUOTES_FN = /url\s*\(\s*"(.*?)"\s*\)/i;
51
- const REGEXP_URL_NO_QUOTES_FN = /url\s*\(\s*(.*?)\s*\)/i;
52
- const REGEXP_URL_FUNCTION = /(url|local|-sf-url-original)\(.*?\)\s*(,|$)/g;
53
- const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
54
- const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
55
- const REGEXP_URL_FUNCTION_WOFF = /^url\(\s*["']?data:font\/(woff2?)/;
56
- const REGEXP_URL_FUNCTION_WOFF_ALT = /^url\(\s*["']?data:application\/x-font-(woff)/;
57
- const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
58
- const REGEXP_FONT_FORMAT_VALUE = /format\((.*?)\)\s*,?$/;
59
- const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
60
44
  const EMPTY_URL_SOURCE = /^url\(["']?data:[^,]*,?["']?\)/;
61
45
  const LOCAL_SOURCE = "local(";
62
- const MEDIA_ALL = "all";
63
- const FONT_STRETCHES = {
64
- "ultra-condensed": "50%",
65
- "extra-condensed": "62.5%",
66
- "condensed": "75%",
67
- "semi-condensed": "87.5%",
68
- "normal": "100%",
69
- "semi-expanded": "112.5%",
70
- "expanded": "125%",
71
- "extra-expanded": "150%",
72
- "ultra-expanded": "200%"
73
- };
74
46
  const FONT_MAX_LOAD_DELAY = 5000;
75
47
 
76
48
  let util;
@@ -100,39 +72,6 @@ function getProcessorHelperClass(utilInstance) {
100
72
  const ProcessorHelperCommon = getProcessorHelperCommonClass(util, cssTree);
101
73
 
102
74
  return class ProcessorHelper extends ProcessorHelperCommon {
103
- async processPageResources(doc, baseURI, options, resources, styles, batchRequest) {
104
- const processAttributeArgs = [
105
- ["link[href][rel*=\"icon\"]", "href", false, true],
106
- ["object[type=\"image/svg+xml\"], object[type=\"image/svg-xml\"], object[data*=\".svg\"]", "data"],
107
- ["img[src], input[src][type=image]", "src", true],
108
- ["embed[src*=\".svg\"]", "src"],
109
- ["video[poster]", "poster"],
110
- ["*[background]", "background"],
111
- ["image", "xlink:href"],
112
- ["image", "href"]
113
- ];
114
- if (options.blockImages) {
115
- doc.querySelectorAll("svg").forEach(element => element.remove());
116
- }
117
- let resourcePromises = processAttributeArgs.map(([selector, attributeName, processDuplicates, removeElementIfMissing]) =>
118
- this.processAttribute(doc.querySelectorAll(selector), attributeName, baseURI, options, "image", resources, styles, batchRequest, processDuplicates, removeElementIfMissing)
119
- );
120
- resourcePromises = resourcePromises.concat([
121
- this.processXLinks(doc.querySelectorAll("use"), doc, baseURI, options, batchRequest),
122
- this.processSrcset(doc.querySelectorAll("img[srcset], source[srcset]"), baseURI, options, batchRequest)
123
- ]);
124
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("object[data*=\".pdf\"]"), "data", baseURI, options, null, resources, styles, batchRequest));
125
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("embed[src*=\".pdf\"]"), "src", baseURI, options, null, resources, styles, batchRequest));
126
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("audio[src], audio > source[src]"), "src", baseURI, options, "audio", resources, styles, batchRequest));
127
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("video[src], video > source[src]"), "src", baseURI, options, "video", resources, styles, batchRequest));
128
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("audio track[src], video track[src]"), "src", baseURI, options, null, resources, styles, batchRequest));
129
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("model[src]"), "src", baseURI, options, null, resources, styles, batchRequest));
130
- await Promise.all(resourcePromises);
131
- if (options.saveFavicon) {
132
- this.processShortcutIcons(doc);
133
- }
134
- }
135
-
136
75
  async processLinkElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement) {
137
76
  if (element.tagName.toUpperCase() == "LINK" && element.charset) {
138
77
  options.charset = element.charset;
@@ -165,7 +104,7 @@ function getProcessorHelperClass(utilInstance) {
165
104
  }
166
105
  }
167
106
 
168
- replaceStylesheets(doc, stylesheets, resources, options) {
107
+ replaceStylesheets(doc, stylesheets, options) {
169
108
  doc.querySelectorAll("style").forEach(styleElement => {
170
109
  const stylesheetInfo = stylesheets.get(styleElement);
171
110
  if (stylesheetInfo) {
@@ -295,54 +234,27 @@ function getProcessorHelperClass(utilInstance) {
295
234
  }
296
235
  }
297
236
 
298
- async processStylesheet(cssRules, baseURI, options, resources, batchRequest) {
299
- const promises = [];
300
- const removedRules = [];
301
- for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
302
- const ruleData = cssRule.data;
303
- if (ruleData.type == "Atrule" && ruleData.name == "charset") {
304
- removedRules.push(cssRule);
305
- } else if (ruleData.block && ruleData.block.children) {
306
- if (ruleData.type == "Rule") {
307
- promises.push(this.processStyle(ruleData, options, resources, batchRequest));
308
- } else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports")) {
309
- promises.push(this.processStylesheet(ruleData.block.children, baseURI, options, resources, batchRequest));
310
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
311
- promises.push(processFontFaceRule(ruleData));
312
- }
313
- }
237
+ async processFont(resourceURL, urlNode, originalResourceURL, baseURI, options, resources, batchRequest) {
238
+ let { content } = await batchRequest.addURL(resourceURL, {
239
+ asBinary: true,
240
+ expectedType: "font",
241
+ baseURI,
242
+ blockMixedContent: options.blockMixedContent
243
+ });
244
+ let resourceURLs = resources.fonts.get(urlNode);
245
+ if (!resourceURLs) {
246
+ resourceURLs = [];
247
+ resources.fonts.set(urlNode, resourceURLs);
314
248
  }
315
- removedRules.forEach(cssRule => cssRules.remove(cssRule));
316
- await Promise.all(promises);
317
-
318
- async function processFontFaceRule(ruleData) {
319
- const urls = getUrlFunctions(ruleData);
320
- await Promise.all(urls.map(async urlNode => {
321
- const originalResourceURL = urlNode.value;
322
- if (!options.blockFonts) {
323
- const resourceURL = normalizeURL(originalResourceURL);
324
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
325
- let { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
326
- let resourceURLs = resources.fonts.get(urlNode);
327
- if (!resourceURLs) {
328
- resourceURLs = [];
329
- resources.fonts.set(urlNode, resourceURLs);
330
- }
331
- resourceURLs.push(resourceURL);
332
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
333
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
334
- } else {
335
- urlNode.value = content;
336
- }
337
- }
338
- } else {
339
- urlNode.value = util.EMPTY_RESOURCE;
340
- }
341
- }));
249
+ resourceURLs.push(resourceURL);
250
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
251
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
252
+ } else {
253
+ urlNode.value = content;
342
254
  }
343
255
  }
344
256
 
345
- async processStyle(ruleData, options, { cssVariables }, batchRequest) {
257
+ async processStyle(ruleData, options, resources, batchRequest) {
346
258
  const urls = getUrlFunctions(ruleData);
347
259
  await Promise.all(urls.map(async urlNode => {
348
260
  const originalResourceURL = urlNode.value;
@@ -357,7 +269,7 @@ function getProcessorHelperClass(utilInstance) {
357
269
  for (let keyName of Object.keys(varNode.children.head.data)) {
358
270
  urlNode[keyName] = varNode.children.head.data[keyName];
359
271
  }
360
- cssVariables.set(indexResource, { content, url: originalResourceURL });
272
+ resources.cssVariables.set(indexResource, { content, url: originalResourceURL });
361
273
  } else {
362
274
  if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
363
275
  urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
@@ -373,7 +285,7 @@ function getProcessorHelperClass(utilInstance) {
373
285
  }));
374
286
  }
375
287
 
376
- async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, { cssVariables }, styles, batchRequest, processDuplicates, removeElementIfMissing) {
288
+ async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, resources, removeElementIfMissing, batchRequest, styles, processDuplicates) {
377
289
  await Promise.all(Array.from(resourceElements).map(async resourceElement => {
378
290
  let resourceURL = resourceElement.getAttribute(attributeName);
379
291
  if (resourceURL != null) {
@@ -445,7 +357,7 @@ function getProcessorHelperClass(utilInstance) {
445
357
  const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
446
358
  if (processDuplicates && duplicate && !isSVG && util.getContentSize(content) < maxSizeDuplicateImages) {
447
359
  if (this.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
448
- cssVariables.set(indexResource, { content, url: originURL });
360
+ resources.cssVariables.set(indexResource, { content, url: originURL });
449
361
  const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
450
362
  styles.set(resourceElement, declarationList);
451
363
  } else {
@@ -477,45 +389,13 @@ function getProcessorHelperClass(utilInstance) {
477
389
  }
478
390
  }
479
391
 
480
- async processSrcset(resourceElements, baseURI, options, batchRequest) {
481
- await Promise.all(Array.from(resourceElements).map(async resourceElement => {
482
- const originSrcset = resourceElement.getAttribute("srcset");
483
- const srcset = util.parseSrcset(originSrcset);
484
- if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
485
- resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
486
- }
487
- if (!options.blockImages) {
488
- const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
489
- let resourceURL = normalizeURL(srcsetValue.url);
490
- if (!testIgnoredPath(resourceURL)) {
491
- if (testValidPath(resourceURL)) {
492
- try {
493
- resourceURL = util.resolveURL(resourceURL, baseURI);
494
- } catch (error) {
495
- // ignored
496
- }
497
- if (testValidURL(resourceURL)) {
498
- const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
499
- const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
500
- if (forbiddenPrefixFound) {
501
- return "";
502
- }
503
- return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
504
- } else {
505
- return "";
506
- }
507
- } else {
508
- return "";
509
- }
510
- } else {
511
- return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
512
- }
513
- }));
514
- resourceElement.setAttribute("srcset", srcsetValues.join(", "));
515
- } else {
516
- resourceElement.setAttribute("srcset", "");
517
- }
518
- }));
392
+ async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest) {
393
+ const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
394
+ const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
395
+ if (forbiddenPrefixFound) {
396
+ return "";
397
+ }
398
+ return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
519
399
  }
520
400
 
521
401
  testEmptyResource(resource) {
@@ -569,10 +449,6 @@ function getProcessorHelperClass(utilInstance) {
569
449
  return {};
570
450
  }
571
451
 
572
- removeAlternativeFonts(doc, stylesheets, fonts, fontTests) {
573
- return removeAlternativeFonts(doc, stylesheets, fonts, fontTests);
574
- }
575
-
576
452
  async processScript(element, resourceURL, options, charset) {
577
453
  const content = await util.getContent(resourceURL, {
578
454
  asBinary: true,
@@ -599,279 +475,92 @@ function getProcessorHelperClass(utilInstance) {
599
475
  removeUnusedStylesheets(doc) {
600
476
  doc.querySelectorAll("link[rel*=stylesheet][rel*=alternate][title]").forEach(element => element.remove());
601
477
  }
602
- };
603
- }
604
478
 
605
- async function removeAlternativeFonts(doc, stylesheets, fontDeclarations, fontTests) {
606
- const fontsDetails = {
607
- fonts: new Map(),
608
- medias: new Map(),
609
- supports: new Map()
610
- };
611
- const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
612
- let sheetIndex = 0;
613
- stylesheets.forEach(stylesheetInfo => {
614
- const cssRules = stylesheetInfo.stylesheet.children;
615
- if (cssRules) {
616
- stats.rules.processed += cssRules.size;
617
- stats.rules.discarded += cssRules.size;
618
- if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
619
- const mediaFontsDetails = createFontsDetailsInfo();
620
- fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
621
- getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
622
- } else {
623
- getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
624
- }
625
- }
626
- sheetIndex++;
627
- });
628
- processFontDetails(fontsDetails);
629
- await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
630
- const cssRules = stylesheetInfo.stylesheet.children;
631
- const media = stylesheetInfo.mediaText;
632
- if (cssRules) {
633
- if (media && media != MEDIA_ALL) {
634
- await processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), fontDeclarations, fontTests, stats);
635
- } else {
636
- await processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontDeclarations, fontTests, stats);
637
- }
638
- stats.rules.discarded -= cssRules.size;
639
- }
640
- }));
641
- return stats;
642
- }
643
-
644
- function getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
645
- let mediaIndex = 0, supportsIndex = 0;
646
- cssRules.forEach(ruleData => {
647
- if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
648
- const mediaText = cssTree.generate(ruleData.prelude);
649
- const fontsDetails = createFontsDetailsInfo();
650
- mediaFontsDetails.medias.set("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText, fontsDetails);
651
- mediaIndex++;
652
- getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
653
- } else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
654
- const supportsText = cssTree.generate(ruleData.prelude);
655
- const fontsDetails = createFontsDetailsInfo();
656
- mediaFontsDetails.supports.set("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText, fontsDetails);
657
- supportsIndex++;
658
- getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
659
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face" && ruleData.block && ruleData.block.children) {
660
- const fontKey = getFontKey(ruleData);
661
- let fontInfo = mediaFontsDetails.fonts.get(fontKey);
662
- if (!fontInfo) {
663
- fontInfo = [];
664
- mediaFontsDetails.fonts.set(fontKey, fontInfo);
665
- }
666
- const src = getPropertyValue(ruleData, "src");
667
- if (src) {
668
- const fontSources = src.match(REGEXP_URL_FUNCTION);
669
- if (fontSources) {
670
- fontSources.forEach(source => fontInfo.unshift(source));
671
- }
672
- }
673
- }
674
- });
675
- }
676
-
677
- function processFontDetails(fontsDetails) {
678
- fontsDetails.fonts.forEach((fontInfo, fontKey) => {
679
- fontsDetails.fonts.set(fontKey, fontInfo.map(fontSource => {
680
- const fontFormatMatch = fontSource.match(REGEXP_FONT_FORMAT_VALUE);
681
- let fontFormat;
682
- const fontUrl = getURL(fontSource);
683
- if (fontFormatMatch && fontFormatMatch[1]) {
684
- fontFormat = fontFormatMatch[1].replace(REGEXP_SIMPLE_QUOTES_STRING, "$1").replace(REGEXP_DOUBLE_QUOTES_STRING, "$1").toLowerCase();
685
- }
686
- if (!fontFormat) {
687
- const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF);
688
- if (fontFormatMatch && fontFormatMatch[1]) {
689
- fontFormat = fontFormatMatch[1];
690
- } else {
691
- const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF_ALT);
692
- if (fontFormatMatch && fontFormatMatch[1]) {
693
- fontFormat = fontFormatMatch[1];
694
- }
695
- }
696
- }
697
- if (!fontFormat && fontUrl) {
698
- const fontFormatMatch = fontUrl.match(REGEXP_FONT_FORMAT);
699
- if (fontFormatMatch && fontFormatMatch[1]) {
700
- fontFormat = fontFormatMatch[1];
479
+ async processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats) {
480
+ const removedNodes = [];
481
+ for (let node = ruleData.block.children.head; node; node = node.next) {
482
+ if (node.data.property == "src") {
483
+ removedNodes.push(node);
701
484
  }
702
485
  }
703
- return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat };
704
- }));
705
- });
706
- fontsDetails.medias.forEach(mediaFontsDetails => processFontDetails(mediaFontsDetails));
707
- fontsDetails.supports.forEach(supportsFontsDetails => processFontDetails(supportsFontsDetails));
708
- }
709
-
710
- async function processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontDeclarations, fontTests, stats) {
711
- const removedRules = [];
712
- let mediaIndex = 0, supportsIndex = 0;
713
- for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
714
- const ruleData = cssRule.data;
715
- if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
716
- const mediaText = cssTree.generate(ruleData.prelude);
717
- await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText), fontDeclarations, fontTests, stats);
718
- mediaIndex++;
719
- } else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
720
- const supportsText = cssTree.generate(ruleData.prelude);
721
- await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.supports.get("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText), fontDeclarations, fontTests, stats);
722
- supportsIndex++;
723
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
724
- const key = getFontKey(ruleData);
725
- const fontInfo = fontsDetails.fonts.get(key);
726
- if (fontInfo) {
727
- const processed = await processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats);
728
- if (processed) {
729
- fontsDetails.fonts.delete(key);
730
- }
731
- } else {
732
- removedRules.push(cssRule);
733
- }
734
- }
735
- }
736
- removedRules.forEach(cssRule => cssRules.remove(cssRule));
737
- }
738
-
739
- async function processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats) {
740
- const removedNodes = [];
741
- for (let node = ruleData.block.children.head; node; node = node.next) {
742
- if (node.data.property == "src") {
743
- removedNodes.push(node);
744
- }
745
- }
746
- removedNodes.pop();
747
- removedNodes.forEach(node => ruleData.block.children.remove(node));
748
- const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
749
- if (srcDeclaration) {
750
- await Promise.all(fontInfo.map(async source => {
751
- if (fontTests.has(source.src)) {
752
- source.valid = fontTests.get(source.src);
753
- } else {
754
- if (FontFace && source.fontUrl) {
755
- const fontFace = new FontFace("test-font", source.src);
756
- try {
757
- let timeout;
758
- await Promise.race([
759
- fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
760
- new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
761
- ]);
762
- } catch (error) {
763
- const urlNodes = cssTree.findAll(srcDeclaration.data, node => node.type == "Url");
764
- const declarationFontURLs = Array.from(fontDeclarations).find(([node]) => urlNodes.includes(node) && node.value == source.fontUrl);
765
- if (declarationFontURLs && declarationFontURLs[1].length) {
766
- const fontURL = declarationFontURLs[1][0];
767
- if (fontURL) {
768
- const fontFace = new FontFace("test-font", "url(" + fontURL + ")");
769
- try {
770
- let timeout;
771
- await Promise.race([
772
- fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
773
- new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
774
- ]);
775
- } catch (error) {
776
- // ignored
486
+ removedNodes.pop();
487
+ removedNodes.forEach(node => ruleData.block.children.remove(node));
488
+ const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
489
+ if (srcDeclaration) {
490
+ await Promise.all(fontInfo.map(async source => {
491
+ if (fontTests.has(source.src)) {
492
+ source.valid = fontTests.get(source.src);
493
+ } else {
494
+ if (FontFace && source.fontUrl) {
495
+ const fontFace = new FontFace("test-font", source.src);
496
+ try {
497
+ let timeout;
498
+ await Promise.race([
499
+ fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
500
+ new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
501
+ ]);
502
+ } catch (error) {
503
+ const urlNodes = cssTree.findAll(srcDeclaration.data, node => node.type == "Url");
504
+ const declarationFontURLs = Array.from(fontDeclarations).find(([node]) => urlNodes.includes(node) && node.value == source.fontUrl);
505
+ if (declarationFontURLs && declarationFontURLs[1].length) {
506
+ const fontURL = declarationFontURLs[1][0];
507
+ if (fontURL) {
508
+ const fontFace = new FontFace("test-font", "url(" + fontURL + ")");
509
+ try {
510
+ let timeout;
511
+ await Promise.race([
512
+ fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
513
+ new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
514
+ ]);
515
+ } catch (error) {
516
+ // ignored
517
+ }
518
+ }
519
+ } else {
520
+ source.valid = true;
777
521
  }
778
522
  }
779
523
  } else {
780
524
  source.valid = true;
781
525
  }
526
+ fontTests.set(source.src, source.valid);
782
527
  }
528
+ }));
529
+ const findSourceByFormat = (fontFormat, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
530
+ const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
531
+ stats.fonts.processed += fontInfo.length;
532
+ stats.fonts.discarded += fontInfo.length;
533
+ const woffFontFound =
534
+ findSourceByFormat("woff2-variations", true) || findSourceByFormat("woff2", true) || findSourceByFormat("woff", true);
535
+ if (woffFontFound) {
536
+ fontInfo = filterSources(woffFontFound);
783
537
  } else {
784
- source.valid = true;
538
+ const ttfFontFound =
539
+ findSourceByFormat("truetype-variations", true) || findSourceByFormat("truetype", true);
540
+ if (ttfFontFound) {
541
+ fontInfo = filterSources(ttfFontFound);
542
+ } else {
543
+ const otfFontFound =
544
+ findSourceByFormat("opentype") || findSourceByFormat("embedded-opentype");
545
+ if (otfFontFound) {
546
+ fontInfo = filterSources(otfFontFound);
547
+ } else {
548
+ fontInfo = fontInfo.filter(source => !source.src.match(EMPTY_URL_SOURCE) && (source.valid) || source.src.startsWith(LOCAL_SOURCE));
549
+ }
550
+ }
785
551
  }
786
- fontTests.set(source.src, source.valid);
787
- }
788
- }));
789
- const findSourceByFormat = (fontFormat, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
790
- const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
791
- stats.fonts.processed += fontInfo.length;
792
- stats.fonts.discarded += fontInfo.length;
793
- const woffFontFound =
794
- findSourceByFormat("woff2-variations", true) || findSourceByFormat("woff2", true) || findSourceByFormat("woff", true);
795
- if (woffFontFound) {
796
- fontInfo = filterSources(woffFontFound);
797
- } else {
798
- const ttfFontFound =
799
- findSourceByFormat("truetype-variations", true) || findSourceByFormat("truetype", true);
800
- if (ttfFontFound) {
801
- fontInfo = filterSources(ttfFontFound);
802
- } else {
803
- const otfFontFound =
804
- findSourceByFormat("opentype") || findSourceByFormat("embedded-opentype");
805
- if (otfFontFound) {
806
- fontInfo = filterSources(otfFontFound);
807
- } else {
808
- fontInfo = fontInfo.filter(source => !source.src.match(EMPTY_URL_SOURCE) && (source.valid) || source.src.startsWith(LOCAL_SOURCE));
552
+ stats.fonts.discarded -= fontInfo.length;
553
+ fontInfo.reverse();
554
+ try {
555
+ srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value", parseCustomProperty: true });
809
556
  }
557
+ catch (error) {
558
+ // ignored
559
+ }
560
+ return true;
561
+ } else {
562
+ return false;
810
563
  }
811
564
  }
812
- stats.fonts.discarded -= fontInfo.length;
813
- fontInfo.reverse();
814
- try {
815
- srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value", parseCustomProperty: true });
816
- }
817
- catch (error) {
818
- // ignored
819
- }
820
- return true;
821
- } else {
822
- return false;
823
- }
824
- }
825
-
826
- function getPropertyValue(ruleData, propertyName) {
827
- let property;
828
- if (ruleData.block.children) {
829
- property = ruleData.block.children.filter(node => {
830
- try {
831
- return node.property == propertyName && !cssTree.generate(node.value).match(/\\9$/);
832
- } catch (error) {
833
- return node.property == propertyName;
834
- }
835
- }).tail;
836
- }
837
- if (property) {
838
- try {
839
- return cssTree.generate(property.data.value);
840
- } catch (error) {
841
- // ignored
842
- }
843
- }
844
- }
845
-
846
- function getFontKey(ruleData) {
847
- return JSON.stringify([
848
- normalizeFontFamily(getPropertyValue(ruleData, "font-family")),
849
- getFontWeight(getPropertyValue(ruleData, "font-weight") || "400"),
850
- getPropertyValue(ruleData, "font-style") || "normal",
851
- getPropertyValue(ruleData, "unicode-range"),
852
- getFontStretch(getPropertyValue(ruleData, "font-stretch")),
853
- getPropertyValue(ruleData, "font-variant") || "normal",
854
- getPropertyValue(ruleData, "font-feature-settings"),
855
- getPropertyValue(ruleData, "font-variation-settings")
856
- ]);
857
- }
858
-
859
- function getFontStretch(stretch) {
860
- return FONT_STRETCHES[stretch] || stretch;
861
- }
862
-
863
- function createFontsDetailsInfo() {
864
- return {
865
- fonts: new Map(),
866
- medias: new Map(),
867
- supports: new Map()
868
565
  };
869
- }
870
-
871
- function getURL(urlFunction) {
872
- urlFunction = urlFunction.replace(/url\(-sf-url-original\\\(\\"(.*?)\\"\\\)\\ /g, "");
873
- const urlMatch = urlFunction.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
874
- urlFunction.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
875
- urlFunction.match(REGEXP_URL_NO_QUOTES_FN);
876
- return urlMatch && urlMatch[1];
877
566
  }