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,42 +24,14 @@
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;
34
30
 
35
31
  const ABOUT_BLANK_URI = "about:blank";
36
32
  const UTF8_CHARSET = "utf-8";
37
-
38
- const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
39
- const REGEXP_URL_DOUBLE_QUOTES_FN = /url\s*\(\s*"(.*?)"\s*\)/i;
40
- const REGEXP_URL_NO_QUOTES_FN = /url\s*\(\s*(.*?)\s*\)/i;
41
- const REGEXP_URL_FUNCTION = /(url|local|-sf-url-original)\(.*?\)\s*(,|$)/g;
42
- const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
43
- const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
44
- const REGEXP_URL_FUNCTION_WOFF = /^url\(\s*["']?data:font\/(woff2?)/;
45
- const REGEXP_URL_FUNCTION_WOFF_ALT = /^url\(\s*["']?data:application\/x-font-(woff)/;
46
- const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
47
- const REGEXP_FONT_FORMAT_VALUE = /format\((.*?)\)\s*,?$/;
48
- const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
49
33
  const EMPTY_URL_SOURCE = /^url\(["']?data:[^,]*,?["']?\)/;
50
34
  const LOCAL_SOURCE = "local(";
51
- const MEDIA_ALL = "all";
52
- const FONT_STRETCHES = {
53
- "ultra-condensed": "50%",
54
- "extra-condensed": "62.5%",
55
- "condensed": "75%",
56
- "semi-condensed": "87.5%",
57
- "normal": "100%",
58
- "semi-expanded": "112.5%",
59
- "expanded": "125%",
60
- "extra-expanded": "150%",
61
- "ultra-expanded": "200%"
62
- };
63
35
  const FONT_MAX_LOAD_DELAY = 5000;
64
36
 
65
37
  let util;
@@ -89,39 +61,6 @@ function getProcessorHelperClass(utilInstance) {
89
61
  const ProcessorHelperCommon = getProcessorHelperCommonClass(util, cssTree);
90
62
 
91
63
  return class ProcessorHelper extends ProcessorHelperCommon {
92
- async processPageResources(doc, baseURI, options, resources, styles, batchRequest) {
93
- const processAttributeArgs = [
94
- ["link[href][rel*=\"icon\"]", "href", true],
95
- ["object[type=\"image/svg+xml\"], object[type=\"image/svg-xml\"], object[data*=\".svg\"]", "data"],
96
- ["img[src], input[src][type=image]", "src"],
97
- ["embed[src*=\".svg\"]", "src"],
98
- ["video[poster]", "poster"],
99
- ["*[background]", "background"],
100
- ["image", "xlink:href"],
101
- ["image", "href"]
102
- ];
103
- if (options.blockImages) {
104
- doc.querySelectorAll("svg").forEach(element => element.remove());
105
- }
106
- let resourcePromises = processAttributeArgs.map(([selector, attributeName, removeElementIfMissing]) =>
107
- this.processAttribute(doc.querySelectorAll(selector), attributeName, baseURI, options, "image", resources, batchRequest, removeElementIfMissing)
108
- );
109
- resourcePromises = resourcePromises.concat([
110
- this.processXLinks(doc.querySelectorAll("use"), doc, baseURI, options, batchRequest),
111
- this.processSrcset(doc.querySelectorAll("img[srcset], source[srcset]"), baseURI, options, resources, batchRequest)
112
- ]);
113
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("object[data*=\".pdf\"]"), "data", baseURI, options, null, resources, batchRequest));
114
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("embed[src*=\".pdf\"]"), "src", baseURI, options, null, resources, batchRequest));
115
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("audio[src], audio > source[src]"), "src", baseURI, options, "audio", resources, batchRequest));
116
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("video[src], video > source[src]"), "src", baseURI, options, "video", resources, batchRequest));
117
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("audio track[src], video track[src]"), "src", baseURI, options, null, resources, batchRequest));
118
- resourcePromises.push(this.processAttribute(doc.querySelectorAll("model[src]"), "src", baseURI, options, null, resources, batchRequest));
119
- await Promise.all(resourcePromises);
120
- if (options.saveFavicon) {
121
- this.processShortcutIcons(doc);
122
- }
123
- }
124
-
125
64
  async processLinkElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement, resources) {
126
65
  if (element.tagName.toUpperCase() == "LINK") {
127
66
  element.removeAttribute("integrity");
@@ -134,13 +73,7 @@ function getProcessorHelperClass(utilInstance) {
134
73
  }
135
74
 
136
75
  async processStylesheetElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement, resources) {
137
- if (options.blockStylesheets) {
138
- if (element.tagName.toUpperCase() == "LINK") {
139
- element.href = util.EMPTY_RESOURCE;
140
- } else {
141
- element.textContent = "";
142
- }
143
- } else {
76
+ if (!options.blockStylesheets) {
144
77
  if (element.tagName.toUpperCase() == "LINK") {
145
78
  await this.resolveLinkStylesheetURLs(stylesheetInfo, element, element.href, baseURI, options, workStyleElement, resources, stylesheets);
146
79
  } else {
@@ -148,10 +81,16 @@ function getProcessorHelperClass(utilInstance) {
148
81
  stylesheetInfo.stylesheet = cssTree.parse(element.textContent, { context: "stylesheet", parseCustomProperty: true });
149
82
  await this.resolveImportURLs(stylesheetInfo, baseURI, options, workStyleElement, resources, stylesheets);
150
83
  }
84
+ } else {
85
+ if (element.tagName.toUpperCase() == "LINK") {
86
+ element.href = util.EMPTY_RESOURCE;
87
+ } else {
88
+ element.textContent = "";
89
+ }
151
90
  }
152
91
  }
153
92
 
154
- replaceStylesheets(doc, stylesheets, resources, options) {
93
+ replaceStylesheets(doc, stylesheets, options, resources) {
155
94
  for (const [key, stylesheetInfo] of stylesheets) {
156
95
  if (key.urlNode) {
157
96
  const name = "stylesheet_" + resources.stylesheets.size + ".css";
@@ -284,48 +223,20 @@ function getProcessorHelperClass(utilInstance) {
284
223
  resources.frames.set(frameWindowId, { name, content: pageData.content, resources: pageData.resources, url: frameData.url });
285
224
  }
286
225
 
287
- async processStylesheet(cssRules, baseURI, options, resources, batchRequest) {
288
- const promises = [];
289
- const removedRules = [];
290
- for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
291
- const ruleData = cssRule.data;
292
- if (ruleData.type == "Atrule" && ruleData.name == "charset") {
293
- removedRules.push(cssRule);
294
- } else if (ruleData.block && ruleData.block.children) {
295
- if (ruleData.type == "Rule") {
296
- promises.push(this.processStyle(ruleData, options, resources, batchRequest));
297
- } else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports")) {
298
- promises.push(this.processStylesheet(ruleData.block.children, baseURI, options, resources, batchRequest));
299
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
300
- promises.push(processFontFaceRule(ruleData));
301
- }
302
- }
303
- }
304
- removedRules.forEach(cssRule => cssRules.remove(cssRule));
305
- await Promise.all(promises);
306
-
307
- async function processFontFaceRule(ruleData) {
308
- const urls = getUrlFunctions(ruleData);
309
- await Promise.all(urls.map(async urlNode => {
310
- const originalResourceURL = urlNode.value;
311
- if (!options.blockFonts) {
312
- const resourceURL = normalizeURL(originalResourceURL);
313
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
314
- let { content, extension, indexResource, contentType } = await batchRequest.addURL(resourceURL,
315
- { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
316
- const name = "fonts/" + indexResource + extension;
317
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
318
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + name;
319
- } else {
320
- urlNode.value = name;
321
- }
322
- resources.fonts.set(indexResource, { name, content, extension, contentType, url: resourceURL });
323
- }
324
- } else {
325
- urlNode.value = util.EMPTY_RESOURCE;
326
- }
327
- }));
226
+ async processFont(resourceURL, urlNode, originalResourceURL, baseURI, options, resources, batchRequest) {
227
+ let { content, extension, indexResource, contentType } = await batchRequest.addURL(resourceURL, {
228
+ asBinary: true,
229
+ expectedType: "font",
230
+ baseURI,
231
+ blockMixedContent: options.blockMixedContent
232
+ });
233
+ const name = "fonts/" + indexResource + extension;
234
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
235
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + name;
236
+ } else {
237
+ urlNode.value = name;
328
238
  }
239
+ resources.fonts.set(indexResource, { name, content, extension, contentType, url: resourceURL });
329
240
  }
330
241
 
331
242
  async processStyle(ruleData, options, resources, batchRequest) {
@@ -351,7 +262,7 @@ function getProcessorHelperClass(utilInstance) {
351
262
  }));
352
263
  }
353
264
 
354
- async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, resources, batchRequest, removeElementIfMissing) {
265
+ async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, resources, removeElementIfMissing, batchRequest) {
355
266
  await Promise.all(Array.from(resourceElements).map(async resourceElement => {
356
267
  let resourceURL = resourceElement.getAttribute(attributeName);
357
268
  if (resourceURL != null) {
@@ -422,43 +333,11 @@ function getProcessorHelperClass(utilInstance) {
422
333
  }
423
334
  }
424
335
 
425
- async processSrcset(resourceElements, baseURI, options, resources, batchRequest) {
426
- await Promise.all(Array.from(resourceElements).map(async resourceElement => {
427
- const originSrcset = resourceElement.getAttribute("srcset");
428
- const srcset = util.parseSrcset(originSrcset);
429
- if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
430
- resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
431
- }
432
- if (!options.blockImages) {
433
- const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
434
- let resourceURL = normalizeURL(srcsetValue.url);
435
- if (!testIgnoredPath(resourceURL)) {
436
- if (testValidPath(resourceURL)) {
437
- try {
438
- resourceURL = util.resolveURL(resourceURL, baseURI);
439
- } catch (error) {
440
- // ignored
441
- }
442
- if (testValidURL(resourceURL)) {
443
- const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
444
- const name = "images/" + indexResource + extension;
445
- resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
446
- return name + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
447
- } else {
448
- return "";
449
- }
450
- } else {
451
- return "";
452
- }
453
- } else {
454
- return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
455
- }
456
- }));
457
- resourceElement.setAttribute("srcset", srcsetValues.join(", "));
458
- } else {
459
- resourceElement.setAttribute("srcset", "");
460
- }
461
- }));
336
+ async processImageSrcset(resourceURL, srcsetValue, resources, batchRequest) {
337
+ const { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
338
+ const name = "images/" + indexResource + extension;
339
+ resources.images.set(indexResource, { name, content, extension, contentType, url: resourceURL });
340
+ return name + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
462
341
  }
463
342
 
464
343
  testEmptyResource(resource) {
@@ -499,10 +378,6 @@ function getProcessorHelperClass(utilInstance) {
499
378
  };
500
379
  }
501
380
 
502
- removeAlternativeFonts(doc, stylesheets, fontResources, fontTests) {
503
- return removeAlternativeFonts(doc, stylesheets, fontResources, fontTests);
504
- }
505
-
506
381
  async processScript(element, resourceURL, options, charset) {
507
382
  const content = await util.getContent(resourceURL, {
508
383
  asBinary: true,
@@ -527,276 +402,91 @@ function getProcessorHelperClass(utilInstance) {
527
402
 
528
403
  removeUnusedStylesheets() {
529
404
  }
530
- };
531
- }
532
-
533
- async function removeAlternativeFonts(doc, stylesheets, fontResources, fontTests) {
534
- const fontsDetails = {
535
- fonts: new Map(),
536
- medias: new Map(),
537
- supports: new Map()
538
- };
539
- const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
540
- let sheetIndex = 0;
541
- stylesheets.forEach(stylesheetInfo => {
542
- const cssRules = stylesheetInfo.stylesheet.children;
543
- if (cssRules) {
544
- stats.rules.processed += cssRules.size;
545
- stats.rules.discarded += cssRules.size;
546
- if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
547
- const mediaFontsDetails = createFontsDetailsInfo();
548
- fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
549
- getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
550
- } else {
551
- getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
552
- }
553
- }
554
- sheetIndex++;
555
- });
556
- processFontDetails(fontsDetails, fontResources);
557
- await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
558
- const cssRules = stylesheetInfo.stylesheet.children;
559
- const media = stylesheetInfo.mediaText;
560
- if (cssRules) {
561
- if (media && media != MEDIA_ALL) {
562
- await processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), fontResources, fontTests, stats);
563
- } else {
564
- await processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontResources, fontTests, stats);
565
- }
566
- stats.rules.discarded -= cssRules.size;
567
- }
568
- }));
569
- return stats;
570
- }
571
-
572
- function getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
573
- let mediaIndex = 0, supportsIndex = 0;
574
- cssRules.forEach(ruleData => {
575
- if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
576
- const mediaText = cssTree.generate(ruleData.prelude);
577
- const fontsDetails = createFontsDetailsInfo();
578
- mediaFontsDetails.medias.set("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText, fontsDetails);
579
- mediaIndex++;
580
- getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
581
- } else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
582
- const supportsText = cssTree.generate(ruleData.prelude);
583
- const fontsDetails = createFontsDetailsInfo();
584
- mediaFontsDetails.supports.set("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText, fontsDetails);
585
- supportsIndex++;
586
- getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
587
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face" && ruleData.block && ruleData.block.children) {
588
- const fontKey = getFontKey(ruleData);
589
- let fontInfo = mediaFontsDetails.fonts.get(fontKey);
590
- if (!fontInfo) {
591
- fontInfo = [];
592
- mediaFontsDetails.fonts.set(fontKey, fontInfo);
593
- }
594
- const src = getPropertyValue(ruleData, "src");
595
- if (src) {
596
- const fontSources = src.match(REGEXP_URL_FUNCTION);
597
- if (fontSources) {
598
- fontSources.forEach(source => fontInfo.unshift(source));
599
- }
600
- }
601
- }
602
- });
603
- }
604
405
 
605
- function processFontDetails(fontsDetails, fontResources) {
606
- fontsDetails.fonts.forEach((fontInfo, fontKey) => {
607
- fontsDetails.fonts.set(fontKey, fontInfo.map(fontSource => {
608
- const fontFormatMatch = fontSource.match(REGEXP_FONT_FORMAT_VALUE);
609
- let fontFormat;
610
- const fontUrl = getURL(fontSource);
611
- if (fontFormatMatch && fontFormatMatch[1]) {
612
- fontFormat = fontFormatMatch[1].replace(REGEXP_SIMPLE_QUOTES_STRING, "$1").replace(REGEXP_DOUBLE_QUOTES_STRING, "$1").toLowerCase();
613
- }
614
- if (!fontFormat) {
615
- const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF);
616
- if (fontFormatMatch && fontFormatMatch[1]) {
617
- fontFormat = fontFormatMatch[1];
406
+ async processFontFaceRule(ruleData, fontInfo, fontResources, fontTests, stats) {
407
+ await Promise.all(fontInfo.map(async source => {
408
+ if (fontTests.has(source.src)) {
409
+ source.valid = fontTests.get(source.src);
618
410
  } else {
619
- const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF_ALT);
620
- if (fontFormatMatch && fontFormatMatch[1]) {
621
- fontFormat = fontFormatMatch[1];
411
+ if (FontFace && source.fontUrl) {
412
+ const resourceEntry = [...fontResources].find(([, resource]) => source.fontUrl && resource.name == source.fontUrl);
413
+ if (resourceEntry) {
414
+ const resource = resourceEntry[1];
415
+ const fontFace = new FontFace("test-font", new Uint8Array(resource.content).buffer);
416
+ try {
417
+ let timeout;
418
+ await Promise.race([
419
+ fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
420
+ new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
421
+ ]);
422
+ } catch (error) {
423
+ const fontFace = new FontFace("test-font", "url(" + resource.url + ")");
424
+ try {
425
+ let timeout;
426
+ await Promise.race([
427
+ fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
428
+ new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
429
+ ]);
430
+ } catch (error) {
431
+ // ignored
432
+ }
433
+ }
434
+ } else {
435
+ source.valid = true;
436
+ }
437
+ } else {
438
+ source.valid = true;
622
439
  }
440
+ fontTests.set(source.src, source.valid);
623
441
  }
624
- }
625
- if (!fontFormat && fontUrl) {
626
- const fontFormatMatch = fontUrl.match(REGEXP_FONT_FORMAT);
627
- if (fontFormatMatch && fontFormatMatch[1]) {
628
- fontFormat = fontFormatMatch[1];
629
- }
630
- }
631
- const fontResource = Array.from(fontResources.values()).find(info => info.name == fontUrl);
632
- return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat, contentType: fontResource && fontResource.contentType };
633
- }));
634
- });
635
- fontsDetails.medias.forEach(mediaFontsDetails => processFontDetails(mediaFontsDetails, fontResources));
636
- fontsDetails.supports.forEach(supportsFontsDetails => processFontDetails(supportsFontsDetails, fontResources));
637
- }
638
-
639
- async function processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontResources, fontTests, stats) {
640
- const removedRules = [];
641
- let mediaIndex = 0, supportsIndex = 0;
642
- for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
643
- const ruleData = cssRule.data;
644
- if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
645
- const mediaText = cssTree.generate(ruleData.prelude);
646
- await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText), fontResources, fontTests, stats);
647
- mediaIndex++;
648
- } else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
649
- const supportsText = cssTree.generate(ruleData.prelude);
650
- await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.supports.get("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText), fontResources, fontTests, stats);
651
- supportsIndex++;
652
- } else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
653
- const key = getFontKey(ruleData);
654
- const fontInfo = fontsDetails.fonts.get(key);
655
- if (fontInfo) {
656
- fontsDetails.fonts.delete(key);
657
- await processFontFaceRule(ruleData, fontInfo, fontResources, fontTests, stats);
442
+ }));
443
+ const findSourceByFormat = (fontFormat, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
444
+ const findSourceByContentType = (contentType, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.contentType == contentType && (!testValidity || source.valid));
445
+ const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
446
+ stats.fonts.processed += fontInfo.length;
447
+ stats.fonts.discarded += fontInfo.length;
448
+ const woffFontFound =
449
+ findSourceByFormat("woff2-variations", true) || findSourceByFormat("woff2", true) || findSourceByFormat("woff", true) ||
450
+ findSourceByContentType("font/woff2", true) || findSourceByContentType("font/woff", true) || findSourceByContentType("application/font-woff", true) || findSourceByContentType("application/x-font-woff", true);
451
+ if (woffFontFound) {
452
+ fontInfo = filterSources(woffFontFound);
658
453
  } else {
659
- removedRules.push(cssRule);
660
- }
661
- }
662
- }
663
- removedRules.forEach(cssRule => cssRules.remove(cssRule));
664
- }
665
-
666
- async function processFontFaceRule(ruleData, fontInfo, fontResources, fontTests, stats) {
667
- await Promise.all(fontInfo.map(async source => {
668
- if (fontTests.has(source.src)) {
669
- source.valid = fontTests.get(source.src);
670
- } else {
671
- if (FontFace && source.fontUrl) {
672
- const resourceEntry = [...fontResources].find(([, resource]) => source.fontUrl && resource.name == source.fontUrl);
673
- if (resourceEntry) {
674
- const resource = resourceEntry[1];
675
- const fontFace = new FontFace("test-font", new Uint8Array(resource.content).buffer);
676
- try {
677
- let timeout;
678
- await Promise.race([
679
- fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
680
- new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
681
- ]);
682
- } catch (error) {
683
- const fontFace = new FontFace("test-font", "url(" + resource.url + ")");
684
- try {
685
- let timeout;
686
- await Promise.race([
687
- fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
688
- new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
689
- ]);
690
- } catch (error) {
691
- // ignored
692
- }
693
- }
454
+ const ttfFontFound =
455
+ findSourceByFormat("truetype-variations", true) || findSourceByFormat("truetype", true) ||
456
+ findSourceByContentType("font/ttf", true) || findSourceByContentType("application/x-font-ttf", true) || findSourceByContentType("application/x-font-ttf", true) || findSourceByContentType("application/x-font-truetype", true);
457
+ if (ttfFontFound) {
458
+ fontInfo = filterSources(ttfFontFound);
694
459
  } else {
695
- source.valid = true;
460
+ const otfFontFound =
461
+ findSourceByFormat("opentype") || findSourceByFormat("embedded-opentype") ||
462
+ findSourceByContentType("font/otf") || findSourceByContentType("application/x-font-opentype") || findSourceByContentType("application/font-sfnt");
463
+ if (otfFontFound) {
464
+ fontInfo = filterSources(otfFontFound);
465
+ } else {
466
+ fontInfo = fontInfo.filter(source => !source.src.match(EMPTY_URL_SOURCE) && (source.valid) || source.src.startsWith(LOCAL_SOURCE));
467
+ }
696
468
  }
697
- } else {
698
- source.valid = true;
699
469
  }
700
- fontTests.set(source.src, source.valid);
701
- }
702
- }));
703
- const findSourceByFormat = (fontFormat, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
704
- const findSourceByContentType = (contentType, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.contentType == contentType && (!testValidity || source.valid));
705
- const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
706
- stats.fonts.processed += fontInfo.length;
707
- stats.fonts.discarded += fontInfo.length;
708
- const woffFontFound =
709
- findSourceByFormat("woff2-variations", true) || findSourceByFormat("woff2", true) || findSourceByFormat("woff", true) ||
710
- findSourceByContentType("font/woff2", true) || findSourceByContentType("font/woff", true) || findSourceByContentType("application/font-woff", true) || findSourceByContentType("application/x-font-woff", true);
711
- if (woffFontFound) {
712
- fontInfo = filterSources(woffFontFound);
713
- } else {
714
- const ttfFontFound =
715
- findSourceByFormat("truetype-variations", true) || findSourceByFormat("truetype", true) ||
716
- findSourceByContentType("font/ttf", true) || findSourceByContentType("application/x-font-ttf", true) || findSourceByContentType("application/x-font-ttf", true) || findSourceByContentType("application/x-font-truetype", true);
717
- if (ttfFontFound) {
718
- fontInfo = filterSources(ttfFontFound);
719
- } else {
720
- const otfFontFound =
721
- findSourceByFormat("opentype") || findSourceByFormat("embedded-opentype") ||
722
- findSourceByContentType("font/otf") || findSourceByContentType("application/x-font-opentype") || findSourceByContentType("application/font-sfnt");
723
- if (otfFontFound) {
724
- fontInfo = filterSources(otfFontFound);
725
- } else {
726
- fontInfo = fontInfo.filter(source => !source.src.match(EMPTY_URL_SOURCE) && (source.valid) || source.src.startsWith(LOCAL_SOURCE));
470
+ stats.fonts.discarded -= fontInfo.length;
471
+ const removedNodes = [];
472
+ for (let node = ruleData.block.children.head; node; node = node.next) {
473
+ if (node.data.property == "src") {
474
+ removedNodes.push(node);
475
+ }
727
476
  }
728
- }
729
- }
730
- stats.fonts.discarded -= fontInfo.length;
731
- const removedNodes = [];
732
- for (let node = ruleData.block.children.head; node; node = node.next) {
733
- if (node.data.property == "src") {
734
- removedNodes.push(node);
735
- }
736
- }
737
- removedNodes.pop();
738
- removedNodes.forEach(node => ruleData.block.children.remove(node));
739
- const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
740
- if (srcDeclaration) {
741
- fontInfo.reverse();
742
- try {
743
- srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value", parseCustomProperty: true });
744
- }
745
- catch (error) {
746
- // ignored
747
- }
748
- }
749
- }
750
-
751
- function getPropertyValue(ruleData, propertyName) {
752
- let property;
753
- if (ruleData.block.children) {
754
- property = ruleData.block.children.filter(node => {
755
- try {
756
- return node.property == propertyName && !cssTree.generate(node.value).match(/\\9$/);
757
- } catch (error) {
758
- return node.property == propertyName;
477
+ removedNodes.pop();
478
+ removedNodes.forEach(node => ruleData.block.children.remove(node));
479
+ const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
480
+ if (srcDeclaration) {
481
+ fontInfo.reverse();
482
+ try {
483
+ srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value", parseCustomProperty: true });
484
+ }
485
+ catch (error) {
486
+ // ignored
487
+ }
488
+ return true;
759
489
  }
760
- }).tail;
761
- }
762
- if (property) {
763
- try {
764
- return cssTree.generate(property.data.value);
765
- } catch (error) {
766
- // ignored
767
490
  }
768
- }
769
- }
770
-
771
- function getFontKey(ruleData) {
772
- return JSON.stringify([
773
- normalizeFontFamily(getPropertyValue(ruleData, "font-family")),
774
- getFontWeight(getPropertyValue(ruleData, "font-weight") || "400"),
775
- getPropertyValue(ruleData, "font-style") || "normal",
776
- getPropertyValue(ruleData, "unicode-range"),
777
- getFontStretch(getPropertyValue(ruleData, "font-stretch")),
778
- getPropertyValue(ruleData, "font-variant") || "normal",
779
- getPropertyValue(ruleData, "font-feature-settings"),
780
- getPropertyValue(ruleData, "font-variation-settings")
781
- ]);
782
- }
783
-
784
- function getFontStretch(stretch) {
785
- return FONT_STRETCHES[stretch] || stretch;
786
- }
787
-
788
- function createFontsDetailsInfo() {
789
- return {
790
- fonts: new Map(),
791
- medias: new Map(),
792
- supports: new Map()
793
491
  };
794
- }
795
-
796
- function getURL(urlFunction) {
797
- urlFunction = urlFunction.replace(/url\(-sf-url-original\\\(\\"(.*?)\\"\\\)\\ /g, "");
798
- const urlMatch = urlFunction.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
799
- urlFunction.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
800
- urlFunction.match(REGEXP_URL_NO_QUOTES_FN);
801
- return urlMatch && urlMatch[1];
802
492
  }