single-file-core 1.6.5 → 1.6.7
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/helper.js +174 -37
- package/core/index.js +82 -43
- package/core/lib/processor-helper-common.js +29 -5
- package/core/lib/processor-helper-inline.js +143 -21
- package/core/lib/processor-helper.js +183 -76
- package/modules/css-fonts-minifier.js +139 -7
- package/modules/css-rules-minifier.js +32 -6
- package/modules/css-selector-sanitizer.js +12 -0
- package/package.json +1 -1
- package/processors/compression/compression-packager.js +8 -0
- package/processors/compression/compression.js +33 -11
- package/processors/frame-tree/content/content-frame-tree.js +2 -0
- package/processors/hooks/content/content-hooks-frames-web.js +3 -1
|
@@ -414,6 +414,7 @@ class ProcessorHelperCommon {
|
|
|
414
414
|
sheetIndex++;
|
|
415
415
|
});
|
|
416
416
|
processFontDetails(fontsDetails, fonts);
|
|
417
|
+
this.markRepeatedFontFaceRules(fontsDetails);
|
|
417
418
|
await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
|
|
418
419
|
if (stylesheetInfo.stylesheet) {
|
|
419
420
|
const cssRules = stylesheetInfo.stylesheet.children;
|
|
@@ -431,6 +432,21 @@ class ProcessorHelperCommon {
|
|
|
431
432
|
return stats;
|
|
432
433
|
}
|
|
433
434
|
|
|
435
|
+
markRepeatedFontFaceRules(fontsDetails) {
|
|
436
|
+
const lastRules = new Map();
|
|
437
|
+
fontsDetails.fonts.forEach((fontInfo, ruleData) => {
|
|
438
|
+
const ruleKey = this.getFontKey(ruleData) + " " + this.getPropertyValue(ruleData, "src");
|
|
439
|
+
const previousRuleData = lastRules.get(ruleKey);
|
|
440
|
+
if (previousRuleData) {
|
|
441
|
+
fontsDetails.repeatedFonts.add(previousRuleData);
|
|
442
|
+
}
|
|
443
|
+
lastRules.set(ruleKey, ruleData);
|
|
444
|
+
});
|
|
445
|
+
fontsDetails.medias.forEach(mediaFontsDetails => this.markRepeatedFontFaceRules(mediaFontsDetails));
|
|
446
|
+
fontsDetails.supports.forEach(supportsFontsDetails => this.markRepeatedFontFaceRules(supportsFontsDetails));
|
|
447
|
+
fontsDetails.layers.forEach(layerFontsDetails => this.markRepeatedFontFaceRules(layerFontsDetails));
|
|
448
|
+
}
|
|
449
|
+
|
|
434
450
|
async processFontFaceRules(cssRules, sheetIndex, fontsDetails, fonts, fontTests, stats) {
|
|
435
451
|
const removedRules = [];
|
|
436
452
|
let mediaIndex = 0, supportsIndex = 0, layerIndex = 0;
|
|
@@ -451,11 +467,9 @@ class ProcessorHelperCommon {
|
|
|
451
467
|
} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
452
468
|
const fontInfo = fontsDetails.fonts.get(ruleData);
|
|
453
469
|
if (fontInfo) {
|
|
454
|
-
|
|
455
|
-
if (fontsDetails.emittedFonts.has(ruleKey)) {
|
|
470
|
+
if (fontsDetails.repeatedFonts.has(ruleData)) {
|
|
456
471
|
removedRules.push(cssRule);
|
|
457
472
|
} else {
|
|
458
|
-
fontsDetails.emittedFonts.add(ruleKey);
|
|
459
473
|
const keptRule = await this.processFontFaceRule(ruleData, fontInfo, fonts, fontTests, stats);
|
|
460
474
|
if (!keptRule) {
|
|
461
475
|
removedRules.push(cssRule);
|
|
@@ -469,6 +483,12 @@ class ProcessorHelperCommon {
|
|
|
469
483
|
removedRules.forEach(cssRule => cssRules.remove(cssRule));
|
|
470
484
|
}
|
|
471
485
|
|
|
486
|
+
groupDuplicateFonts() {
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
groupDuplicateImages() {
|
|
490
|
+
}
|
|
491
|
+
|
|
472
492
|
getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
|
|
473
493
|
let mediaIndex = 0, supportsIndex = 0, layerIndex = 0;
|
|
474
494
|
cssRules.forEach(ruleData => {
|
|
@@ -516,7 +536,7 @@ class ProcessorHelperCommon {
|
|
|
516
536
|
medias: new Map(),
|
|
517
537
|
supports: new Map(),
|
|
518
538
|
layers: new Map(),
|
|
519
|
-
|
|
539
|
+
repeatedFonts: new Set()
|
|
520
540
|
};
|
|
521
541
|
}
|
|
522
542
|
|
|
@@ -529,7 +549,11 @@ class ProcessorHelperCommon {
|
|
|
529
549
|
getFontStretch(this.getPropertyValue(ruleData, "font-stretch")),
|
|
530
550
|
this.getPropertyValue(ruleData, "font-variant") || "normal",
|
|
531
551
|
this.getPropertyValue(ruleData, "font-feature-settings"),
|
|
532
|
-
this.getPropertyValue(ruleData, "font-variation-settings")
|
|
552
|
+
this.getPropertyValue(ruleData, "font-variation-settings"),
|
|
553
|
+
this.getPropertyValue(ruleData, "ascent-override"),
|
|
554
|
+
this.getPropertyValue(ruleData, "descent-override"),
|
|
555
|
+
this.getPropertyValue(ruleData, "line-gap-override"),
|
|
556
|
+
this.getPropertyValue(ruleData, "size-adjust")
|
|
533
557
|
]);
|
|
534
558
|
}
|
|
535
559
|
|
|
@@ -33,6 +33,13 @@ const Image = globalThis.Image;
|
|
|
33
33
|
|
|
34
34
|
const ABOUT_BLANK_URI = "about:blank";
|
|
35
35
|
const UTF8_CHARSET = "utf-8";
|
|
36
|
+
const DATA_URI_PREFIX = "data:";
|
|
37
|
+
const DEFAULT_FONT_WEIGHT = 400;
|
|
38
|
+
const FONT_KEY_FAMILY_INDEX = 0;
|
|
39
|
+
const FONT_KEY_WEIGHT_INDEX = 1;
|
|
40
|
+
const FONT_KEY_STYLE_INDEX = 2;
|
|
41
|
+
const FONT_KEY_STRETCH_INDEX = 4;
|
|
42
|
+
const NESTED_AT_RULE_NAMES = ["media", "supports", "layer", "container"];
|
|
36
43
|
const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
|
|
37
44
|
const PREFIXES_FORBIDDEN_DATA_URI = ["data:text/"];
|
|
38
45
|
const SCRIPT_TAG_FOUND = /<script/gi;
|
|
@@ -65,6 +72,7 @@ import {
|
|
|
65
72
|
resizeImage,
|
|
66
73
|
toDataURI
|
|
67
74
|
} from "./processor-helper-common.js";
|
|
75
|
+
import { getFontWeight } from "./../helper.js";
|
|
68
76
|
|
|
69
77
|
export {
|
|
70
78
|
getProcessorHelperClass,
|
|
@@ -119,33 +127,40 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
replaceStylesheets(doc, stylesheets, options) {
|
|
122
|
-
doc.querySelectorAll("style")
|
|
130
|
+
const styleElements = Array.from(doc.querySelectorAll("style"));
|
|
131
|
+
styleElements.forEach(element => {
|
|
132
|
+
const stylesheetInfo = stylesheets.get(element);
|
|
133
|
+
if (stylesheetInfo && !options.inlineStylesheetsRefs.has(element)) {
|
|
134
|
+
element.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
|
|
135
|
+
options.inlineStylesheets.forEach(({ styleElement }, index) => {
|
|
136
|
+
if (styleElement === element) {
|
|
137
|
+
options.inlineStylesheets.set(index, {
|
|
138
|
+
styleElement,
|
|
139
|
+
content: element.textContent
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
styleElements.forEach(element => {
|
|
123
146
|
const stylesheetInfo = stylesheets.get(element);
|
|
124
147
|
if (stylesheetInfo) {
|
|
125
148
|
stylesheets.delete(element);
|
|
126
149
|
const stylesheetRefIndex = options.inlineStylesheetsRefs.get(element);
|
|
127
|
-
if (stylesheetRefIndex
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
options.inlineStylesheets.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
if (stylesheetRefIndex !== undefined) {
|
|
151
|
+
if (options.groupDuplicateStylesheets) {
|
|
152
|
+
if (!doc.querySelector("style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]")) {
|
|
153
|
+
const styleElement = doc.createElement("style");
|
|
154
|
+
styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
155
|
+
styleElement.setAttribute("media", "not all");
|
|
156
|
+
styleElement.setAttribute(DUPLICATE_STYLESHEET_ATTRIBUTE_NAME, stylesheetRefIndex);
|
|
157
|
+
doc.head.appendChild(styleElement);
|
|
135
158
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
141
|
-
styleElement.setAttribute("media", "not all");
|
|
142
|
-
styleElement.setAttribute(DUPLICATE_STYLESHEET_ATTRIBUTE_NAME, stylesheetRefIndex);
|
|
143
|
-
doc.head.appendChild(styleElement);
|
|
159
|
+
element.textContent = "/* */";
|
|
160
|
+
element.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]')?.textContent;this.removeAttribute('onload')");
|
|
161
|
+
} else {
|
|
162
|
+
element.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
144
163
|
}
|
|
145
|
-
element.textContent = "/* */";
|
|
146
|
-
element.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]')?.textContent;this.removeAttribute('onload')");
|
|
147
|
-
} else {
|
|
148
|
-
element.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
|
|
149
164
|
}
|
|
150
165
|
if (stylesheetInfo.mediaText) {
|
|
151
166
|
element.media = stylesheetInfo.mediaText;
|
|
@@ -569,6 +584,97 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
569
584
|
doc.querySelectorAll("link[rel*=stylesheet][rel*=alternate][title]").forEach(element => element.remove());
|
|
570
585
|
}
|
|
571
586
|
|
|
587
|
+
groupDuplicateFonts(stylesheets, fonts, options) {
|
|
588
|
+
if (options.usedFonts && options.usedFonts.length) {
|
|
589
|
+
const fontFaces = [];
|
|
590
|
+
stylesheets.forEach(stylesheetInfo => {
|
|
591
|
+
if (stylesheetInfo.stylesheet && stylesheetInfo.stylesheet.children) {
|
|
592
|
+
getFontFaces(stylesheetInfo.stylesheet.children, "", fontFaces);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
const families = new Map();
|
|
596
|
+
fontFaces.forEach(fontFace => {
|
|
597
|
+
const fontKey = JSON.parse(this.getFontKey(fontFace.ruleData));
|
|
598
|
+
const familyKey = fontFace.scope + JSON.stringify([fontKey[FONT_KEY_FAMILY_INDEX],
|
|
599
|
+
fontKey[FONT_KEY_STYLE_INDEX], fontKey[FONT_KEY_STRETCH_INDEX]]);
|
|
600
|
+
let family = families.get(familyKey);
|
|
601
|
+
if (!family) {
|
|
602
|
+
family = { name: fontKey[FONT_KEY_FAMILY_INDEX], groups: new Map(), mergeable: true };
|
|
603
|
+
families.set(familyKey, family);
|
|
604
|
+
}
|
|
605
|
+
const source = this.getPropertyValue(fontFace.ruleData, "src");
|
|
606
|
+
const range = this.getFontWeightRange(fontFace.ruleData);
|
|
607
|
+
if (source && source.includes(DATA_URI_PREFIX) && range) {
|
|
608
|
+
fontKey[FONT_KEY_WEIGHT_INDEX] = null;
|
|
609
|
+
const groupKey = JSON.stringify(fontKey) + " " + source;
|
|
610
|
+
const group = family.groups.get(groupKey);
|
|
611
|
+
if (group) {
|
|
612
|
+
group.push({ fontFace, range });
|
|
613
|
+
} else {
|
|
614
|
+
family.groups.set(groupKey, [{ fontFace, range }]);
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
family.mergeable = false;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
families.forEach(family => this.mergeFontFaceWeights(family, options.usedFonts));
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Every group of the family has to be merged or none of it: two groups that differ only in
|
|
625
|
+
// unicode-range are one composite face, and merging one of them alone would leave a weight
|
|
626
|
+
// matched by two faces that are no longer a composite, which CSS Fonts 4 §5.2 resolves in a way
|
|
627
|
+
// that "can differ between multiple user agents". Requiring every group to span the same set of
|
|
628
|
+
// weights is what keeps the merged faces a composite, and it also blocks the case where some
|
|
629
|
+
// other face of the family sits inside the interval, since that face is a group of its own.
|
|
630
|
+
mergeFontFaceWeights(family, usedFonts) {
|
|
631
|
+
const groups = Array.from(family.groups.values());
|
|
632
|
+
const weightKey = group => group.map(({ range }) => range.join("-")).sort().join(",");
|
|
633
|
+
if (family.mergeable && groups.some(group => group.length > 1) &&
|
|
634
|
+
groups.every(group => weightKey(group) == weightKey(groups[0]))) {
|
|
635
|
+
const ranges = groups[0].map(({ range }) => range);
|
|
636
|
+
const minWeight = Math.min(...ranges.map(([min]) => min));
|
|
637
|
+
const maxWeight = Math.max(...ranges.map(([, max]) => max));
|
|
638
|
+
const declared = weight => ranges.some(([min, max]) => weight >= min && weight <= max);
|
|
639
|
+
const usedWeightInRange = usedFonts.some(([usedFamily, usedWeight]) => {
|
|
640
|
+
const weight = Number(usedWeight);
|
|
641
|
+
return usedFamily == family.name && weight > minWeight && weight < maxWeight && !declared(weight);
|
|
642
|
+
});
|
|
643
|
+
if (!usedWeightInRange) {
|
|
644
|
+
groups.forEach(group => {
|
|
645
|
+
const kept = group[group.length - 1];
|
|
646
|
+
this.setFontWeightRange(kept.fontFace.ruleData, minWeight, maxWeight);
|
|
647
|
+
group.forEach(({ fontFace }) => {
|
|
648
|
+
if (fontFace != kept.fontFace) {
|
|
649
|
+
fontFace.cssRules.remove(fontFace.cssRule);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
getFontWeightRange(ruleData) {
|
|
658
|
+
const value = this.getPropertyValue(ruleData, "font-weight");
|
|
659
|
+
if (value === undefined) {
|
|
660
|
+
return [DEFAULT_FONT_WEIGHT, DEFAULT_FONT_WEIGHT];
|
|
661
|
+
}
|
|
662
|
+
const weights = value.trim().split(/\s+/).map(weight => Number(getFontWeight(util.removeQuotes(weight))));
|
|
663
|
+
if (weights.length <= 2 && weights.every(weight => Number.isFinite(weight))) {
|
|
664
|
+
return [Math.min(...weights), Math.max(...weights)];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
setFontWeightRange(ruleData, minWeight, maxWeight) {
|
|
669
|
+
const value = cssTree.parse(minWeight == maxWeight ? String(minWeight) : minWeight + " " + maxWeight, { context: "value" });
|
|
670
|
+
const declaration = ruleData.block.children.filter(node => node.property == "font-weight").tail;
|
|
671
|
+
if (declaration) {
|
|
672
|
+
declaration.data.value = value;
|
|
673
|
+
} else {
|
|
674
|
+
ruleData.block.children.appendData({ type: "Declaration", property: "font-weight", important: false, value });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
572
678
|
async processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats) {
|
|
573
679
|
const removedNodes = [];
|
|
574
680
|
for (let node = ruleData.block.children.head; node; node = node.next) {
|
|
@@ -662,4 +768,20 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
662
768
|
return true;
|
|
663
769
|
}
|
|
664
770
|
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function getFontFaces(cssRules, scope, fontFaces) {
|
|
774
|
+
for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
|
|
775
|
+
const ruleData = cssRule.data;
|
|
776
|
+
if (ruleData.type == "Atrule" && ruleData.name == "import" && ruleData.prelude && ruleData.prelude.children &&
|
|
777
|
+
ruleData.prelude.children.head.data.importedChildren) {
|
|
778
|
+
getFontFaces(ruleData.prelude.children.head.data.importedChildren,
|
|
779
|
+
scope + "|import " + cssTree.generate(ruleData.prelude), fontFaces);
|
|
780
|
+
} else if (ruleData.type == "Atrule" && NESTED_AT_RULE_NAMES.includes(ruleData.name) && ruleData.block && ruleData.block.children) {
|
|
781
|
+
getFontFaces(ruleData.block.children,
|
|
782
|
+
scope + "|" + ruleData.name + " " + (ruleData.prelude ? cssTree.generate(ruleData.prelude) : ""), fontFaces);
|
|
783
|
+
} else if (ruleData.type == "Atrule" && ruleData.name == "font-face" && ruleData.block && ruleData.block.children) {
|
|
784
|
+
fontFaces.push({ ruleData, cssRule, cssRules, scope });
|
|
785
|
+
}
|
|
786
|
+
}
|
|
665
787
|
}
|
|
@@ -38,6 +38,9 @@ const LOCAL_SOURCE = "local(";
|
|
|
38
38
|
const FONT_MAX_LOAD_DELAY = 5000;
|
|
39
39
|
const SCRIPT_EXTENSION = ".js";
|
|
40
40
|
const LINK_OWN_ATTRIBUTE_NAMES = ["rel", "type", "href", "media"];
|
|
41
|
+
const SRCSET_ATTRIBUTE_NAME = "srcset";
|
|
42
|
+
const IMAGE_ATTRIBUTE_NAMES = ["src", "href", "data", "poster", "background", "xlink:href", SRCSET_ATTRIBUTE_NAME];
|
|
43
|
+
const IMAGE_ATTRIBUTE_SELECTOR = "[src], [href], [data], [poster], [background], [srcset], image, feImage";
|
|
41
44
|
|
|
42
45
|
let util;
|
|
43
46
|
|
|
@@ -103,36 +106,49 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
103
106
|
const entries = Array.from(stylesheets).reverse();
|
|
104
107
|
const linkElements = new Map();
|
|
105
108
|
const sharedStyleElements = new Map();
|
|
109
|
+
const emptyStylesheetRefIndexes = new Set();
|
|
106
110
|
Array.from(new Set(options.inlineStylesheetsRefs.values())).forEach(stylesheetRefIndex => {
|
|
107
|
-
const linkElement = doc.createElement("link");
|
|
108
|
-
linkElement.setAttribute("rel", "stylesheet");
|
|
109
|
-
linkElement.setAttribute("type", "text/css");
|
|
110
|
-
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
111
|
-
linkElement.setAttribute("href", name);
|
|
112
111
|
const { styleElement, content } = options.inlineStylesheets.get(stylesheetRefIndex);
|
|
113
112
|
const sharedEntry = entries.find(([key]) => key.element == styleElement);
|
|
114
113
|
const stylesheet = sharedEntry
|
|
115
114
|
? sharedEntry[1].stylesheet
|
|
116
115
|
: cssTree.parse(content, { context: "stylesheet", parseCustomProperty: true });
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
if (isEmptyStylesheet(stylesheet)) {
|
|
117
|
+
emptyStylesheetRefIndexes.add(stylesheetRefIndex);
|
|
118
|
+
} else {
|
|
119
|
+
const linkElement = doc.createElement("link");
|
|
120
|
+
linkElement.setAttribute("rel", "stylesheet");
|
|
121
|
+
linkElement.setAttribute("type", "text/css");
|
|
122
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
123
|
+
linkElement.setAttribute("href", name);
|
|
124
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet });
|
|
125
|
+
linkElements.set(stylesheetRefIndex, linkElement);
|
|
126
|
+
}
|
|
119
127
|
sharedStyleElements.set(styleElement, stylesheetRefIndex);
|
|
120
128
|
});
|
|
121
129
|
for (const [key, stylesheetInfo] of entries) {
|
|
122
130
|
if (key.urlNode) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
key.urlNode.type = "Url";
|
|
126
|
-
key.urlNode.value = "-sf-url-original(" + JSON.stringify(stylesheetInfo.url) + ") " + name;
|
|
131
|
+
if (isEmptyStylesheet(stylesheetInfo.stylesheet) && !key.importDeclaresLayer) {
|
|
132
|
+
removeImportRule(key.importParent, key.importNode);
|
|
127
133
|
} else {
|
|
128
|
-
|
|
134
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
135
|
+
if (!isDataURL(stylesheetInfo.url) && options.saveOriginalURLs) {
|
|
136
|
+
key.urlNode.type = "Url";
|
|
137
|
+
key.urlNode.value = "-sf-url-original(" + JSON.stringify(stylesheetInfo.url) + ") " + name;
|
|
138
|
+
} else {
|
|
139
|
+
key.urlNode.value = name;
|
|
140
|
+
}
|
|
141
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
129
142
|
}
|
|
130
|
-
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
131
143
|
} else if (key.element.tagName.toUpperCase() == "LINK") {
|
|
132
144
|
const linkElement = key.element;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
if (isEmptyStylesheet(stylesheetInfo.stylesheet)) {
|
|
146
|
+
linkElement.remove();
|
|
147
|
+
} else {
|
|
148
|
+
const name = "stylesheet_" + resources.stylesheets.size + ".css";
|
|
149
|
+
linkElement.setAttribute("href", name);
|
|
150
|
+
resources.stylesheets.set(resources.stylesheets.size, { name, stylesheet: stylesheetInfo.stylesheet, url: stylesheetInfo.url });
|
|
151
|
+
}
|
|
136
152
|
} else {
|
|
137
153
|
const styleElement = key.element;
|
|
138
154
|
const stylesheetRefIndex = options.inlineStylesheetsRefs.has(styleElement)
|
|
@@ -140,6 +156,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
140
156
|
: sharedStyleElements.get(styleElement);
|
|
141
157
|
if (stylesheetRefIndex === undefined) {
|
|
142
158
|
styleElement.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
|
|
159
|
+
} else if (emptyStylesheetRefIndexes.has(stylesheetRefIndex)) {
|
|
160
|
+
styleElement.remove();
|
|
143
161
|
} else {
|
|
144
162
|
const linkElement = linkElements.get(stylesheetRefIndex).cloneNode(true);
|
|
145
163
|
Array.from(styleElement.attributes).forEach(({ name, value }) => {
|
|
@@ -194,35 +212,24 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
194
212
|
if (supportsNode) {
|
|
195
213
|
supportsCondition = cssTree.generate(supportsNode);
|
|
196
214
|
}
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
stylesheetInfo.url = resourceURL = content.resourceURL;
|
|
216
|
-
content.data = getUpdatedResourceContent(resourceURL, options) || content.data;
|
|
217
|
-
stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
|
|
218
|
-
stylesheet = stylesheetInfo.stylesheet;
|
|
219
|
-
const ancestorStyleSheets = new Set(importedStyleSheets);
|
|
220
|
-
ancestorStyleSheets.add(requestedURL);
|
|
221
|
-
ancestorStyleSheets.add(resourceURL);
|
|
222
|
-
await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets, ancestorStyleSheets);
|
|
223
|
-
stylesheets.set({ urlNode }, stylesheetInfo);
|
|
224
|
-
}
|
|
225
|
-
urlNode.importedChildren = stylesheet.children;
|
|
215
|
+
const stylesheetInfo = {
|
|
216
|
+
scoped,
|
|
217
|
+
mediaText,
|
|
218
|
+
layerName,
|
|
219
|
+
supportsCondition
|
|
220
|
+
};
|
|
221
|
+
const importDeclaresLayer = Boolean(cssTree.find(node, node => node.type == "Layer" || ((node.type == "Identifier" || node.type == "Function") && node.name.toLowerCase() == "layer")));
|
|
222
|
+
stylesheets.set({ urlNode, importNode: node, importParent: stylesheet, importDeclaresLayer }, stylesheetInfo);
|
|
223
|
+
const requestedURL = resourceURL;
|
|
224
|
+
const content = await this.getStylesheetContent(resourceURL, options);
|
|
225
|
+
stylesheetInfo.url = resourceURL = content.resourceURL;
|
|
226
|
+
content.data = getUpdatedResourceContent(resourceURL, options) || content.data;
|
|
227
|
+
stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
|
|
228
|
+
const ancestorStyleSheets = new Set(importedStyleSheets);
|
|
229
|
+
ancestorStyleSheets.add(requestedURL);
|
|
230
|
+
ancestorStyleSheets.add(resourceURL);
|
|
231
|
+
await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets, ancestorStyleSheets);
|
|
232
|
+
urlNode.importedChildren = stylesheetInfo.stylesheet.children;
|
|
226
233
|
urlNode.importedMediaText = mediaText;
|
|
227
234
|
urlNode.importedLayerName = layerName;
|
|
228
235
|
urlNode.importedSupportsCondition = supportsCondition;
|
|
@@ -235,36 +242,27 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
235
242
|
async resolveLinkStylesheetURLs(stylesheetInfo, element, resourceURL, baseURI, options, workStylesheet, resources, stylesheets) {
|
|
236
243
|
resourceURL = normalizeURL(resourceURL);
|
|
237
244
|
if (resourceURL && resourceURL != baseURI && resourceURL != ABOUT_BLANK_URI) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
const content = await util.getContent(resourceURL, {
|
|
246
|
+
maxResourceSize: options.maxResourceSize,
|
|
247
|
+
maxResourceSizeEnabled: options.maxResourceSizeEnabled,
|
|
248
|
+
charset: options.charset,
|
|
249
|
+
frameId: options.frameId,
|
|
250
|
+
resourceReferrer: options.resourceReferrer,
|
|
251
|
+
validateTextContentType: true,
|
|
252
|
+
baseURI: baseURI,
|
|
253
|
+
blockMixedContent: options.blockMixedContent,
|
|
254
|
+
expectedType: "stylesheet",
|
|
255
|
+
acceptHeaders: options.acceptHeaders,
|
|
256
|
+
networkTimeout: options.networkTimeout
|
|
257
|
+
});
|
|
258
|
+
if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
|
|
259
|
+
options = Object.assign({}, options, { charset: getCharset(content.data) });
|
|
260
|
+
await this.resolveLinkStylesheetURLs(stylesheetInfo, element, resourceURL, baseURI, options, workStylesheet, resources, stylesheets);
|
|
245
261
|
} else {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
frameId: options.frameId,
|
|
251
|
-
resourceReferrer: options.resourceReferrer,
|
|
252
|
-
validateTextContentType: true,
|
|
253
|
-
baseURI: baseURI,
|
|
254
|
-
blockMixedContent: options.blockMixedContent,
|
|
255
|
-
expectedType: "stylesheet",
|
|
256
|
-
acceptHeaders: options.acceptHeaders,
|
|
257
|
-
networkTimeout: options.networkTimeout
|
|
258
|
-
});
|
|
259
|
-
if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
|
|
260
|
-
options = Object.assign({}, options, { charset: getCharset(content.data) });
|
|
261
|
-
await this.resolveLinkStylesheetURLs(stylesheetInfo, element, resourceURL, baseURI, options, workStylesheet, resources, stylesheets);
|
|
262
|
-
} else {
|
|
263
|
-
resourceURL = content.resourceURL;
|
|
264
|
-
content.data = getUpdatedResourceContent(content.resourceURL, options) || content.data;
|
|
265
|
-
stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
|
|
266
|
-
await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets);
|
|
267
|
-
}
|
|
262
|
+
resourceURL = content.resourceURL;
|
|
263
|
+
content.data = getUpdatedResourceContent(content.resourceURL, options) || content.data;
|
|
264
|
+
stylesheetInfo.stylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
|
|
265
|
+
await this.resolveImportURLs(stylesheetInfo, resourceURL, options, workStylesheet, resources, stylesheets);
|
|
268
266
|
}
|
|
269
267
|
}
|
|
270
268
|
}
|
|
@@ -281,7 +279,7 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
281
279
|
} else {
|
|
282
280
|
frameElement.setAttribute("src", name + "index.html");
|
|
283
281
|
}
|
|
284
|
-
resources.frames.set(frameWindowId, { name, content: pageData.content, resources: pageData.resources, url: frameData.url });
|
|
282
|
+
resources.frames.set(frameWindowId, { name, content: pageData.content, resources: pageData.resources, url: frameData.url, title: pageData.title });
|
|
285
283
|
}
|
|
286
284
|
|
|
287
285
|
async processFont(resourceURL, urlNode, originalResourceURL, baseURI, options, resources, batchRequest) {
|
|
@@ -300,6 +298,69 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
300
298
|
resources.fonts.set(indexResource, { name, content, extension, contentType, url: resourceURL });
|
|
301
299
|
}
|
|
302
300
|
|
|
301
|
+
groupDuplicateFonts(stylesheets, fonts) {
|
|
302
|
+
const canonicalNames = new Map();
|
|
303
|
+
const fontResources = [...fonts]
|
|
304
|
+
.filter(([, resource]) => resource.content)
|
|
305
|
+
.sort(([indexResource], [otherIndexResource]) => indexResource - otherIndexResource);
|
|
306
|
+
fontResources.forEach(([indexResource, resource], index) => {
|
|
307
|
+
const original = fontResources.slice(0, index).find(([, previousResource]) => testSameContent(previousResource.content, resource.content));
|
|
308
|
+
if (original) {
|
|
309
|
+
canonicalNames.set(resource.name, original[1].name);
|
|
310
|
+
fonts.delete(indexResource);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (canonicalNames.size) {
|
|
314
|
+
stylesheets.forEach(stylesheetInfo => {
|
|
315
|
+
if (stylesheetInfo.stylesheet) {
|
|
316
|
+
getUrlFunctions(stylesheetInfo.stylesheet).forEach(urlNode => replaceResourceName(urlNode, canonicalNames));
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
groupDuplicateImages(doc, stylesheets, styles, images) {
|
|
323
|
+
const canonicalNames = new Map();
|
|
324
|
+
const resourcesBySize = new Map();
|
|
325
|
+
[...images]
|
|
326
|
+
.filter(([, resource]) => resource.content && resource.content.length)
|
|
327
|
+
.sort(([indexResource], [otherIndexResource]) => indexResource - otherIndexResource)
|
|
328
|
+
.forEach(([indexResource, resource]) => {
|
|
329
|
+
const sameSizeResources = resourcesBySize.get(resource.content.length) || [];
|
|
330
|
+
const original = sameSizeResources.find(previousResource => testSameContent(previousResource.content, resource.content));
|
|
331
|
+
if (original) {
|
|
332
|
+
canonicalNames.set(resource.name, original.name);
|
|
333
|
+
images.delete(indexResource);
|
|
334
|
+
} else {
|
|
335
|
+
sameSizeResources.push(resource);
|
|
336
|
+
resourcesBySize.set(resource.content.length, sameSizeResources);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
if (canonicalNames.size) {
|
|
340
|
+
stylesheets.forEach(stylesheetInfo => {
|
|
341
|
+
if (stylesheetInfo.stylesheet) {
|
|
342
|
+
getUrlFunctions(stylesheetInfo.stylesheet).forEach(urlNode => replaceResourceName(urlNode, canonicalNames));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
styles.forEach(declarationList => getUrlFunctions(declarationList).forEach(urlNode => replaceResourceName(urlNode, canonicalNames)));
|
|
346
|
+
doc.querySelectorAll(IMAGE_ATTRIBUTE_SELECTOR).forEach(element => {
|
|
347
|
+
IMAGE_ATTRIBUTE_NAMES.forEach(attributeName => {
|
|
348
|
+
const value = element.getAttribute(attributeName);
|
|
349
|
+
if (value !== null) {
|
|
350
|
+
if (canonicalNames.has(value)) {
|
|
351
|
+
element.setAttribute(attributeName, canonicalNames.get(value));
|
|
352
|
+
} else if (attributeName == SRCSET_ATTRIBUTE_NAME) {
|
|
353
|
+
const srcset = util.parseSrcset(value);
|
|
354
|
+
if (srcset.some(srcsetValue => canonicalNames.has(srcsetValue.url))) {
|
|
355
|
+
element.setAttribute(attributeName, serializeSrcset(srcset.map(srcsetValue => Object.assign({}, srcsetValue, { url: canonicalNames.get(srcsetValue.url) || srcsetValue.url }))));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
303
364
|
async processStyle(ruleData, options, resources, batchRequest) {
|
|
304
365
|
const urls = getUrlFunctions(ruleData);
|
|
305
366
|
await Promise.all(urls.map(async urlNode => {
|
|
@@ -428,7 +489,11 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
428
489
|
Object.keys(pageResources).forEach(resourceType => {
|
|
429
490
|
const unusedResources = Array.from(pageResources[resourceType]).filter(([, value]) => !textContent.includes(value.name));
|
|
430
491
|
unusedResources.forEach(([indexResource]) => pageResources[resourceType].delete(indexResource));
|
|
431
|
-
|
|
492
|
+
const orderedResources = Array.from(pageResources[resourceType]);
|
|
493
|
+
if (orderedResources.every(([indexResource]) => typeof indexResource == "number")) {
|
|
494
|
+
orderedResources.sort(([indexResource], [otherIndexResource]) => indexResource - otherIndexResource);
|
|
495
|
+
}
|
|
496
|
+
resources[resourceType] = orderedResources.map(([, resource]) => resource);
|
|
432
497
|
});
|
|
433
498
|
const viewportElement = doc.head.querySelector("meta[name=viewport]");
|
|
434
499
|
const viewport = viewportElement ? viewportElement.content : null;
|
|
@@ -588,4 +653,46 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
588
653
|
return true;
|
|
589
654
|
}
|
|
590
655
|
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function isEmptyStylesheet(stylesheet) {
|
|
659
|
+
return !stylesheet || !stylesheet.children || !stylesheet.children.size;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function removeImportRule(parentStylesheet, importNode) {
|
|
663
|
+
let importItem, importList;
|
|
664
|
+
cssTree.walk(parentStylesheet, {
|
|
665
|
+
visit: "Atrule",
|
|
666
|
+
enter(node, item, list) {
|
|
667
|
+
if (node === importNode) {
|
|
668
|
+
importItem = item;
|
|
669
|
+
importList = list;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
if (importItem) {
|
|
674
|
+
importList.remove(importItem);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function testSameContent(content, otherContent) {
|
|
679
|
+
if (content.length != otherContent.length) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
for (let index = 0; index < content.length; index++) {
|
|
683
|
+
if (content[index] != otherContent[index]) {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function replaceResourceName(urlNode, canonicalNames) {
|
|
691
|
+
canonicalNames.forEach((canonicalName, name) => {
|
|
692
|
+
if (urlNode.value == name) {
|
|
693
|
+
urlNode.value = canonicalName;
|
|
694
|
+
} else if (urlNode.value.endsWith(" " + name)) {
|
|
695
|
+
urlNode.value = urlNode.value.substring(0, urlNode.value.length - name.length) + canonicalName;
|
|
696
|
+
}
|
|
697
|
+
});
|
|
591
698
|
}
|