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 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.remove());
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.remove());
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.remove();
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.remove());
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\((.*?)\)\s*,?$/;
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 == "Rule") {
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
- let fontFormat;
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" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container") && ruleData.block && ruleData.block.children) {
179
- getFontsInfo(ruleData.block.children, fontsInfo, options);
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, "font-stretch") || "normal";
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 if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container") && ruleData.block && ruleData.block.children) {
207
- getCustomPropertiesInfo(ruleData.block.children, customProperties);
208
- } else if (ruleData.type == "Rule" && ruleData.block && ruleData.block.children) {
209
- getCustomProperties(ruleData.block.children, customProperties);
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
- let values;
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 = [computedValue];
256
+ values.add(computedValue.trim());
243
257
  }
244
258
  }
245
- if (!values) {
246
- // the property is not inherited by the body: it is declared on a descendant, or in a
247
- // media query that does not apply, so the value seen by the element using it cannot be
248
- // determined here. Every value declared for it in the document is taken as a candidate,
249
- // which still discards the fonts named by none of them
250
- const declaredValues = options.customProperties && options.customProperties.get(name);
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 (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container") && ruleData.block && ruleData.block.children) {
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 fonts = declaredFonts
431
- .filter(fontInfo => fontInfo.fontFamily == familyName)
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.fontStretch)
447
+ stretch: parseFontStretch(fontInfo[reading])
437
448
  }))
438
- .filter(fontInfo => fontInfo.weight && fontInfo.style && fontInfo.stretch);
439
- const selection = { candidates: new Set(fonts.map(fontInfo => fontInfo.ruleData)), rules: new Set(), rulesByStyle: new Map() };
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 == propertyName).tail;
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)).toLowerCase();
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
- if (ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
773
- if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container")) {
774
- content = getRulesTextContent(doc, ruleData.block.children, workStylesheet, content);
775
- } else if (ruleData.type == "Rule") {
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 = getDeclarationValue(declarations, property) || "";
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
- return mediaQueryParser.parseMediaList(mediaText);
4
+ return mediaQueryParser.parseMediaList(mediaText);
5
5
  }
6
6
 
7
7
  function containsNotKeyword(node) {
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;
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
- 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;
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
- 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;
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
- 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;
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
- parseMediaListSafe,
59
- containsNotKeyword,
60
- isMediaTypeNegated,
61
- extractMediaTypes,
62
- isFeaturefulOrCompound
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";