single-file-core 1.6.22 → 1.6.24
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 +27 -4
- package/core/lib/processor-helper-common.js +49 -10
- package/core/lib/processor-helper-inline.js +2 -2
- package/core/lib/processor-helper.js +3 -3
- package/modules/css-fonts-minifier.js +65 -47
- package/modules/css-media-query-utils.js +44 -44
- package/modules/css-medias-alt-minifier.js +3 -0
- package/modules/css-nested-rules.js +44 -0
- package/modules/css-rules-minifier.js +1 -2
- package/modules/css-scope-prelude-parser.js +61 -61
- package/modules/css-selector-sanitizer.js +79 -79
- package/modules/css-specificity.js +138 -110
- package/modules/html-minifier.js +1 -1
- package/package.json +1 -1
- package/processors/compression/compression-display.js +5 -2
- package/processors/compression/compression.js +5 -0
- package/processors/hooks/content/content-hooks-frames-web.js +25 -12
- package/vendor/zip/zip.js +31 -15
- package/vendor/zip/zip.min.js +1 -1
package/core/index.js
CHANGED
|
@@ -911,7 +911,7 @@ class Processor {
|
|
|
911
911
|
const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_OPTIONS + "])");
|
|
912
912
|
this.stats.set("discarded", "scripts", scriptElements.length);
|
|
913
913
|
this.stats.set("processed", "scripts", scriptElements.length);
|
|
914
|
-
scriptElements.forEach(element => element
|
|
914
|
+
scriptElements.forEach(element => removeElement(element));
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
removeDiscardedResources() {
|
|
@@ -929,7 +929,7 @@ class Processor {
|
|
|
929
929
|
placeholderElement.replaceWith(noscriptElement);
|
|
930
930
|
});
|
|
931
931
|
} else {
|
|
932
|
-
this.doc.querySelectorAll("noscript").forEach(element => element
|
|
932
|
+
this.doc.querySelectorAll("noscript").forEach(element => removeElement(element));
|
|
933
933
|
}
|
|
934
934
|
this.doc.querySelectorAll("meta[http-equiv=refresh], meta[disabled-http-equiv]").forEach(element => element.remove());
|
|
935
935
|
this.doc.querySelectorAll("meta[http-equiv=\"content-security-policy\"]").forEach(element => element.remove());
|
|
@@ -946,13 +946,13 @@ class Processor {
|
|
|
946
946
|
if (relValue.length) {
|
|
947
947
|
element.setAttribute("rel", relValue);
|
|
948
948
|
} else {
|
|
949
|
-
element
|
|
949
|
+
removeElement(element);
|
|
950
950
|
}
|
|
951
951
|
});
|
|
952
952
|
this.processorHelper.removeUnusedStylesheets(this.doc);
|
|
953
953
|
this.doc.querySelectorAll("link[rel*=stylesheet]:not([href]),link[rel*=stylesheet][href=\"\"]").forEach(element => element.remove());
|
|
954
954
|
if (this.options.removeHiddenElements) {
|
|
955
|
-
this.doc.querySelectorAll("input[type=hidden]").forEach(element => element
|
|
955
|
+
this.doc.querySelectorAll("input[type=hidden]").forEach(element => removeElement(element));
|
|
956
956
|
}
|
|
957
957
|
if (this.options.removedElementsSelector) {
|
|
958
958
|
try {
|
|
@@ -1075,6 +1075,9 @@ class Processor {
|
|
|
1075
1075
|
moveStylesInHead() {
|
|
1076
1076
|
this.doc.querySelectorAll("style").forEach(stylesheet => {
|
|
1077
1077
|
if (stylesheet.getAttribute(util.STYLE_ATTRIBUTE_NAME) == "") {
|
|
1078
|
+
if (testSiblingPositionKept(stylesheet)) {
|
|
1079
|
+
stylesheet.before(createPlaceholderElement(stylesheet));
|
|
1080
|
+
}
|
|
1078
1081
|
this.doc.head.appendChild(stylesheet);
|
|
1079
1082
|
}
|
|
1080
1083
|
});
|
|
@@ -1816,6 +1819,26 @@ function normalizeURL(url) {
|
|
|
1816
1819
|
}
|
|
1817
1820
|
}
|
|
1818
1821
|
|
|
1822
|
+
function removeElement(element) {
|
|
1823
|
+
if (testSiblingPositionKept(element)) {
|
|
1824
|
+
element.replaceWith(createPlaceholderElement(element));
|
|
1825
|
+
} else {
|
|
1826
|
+
element.remove();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function testSiblingPositionKept(element) {
|
|
1831
|
+
return Boolean(element.parentElement) && element.parentElement != element.ownerDocument.head;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function createPlaceholderElement(element) {
|
|
1835
|
+
const placeholderElement = element.ownerDocument.createElementNS(element.namespaceURI, element.localName);
|
|
1836
|
+
if (element.localName == "input") {
|
|
1837
|
+
placeholderElement.setAttribute("type", "hidden");
|
|
1838
|
+
}
|
|
1839
|
+
return placeholderElement;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1819
1842
|
function isScriptURI(value) {
|
|
1820
1843
|
try {
|
|
1821
1844
|
return new URL(value).protocol == JAVASCRIPT_URI_PROTOCOL;
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
getDataURI
|
|
30
30
|
} from "./../helper.js";
|
|
31
31
|
import { serialize as serializeSrcset } from "./../../vendor/html-srcset-parser.js";
|
|
32
|
+
import { getNestedChildren } from "./../../modules/css-nested-rules.js";
|
|
33
|
+
import { decodeName } from "./../../modules/css-identifier.js";
|
|
32
34
|
|
|
33
35
|
const DATA_URI_PREFIX = "data:";
|
|
34
36
|
const ABOUT_BLANK_URI = "about:blank";
|
|
@@ -50,7 +52,9 @@ const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
|
|
|
50
52
|
const REGEXP_URL_FUNCTION_WOFF = /^url\(\s*["']?data:font\/(woff2?)/;
|
|
51
53
|
const REGEXP_URL_FUNCTION_WOFF_ALT = /^url\(\s*["']?data:application\/x-font-(woff)/;
|
|
52
54
|
const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
|
|
53
|
-
const REGEXP_FONT_FORMAT_VALUE = /format\((
|
|
55
|
+
const REGEXP_FONT_FORMAT_VALUE = /format\(\s*([^)]*?)\s*\)/;
|
|
56
|
+
const REGEXP_FONT_TECH_VALUE = /tech\(\s*([^)]*?)\s*\)/;
|
|
57
|
+
const REGEXP_FONT_FORMAT_SUPPORTS = /^(\S+)\s+supports\s+(.+)$/;
|
|
54
58
|
const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
|
|
55
59
|
const MEDIA_ALL = "all";
|
|
56
60
|
const Blob = globalThis.Blob;
|
|
@@ -173,7 +177,7 @@ class ProcessorHelperCommon {
|
|
|
173
177
|
}));
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
async processStylesheet(cssRules, baseURI, options, resources, batchRequest) {
|
|
180
|
+
async processStylesheet(cssRules, baseURI, options, resources, batchRequest, fontFaceInvalid = false) {
|
|
177
181
|
const promises = [];
|
|
178
182
|
const removedRules = [];
|
|
179
183
|
const processorHelper = this;
|
|
@@ -182,13 +186,22 @@ class ProcessorHelperCommon {
|
|
|
182
186
|
if (ruleData.type == "Atrule" && ruleData.name == "charset") {
|
|
183
187
|
removedRules.push(cssRule);
|
|
184
188
|
} else if (ruleData.block && ruleData.block.children) {
|
|
185
|
-
if (ruleData.type == "
|
|
189
|
+
if (ruleData.type == "Atrule" && decodeName(ruleData.name) == "font-face") {
|
|
190
|
+
if (fontFaceInvalid) {
|
|
191
|
+
removedRules.push(cssRule);
|
|
192
|
+
} else {
|
|
193
|
+
promises.push(processFontFaceRule(ruleData));
|
|
194
|
+
}
|
|
195
|
+
} else if (ruleData.type == "Rule") {
|
|
196
|
+
removeFontFaceRules(ruleData.block.children);
|
|
197
|
+
promises.push(processorHelper.processStyle(ruleData, options, resources, batchRequest));
|
|
198
|
+
} else if (ruleData.type == "Atrule" && getNestedChildren(ruleData)) {
|
|
199
|
+
promises.push(processorHelper.processStylesheet(ruleData.block.children, baseURI, options, resources, batchRequest, fontFaceInvalid || decodeName(ruleData.name) == "scope"));
|
|
200
|
+
} else {
|
|
186
201
|
promises.push(processorHelper.processStyle(ruleData, options, resources, batchRequest));
|
|
187
|
-
} else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container")) {
|
|
188
|
-
promises.push(processorHelper.processStylesheet(ruleData.block.children, baseURI, options, resources, batchRequest));
|
|
189
|
-
} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
190
|
-
promises.push(processFontFaceRule(ruleData));
|
|
191
202
|
}
|
|
203
|
+
} else if (ruleData.type == "Declaration") {
|
|
204
|
+
promises.push(processorHelper.processStyle(ruleData, options, resources, batchRequest));
|
|
192
205
|
}
|
|
193
206
|
}
|
|
194
207
|
removedRules.forEach(cssRule => cssRules.remove(cssRule));
|
|
@@ -572,6 +585,7 @@ class ProcessorHelperCommon {
|
|
|
572
585
|
this.getPropertyValue(ruleData, "font-style") || "normal",
|
|
573
586
|
this.getPropertyValue(ruleData, "unicode-range"),
|
|
574
587
|
getFontStretch(this.getPropertyValue(ruleData, "font-stretch")),
|
|
588
|
+
this.getPropertyValue(ruleData, "font-width"),
|
|
575
589
|
this.getPropertyValue(ruleData, "font-variant") || "normal",
|
|
576
590
|
this.getPropertyValue(ruleData, "font-feature-settings"),
|
|
577
591
|
this.getPropertyValue(ruleData, "font-variation-settings"),
|
|
@@ -609,10 +623,19 @@ function processFontDetails(fontsDetails, fontResources) {
|
|
|
609
623
|
fontsDetails.fonts.forEach((fontInfo, fontKey) => {
|
|
610
624
|
fontsDetails.fonts.set(fontKey, fontInfo.map(fontSource => {
|
|
611
625
|
const fontFormatMatch = fontSource.match(REGEXP_FONT_FORMAT_VALUE);
|
|
612
|
-
|
|
626
|
+
const fontTechMatch = fontSource.match(REGEXP_FONT_TECH_VALUE);
|
|
627
|
+
let fontFormat, fontTech;
|
|
613
628
|
const fontUrl = getURL(fontSource);
|
|
614
629
|
if (fontFormatMatch && fontFormatMatch[1]) {
|
|
615
630
|
fontFormat = fontFormatMatch[1].replace(REGEXP_SIMPLE_QUOTES_STRING, "$1").replace(REGEXP_DOUBLE_QUOTES_STRING, "$1").toLowerCase();
|
|
631
|
+
const fontFormatSupportsMatch = fontFormat.match(REGEXP_FONT_FORMAT_SUPPORTS);
|
|
632
|
+
if (fontFormatSupportsMatch) {
|
|
633
|
+
fontFormat = fontFormatSupportsMatch[1];
|
|
634
|
+
fontTech = fontFormatSupportsMatch[2];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (fontTechMatch && fontTechMatch[1]) {
|
|
638
|
+
fontTech = fontTechMatch[1].toLowerCase();
|
|
616
639
|
}
|
|
617
640
|
if (!fontFormat) {
|
|
618
641
|
const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF);
|
|
@@ -633,9 +656,9 @@ function processFontDetails(fontsDetails, fontResources) {
|
|
|
633
656
|
}
|
|
634
657
|
if (fontResources) {
|
|
635
658
|
const fontResource = Array.from(fontResources.values()).find(info => info.name == fontUrl);
|
|
636
|
-
return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat, contentType: fontResource && fontResource.contentType };
|
|
659
|
+
return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat, tech: fontTech, contentType: fontResource && fontResource.contentType };
|
|
637
660
|
} else {
|
|
638
|
-
return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat };
|
|
661
|
+
return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat, tech: fontTech };
|
|
639
662
|
}
|
|
640
663
|
}));
|
|
641
664
|
});
|
|
@@ -681,6 +704,22 @@ function getCharset(stylesheetContent = "") {
|
|
|
681
704
|
}
|
|
682
705
|
}
|
|
683
706
|
|
|
707
|
+
function removeFontFaceRules(cssRules) {
|
|
708
|
+
const removedRules = [];
|
|
709
|
+
for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
|
|
710
|
+
const ruleData = cssRule.data;
|
|
711
|
+
if (ruleData.type == "Atrule" && decodeName(ruleData.name) == "font-face") {
|
|
712
|
+
removedRules.push(cssRule);
|
|
713
|
+
} else {
|
|
714
|
+
const children = getNestedChildren(ruleData);
|
|
715
|
+
if (children) {
|
|
716
|
+
removeFontFaceRules(children);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
removedRules.forEach(cssRule => cssRules.remove(cssRule));
|
|
721
|
+
}
|
|
722
|
+
|
|
684
723
|
function getUrlFunctions(declarationList) {
|
|
685
724
|
return cssTree.findAll(declarationList, node => node.type == "Url");
|
|
686
725
|
}
|
|
@@ -731,8 +731,8 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
731
731
|
fontTests.set(source.src, source.valid);
|
|
732
732
|
}
|
|
733
733
|
}));
|
|
734
|
-
const findSourceByFormat = (fontFormat, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
|
|
735
|
-
const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
|
|
734
|
+
const findSourceByFormat = (fontFormat, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && !source.tech && source.format == fontFormat && (!testValidity || source.valid));
|
|
735
|
+
const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE) || (source.tech && source.valid));
|
|
736
736
|
stats.fonts.processed += fontInfo.length;
|
|
737
737
|
stats.fonts.discarded += fontInfo.length;
|
|
738
738
|
const woffFontFound =
|
|
@@ -645,9 +645,9 @@ function getProcessorHelperClass(utilInstance) {
|
|
|
645
645
|
fontTests.set(source.src, source.valid);
|
|
646
646
|
}
|
|
647
647
|
}));
|
|
648
|
-
const findSourceByFormat = (fontFormat, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
|
|
649
|
-
const findSourceByContentType = (contentType, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && source.contentType == contentType && (!testValidity || source.valid));
|
|
650
|
-
const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
|
|
648
|
+
const findSourceByFormat = (fontFormat, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && !source.tech && source.format == fontFormat && (!testValidity || source.valid));
|
|
649
|
+
const findSourceByContentType = (contentType, testValidity) => util.findLast(fontInfo, source => !source.src.match(EMPTY_URL_SOURCE) && !source.tech && source.contentType == contentType && (!testValidity || source.valid));
|
|
650
|
+
const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE) || (source.tech && source.valid));
|
|
651
651
|
stats.fonts.processed += fontInfo.length;
|
|
652
652
|
stats.fonts.discarded += fontInfo.length;
|
|
653
653
|
const woffFontFound =
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import * as cssTree from "./../vendor/css-tree.js";
|
|
25
25
|
import * as fontPropertyParser from "./../vendor/css-font-property-parser.js";
|
|
26
|
+
import { getNestedChildren } from "./css-nested-rules.js";
|
|
27
|
+
import { decodeName } from "./css-identifier.js";
|
|
26
28
|
import {
|
|
27
29
|
normalizeFontFamily,
|
|
28
30
|
flatten,
|
|
@@ -41,6 +43,9 @@ const helper = {
|
|
|
41
43
|
removeQuotes
|
|
42
44
|
};
|
|
43
45
|
|
|
46
|
+
const FONT_FACE_PARENT_AT_RULE_NAMES = ["media", "supports", "layer", "container"];
|
|
47
|
+
const FONT_WIDTH_DESCRIPTOR_NAMES = ["font-stretch", "font-width"];
|
|
48
|
+
const FONT_STRETCH_READINGS = ["fontStretch", "legacyFontStretch"];
|
|
44
49
|
const REGEXP_COMMA = /\s*,\s*/;
|
|
45
50
|
const REGEXP_SPACES = /\s+/g;
|
|
46
51
|
const REGEXP_ANGLE = /^([-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(deg|grad|rad|turn)?$/;
|
|
@@ -173,40 +178,49 @@ function keepDeclaredFontIfRendered(familyName, fontsInfo, filteredUsedFonts, op
|
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
180
|
|
|
176
|
-
function getFontsInfo(cssRules, fontsInfo, options) {
|
|
181
|
+
function getFontsInfo(cssRules, fontsInfo, options, fontFaceAllowed = true) {
|
|
177
182
|
cssRules.forEach(ruleData => {
|
|
178
|
-
if (ruleData.type == "Atrule" &&
|
|
179
|
-
|
|
180
|
-
} else if (ruleData.type == "Rule") {
|
|
181
|
-
const fontFamilyNames = getFontFamilyNames(ruleData.block, options);
|
|
182
|
-
if (fontFamilyNames.length) {
|
|
183
|
-
fontsInfo.used.push(fontFamilyNames);
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
186
|
-
if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
183
|
+
if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
184
|
+
if (fontFaceAllowed) {
|
|
187
185
|
const fontFamily = helper.normalizeFontFamily(getDeclarationValue(ruleData.block.children, "font-family"));
|
|
188
186
|
if (fontFamily) {
|
|
189
187
|
const fontWeight = getDeclarationValue(ruleData.block.children, "font-weight") || "400";
|
|
190
188
|
const fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
|
|
191
189
|
const fontVariant = getDeclarationValue(ruleData.block.children, "font-variant") || "normal";
|
|
192
|
-
const fontStretch = getDeclarationValue(ruleData.block.children,
|
|
190
|
+
const fontStretch = getDeclarationValue(ruleData.block.children, FONT_WIDTH_DESCRIPTOR_NAMES) || "normal";
|
|
191
|
+
const legacyFontStretch = getDeclarationValue(ruleData.block.children, "font-stretch") || "normal";
|
|
193
192
|
const unicodeRange = getDeclarationValue(ruleData.block.children, "unicode-range");
|
|
194
193
|
fontWeight.split(",").forEach(weightValue =>
|
|
195
|
-
fontsInfo.declared.push({ fontFamily, fontWeight: helper.getFontWeight(helper.removeQuotes(weightValue)), fontStyle, fontVariant, fontStretch, unicodeRange, ruleData }));
|
|
194
|
+
fontsInfo.declared.push({ fontFamily, fontWeight: helper.getFontWeight(helper.removeQuotes(weightValue)), fontStyle, fontVariant, fontStretch, legacyFontStretch, unicodeRange, ruleData }));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
const children = getNestedChildren(ruleData);
|
|
199
|
+
if (children) {
|
|
200
|
+
const fontFamilyNames = getFontFamilyNames(ruleData.block, options);
|
|
201
|
+
if (fontFamilyNames.length) {
|
|
202
|
+
fontsInfo.used.push(fontFamilyNames);
|
|
196
203
|
}
|
|
204
|
+
getFontsInfo(children, fontsInfo, options, fontFaceAllowed && testFontFaceParent(ruleData));
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
});
|
|
200
208
|
}
|
|
201
209
|
|
|
210
|
+
function testFontFaceParent(ruleData) {
|
|
211
|
+
return ruleData.type == "Atrule" && typeof ruleData.name == "string" && FONT_FACE_PARENT_AT_RULE_NAMES.includes(decodeName(ruleData.name));
|
|
212
|
+
}
|
|
213
|
+
|
|
202
214
|
function getCustomPropertiesInfo(cssRules, customProperties) {
|
|
203
215
|
cssRules.forEach(ruleData => {
|
|
204
216
|
if (ruleData.type == "Atrule" && ruleData.name == "import" && ruleData.prelude && ruleData.prelude.children && ruleData.prelude.children.head.data.importedChildren) {
|
|
205
217
|
getCustomPropertiesInfo(ruleData.prelude.children.head.data.importedChildren, customProperties);
|
|
206
|
-
} else
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
} else {
|
|
219
|
+
const children = getNestedChildren(ruleData);
|
|
220
|
+
if (children) {
|
|
221
|
+
getCustomProperties(children, customProperties);
|
|
222
|
+
getCustomPropertiesInfo(children, customProperties);
|
|
223
|
+
}
|
|
210
224
|
}
|
|
211
225
|
});
|
|
212
226
|
}
|
|
@@ -235,24 +249,20 @@ function getCustomProperties(declarations, customProperties) {
|
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
function getCustomPropertyValues(name, options) {
|
|
238
|
-
|
|
252
|
+
const values = new Set();
|
|
239
253
|
if (globalThis.getComputedStyle && options.doc) {
|
|
240
254
|
const computedValue = globalThis.getComputedStyle(options.doc.body).getPropertyValue(name);
|
|
241
255
|
if (computedValue && computedValue.trim()) {
|
|
242
|
-
values
|
|
256
|
+
values.add(computedValue.trim());
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (declaredValues && declaredValues.size) {
|
|
252
|
-
values = Array.from(declaredValues);
|
|
253
|
-
}
|
|
259
|
+
const declaredValues = options.customProperties && options.customProperties.get(name);
|
|
260
|
+
if (declaredValues) {
|
|
261
|
+
declaredValues.forEach(value => values.add(value));
|
|
262
|
+
}
|
|
263
|
+
if (values.size) {
|
|
264
|
+
return Array.from(values);
|
|
254
265
|
}
|
|
255
|
-
return values;
|
|
256
266
|
}
|
|
257
267
|
|
|
258
268
|
function resolveFamilyName(familyName, options, resolvedProperties = new Set()) {
|
|
@@ -325,7 +335,7 @@ function filterUnusedFonts(cssRules, declaredFonts, unusedFonts, selectedFonts,
|
|
|
325
335
|
const ruleData = cssRule.data;
|
|
326
336
|
if (ruleData.type == "Atrule" && ruleData.name == "import" && ruleData.prelude && ruleData.prelude.children && ruleData.prelude.children.head.data.importedChildren) {
|
|
327
337
|
filterUnusedFonts(ruleData.prelude.children.head.data.importedChildren, declaredFonts, unusedFonts, selectedFonts, docChars, usedFontsCharacters);
|
|
328
|
-
} else if (
|
|
338
|
+
} else if (testFontFaceParent(ruleData) && ruleData.block && ruleData.block.children) {
|
|
329
339
|
filterUnusedFonts(ruleData.block.children, declaredFonts, unusedFonts, selectedFonts, docChars, usedFontsCharacters);
|
|
330
340
|
} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
331
341
|
const fontFamily = helper.normalizeFontFamily(getDeclarationValue(ruleData.block.children, "font-family"));
|
|
@@ -427,26 +437,27 @@ function testUsedFont(ruleData, familyName, selectedFonts) {
|
|
|
427
437
|
function getSelectedFonts(declaredFonts, filteredUsedFonts) {
|
|
428
438
|
const selectedFonts = new Map();
|
|
429
439
|
filteredUsedFonts.forEach((usedFonts, familyName) => {
|
|
430
|
-
const
|
|
431
|
-
|
|
440
|
+
const familyFonts = declaredFonts.filter(fontInfo => fontInfo.fontFamily == familyName);
|
|
441
|
+
const readings = familyFonts.some(fontInfo => fontInfo.legacyFontStretch != fontInfo.fontStretch) ? FONT_STRETCH_READINGS : FONT_STRETCH_READINGS.slice(0, 1);
|
|
442
|
+
const fontLists = readings.map(reading => familyFonts
|
|
432
443
|
.map(fontInfo => ({
|
|
433
444
|
ruleData: fontInfo.ruleData,
|
|
434
445
|
weight: parseFontWeight(fontInfo.fontWeight),
|
|
435
446
|
style: parseFontStyle(fontInfo.fontStyle),
|
|
436
|
-
stretch: parseFontStretch(fontInfo
|
|
447
|
+
stretch: parseFontStretch(fontInfo[reading])
|
|
437
448
|
}))
|
|
438
|
-
.filter(fontInfo => fontInfo.weight && fontInfo.style && fontInfo.stretch);
|
|
439
|
-
const selection = { candidates: new Set(
|
|
449
|
+
.filter(fontInfo => fontInfo.weight && fontInfo.style && fontInfo.stretch));
|
|
450
|
+
const selection = { candidates: new Set(helper.flatten(fontLists).map(fontInfo => fontInfo.ruleData)), rules: new Set(), rulesByStyle: new Map() };
|
|
440
451
|
usedFonts.forEach(([, fontWeight, fontStyle, , fontStretch]) => {
|
|
441
452
|
let styleRules = selection.rulesByStyle.get(fontStyle);
|
|
442
453
|
if (!styleRules) {
|
|
443
454
|
styleRules = new Set();
|
|
444
455
|
selection.rulesByStyle.set(fontStyle, styleRules);
|
|
445
456
|
}
|
|
446
|
-
FONT_MATCHING_ALGORITHMS.forEach(algorithm => selectFonts(fonts, fontWeight, fontStyle, fontStretch, algorithm).forEach(fontInfo => {
|
|
457
|
+
fontLists.forEach(fonts => FONT_MATCHING_ALGORITHMS.forEach(algorithm => selectFonts(fonts, fontWeight, fontStyle, fontStretch, algorithm).forEach(fontInfo => {
|
|
447
458
|
styleRules.add(fontInfo.ruleData);
|
|
448
459
|
selection.rules.add(fontInfo.ruleData);
|
|
449
|
-
}));
|
|
460
|
+
})));
|
|
450
461
|
});
|
|
451
462
|
selectedFonts.set(familyName, selection);
|
|
452
463
|
});
|
|
@@ -658,13 +669,21 @@ function parseRange(value, parseValue) {
|
|
|
658
669
|
}
|
|
659
670
|
|
|
660
671
|
function getDeclarationValue(declarations, propertyName) {
|
|
672
|
+
const value = getDeclarationText(declarations, propertyName);
|
|
673
|
+
if (value !== undefined) {
|
|
674
|
+
return value.toLowerCase();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function getDeclarationText(declarations, propertyName) {
|
|
679
|
+
const propertyNames = Array.isArray(propertyName) ? propertyName : [propertyName];
|
|
661
680
|
let property;
|
|
662
681
|
if (declarations) {
|
|
663
|
-
property = declarations.filter(declaration => declaration.property
|
|
682
|
+
property = declarations.filter(declaration => propertyNames.includes(declaration.property)).tail;
|
|
664
683
|
}
|
|
665
684
|
if (property) {
|
|
666
685
|
try {
|
|
667
|
-
return helper.removeQuotes(cssTree.generate(property.data.value))
|
|
686
|
+
return helper.removeQuotes(cssTree.generate(property.data.value));
|
|
668
687
|
// eslint-disable-next-line no-unused-vars
|
|
669
688
|
} catch (error) {
|
|
670
689
|
// ignored
|
|
@@ -769,12 +788,10 @@ function parseFamilyNames(fontFamilyNameTokenData, fontFamilyNames) {
|
|
|
769
788
|
|
|
770
789
|
function getRulesTextContent(doc, cssRules, workStylesheet, content) {
|
|
771
790
|
cssRules.forEach(ruleData => {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
content = getDeclarationsTextContent(ruleData.block.children, workStylesheet, content);
|
|
777
|
-
}
|
|
791
|
+
const children = getNestedChildren(ruleData);
|
|
792
|
+
if (children) {
|
|
793
|
+
content = getDeclarationsTextContent(children, workStylesheet, content);
|
|
794
|
+
content = getRulesTextContent(doc, children, workStylesheet, content);
|
|
778
795
|
}
|
|
779
796
|
});
|
|
780
797
|
return content;
|
|
@@ -793,7 +810,7 @@ function getDeclarationsTextContent(declarations, workStylesheet, content) {
|
|
|
793
810
|
}
|
|
794
811
|
|
|
795
812
|
function getDeclarationUnescapedValue(declarations, property, workStylesheet) {
|
|
796
|
-
const rawValue =
|
|
813
|
+
const rawValue = getDeclarationText(declarations, property) || "";
|
|
797
814
|
if (rawValue) {
|
|
798
815
|
workStylesheet.textContent = "tmp { content:\"" + rawValue + "\"}";
|
|
799
816
|
if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
|
|
@@ -837,14 +854,15 @@ function getLaterUnicodeRanges(declaredFonts, ruleData) {
|
|
|
837
854
|
if (index == -1) {
|
|
838
855
|
return [];
|
|
839
856
|
}
|
|
840
|
-
const { fontFamily, fontWeight, fontStyle, fontStretch } = declaredFonts[index];
|
|
857
|
+
const { fontFamily, fontWeight, fontStyle, fontStretch, legacyFontStretch } = declaredFonts[index];
|
|
841
858
|
return declaredFonts
|
|
842
859
|
.slice(index + 1)
|
|
843
860
|
.filter(fontInfo => fontInfo.ruleData != ruleData &&
|
|
844
861
|
fontInfo.fontFamily == fontFamily &&
|
|
845
862
|
fontInfo.fontWeight == fontWeight &&
|
|
846
863
|
fontInfo.fontStyle == fontStyle &&
|
|
847
|
-
fontInfo.fontStretch == fontStretch
|
|
864
|
+
fontInfo.fontStretch == fontStretch &&
|
|
865
|
+
fontInfo.legacyFontStretch == legacyFontStretch)
|
|
848
866
|
.map(fontInfo => fontInfo.unicodeRange);
|
|
849
867
|
}
|
|
850
868
|
|
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
import * as mediaQueryParser from "../vendor/css-media-query-parser.js";
|
|
2
2
|
|
|
3
3
|
function parseMediaListSafe(mediaText) {
|
|
4
|
-
|
|
4
|
+
return mediaQueryParser.parseMediaList(mediaText);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
function containsNotKeyword(node) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
if (!node || !node.nodes) return false;
|
|
9
|
+
for (const token of node.nodes) {
|
|
10
|
+
if (token && token.type === "keyword" && token.value && token.value.toLowerCase() === "not") {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function isMediaTypeNegated(parentNode, index) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
for (let j = index - 1; j >= 0; j--) {
|
|
19
|
+
const prev = parentNode.nodes[j];
|
|
20
|
+
if (!prev) continue;
|
|
21
|
+
if (prev.type === "operator" && prev.value === ",") {
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (prev.type === "keyword" && prev.value && prev.value.toLowerCase() === "not") {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function extractMediaTypes(parentNode, mediaTypes = []) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
for (let index = 0; index < parentNode.nodes.length; index++) {
|
|
33
|
+
const node = parentNode.nodes[index];
|
|
34
|
+
if (node.type == "media-query") {
|
|
35
|
+
extractMediaTypes(node, mediaTypes);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (node.type == "media-type") {
|
|
39
|
+
const negated = isMediaTypeNegated(parentNode, index);
|
|
40
|
+
mediaTypes.push({ not: negated, value: node.value });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return mediaTypes;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function isFeaturefulOrCompound(node) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
if (!node || !node.nodes) return false;
|
|
48
|
+
for (const token of node.nodes) {
|
|
49
|
+
if (!token) continue;
|
|
50
|
+
if (token.type === "media-feature-expression") return true;
|
|
51
|
+
if (token.type === "keyword" && token.value && token.value.toLowerCase() === "and") return true;
|
|
52
|
+
}
|
|
53
|
+
if (containsNotKeyword(node)) return true;
|
|
54
|
+
return false;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
parseMediaListSafe,
|
|
59
|
+
containsNotKeyword,
|
|
60
|
+
isMediaTypeNegated,
|
|
61
|
+
extractMediaTypes,
|
|
62
|
+
isFeaturefulOrCompound
|
|
63
63
|
};
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import * as cssTree from "./../vendor/css-tree.js";
|
|
25
25
|
import { flatten } from "./../core/helper.js";
|
|
26
|
+
import { getNestedChildren } from "./css-nested-rules.js";
|
|
26
27
|
import {
|
|
27
28
|
parseMediaListSafe,
|
|
28
29
|
containsNotKeyword as utilContainsNotKeyword,
|
|
@@ -71,6 +72,8 @@ function processRules(cssRules, stats, keepPrintStyleSheets, removedRules = [])
|
|
|
71
72
|
removedRules.push({ cssRules, cssRule });
|
|
72
73
|
stats.discarded++;
|
|
73
74
|
}
|
|
75
|
+
} else if (getNestedChildren(ruleData)) {
|
|
76
|
+
processRules(ruleData.block.children, stats, keepPrintStyleSheets, removedRules);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
return removedRules;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2022 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import { decodeName } from "./css-identifier.js";
|
|
26
|
+
|
|
27
|
+
const RULE_TYPE = "Rule";
|
|
28
|
+
const AT_RULE_TYPE = "Atrule";
|
|
29
|
+
const LEAF_AT_RULE_NAMES = ["font-face", "keyframes"];
|
|
30
|
+
const REGEXP_VENDOR_PREFIX = /^-[^-]+-/;
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
getNestedChildren
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function getNestedChildren(ruleData) {
|
|
37
|
+
if (ruleData.block && ruleData.block.children) {
|
|
38
|
+
if (ruleData.type == RULE_TYPE) {
|
|
39
|
+
return ruleData.block.children;
|
|
40
|
+
} else if (ruleData.type == AT_RULE_TYPE && typeof ruleData.name == "string" && !LEAF_AT_RULE_NAMES.includes(decodeName(ruleData.name).replace(REGEXP_VENDOR_PREFIX, ""))) {
|
|
41
|
+
return ruleData.block.children;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -22,14 +22,13 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import * as cssTree from "./../vendor/css-tree.js";
|
|
25
|
-
import { computeMaxSpecificity } from "./css-specificity.js";
|
|
25
|
+
import { computeMaxSpecificity, PSEUDO_ELEMENT_SYNONYMS } from "./css-specificity.js";
|
|
26
26
|
import { parsePrelude } from "./css-scope-prelude-parser.js";
|
|
27
27
|
import { sanitizeSelector, matchUnqueryableAttributeSelector, matchUnqueryablePseudoClass } from "./css-selector-sanitizer.js";
|
|
28
28
|
import { ESCAPE_CHARACTER, decodeIdentifier, decodeName } from "./css-identifier.js";
|
|
29
29
|
|
|
30
30
|
const DEBUG = false;
|
|
31
31
|
|
|
32
|
-
const PSEUDO_ELEMENT_SYNONYMS = new Set(["after", "before", "first-letter", "first-line"]);
|
|
33
32
|
const FUNCTIONAL_PSEUDO_CLASS_NAMES = new Set(["not", "is", "where", "has", "nth-child", "nth-last-child"]);
|
|
34
33
|
const MEDIA_AT_RULE_NAME = "media";
|
|
35
34
|
const SUPPORTS_AT_RULE_NAME = "supports";
|