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.
- package/core/index.js +1 -1
- package/core/lib/processor-helper-common.js +373 -42
- package/core/lib/processor-helper-inline.js +101 -412
- package/core/lib/processor-helper.js +102 -412
- package/modules/template-formatter.js +27 -12
- package/package.json +1 -1
- package/processors/compression/compression-extract.js +12 -1
- package/processors/compression/compression.js +6 -6
- package/single-file.js +1 -0
|
@@ -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,
|
|
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
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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,
|
|
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
|
|
426
|
-
await
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
-
|
|
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
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
}
|