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
|
@@ -48,6 +48,8 @@ const REGEXP_CUSTOM_PROPERTY = /var\(\s*(--[^\s,)]+)\s*(?:,[^)]*)?\)/g;
|
|
|
48
48
|
const REGEXP_CUSTOM_PROPERTY_FAMILY = /^var\(\s*(--[^\s,)]+)\s*(?:,(.*))?\)$/;
|
|
49
49
|
const REGEXP_CUSTOM_PROPERTY_NAME = /^--/;
|
|
50
50
|
const VALID_FONT_STYLES = [/^normal$/, /^italic$/, /^oblique$/, /^oblique\s+/];
|
|
51
|
+
const NON_GLYPH_CHAR_CODES = [9, 10, 12, 13];
|
|
52
|
+
const MAX_NON_GLYPH_CHAR_CODE = 13;
|
|
51
53
|
// a family name kept when the "font" shorthand cannot be read: it resolves to nothing, so it
|
|
52
54
|
// survives the substitution below and marks the fonts as undetermined instead of unused
|
|
53
55
|
const UNRESOLVED_CUSTOM_PROPERTY_FAMILY = "var(--)";
|
|
@@ -125,11 +127,12 @@ function process(doc, stylesheets, styles, options) {
|
|
|
125
127
|
unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
|
|
126
128
|
}
|
|
127
129
|
const docChars = Array.from(new Set(docContent)).map(char => char.charCodeAt(0)).sort((value1, value2) => value1 - value2);
|
|
130
|
+
const usedFontsCharacters = getUsedFontsCharacters(options);
|
|
128
131
|
stylesheets.forEach(stylesheetInfo => {
|
|
129
132
|
if (stylesheetInfo.stylesheet) {
|
|
130
133
|
const cssRules = stylesheetInfo.stylesheet.children;
|
|
131
134
|
if (cssRules) {
|
|
132
|
-
filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docChars);
|
|
135
|
+
filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docChars, usedFontsCharacters);
|
|
133
136
|
stats.rules.discarded -= cssRules.size;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
@@ -164,8 +167,9 @@ function getFontsInfo(cssRules, fontsInfo, options) {
|
|
|
164
167
|
const fontWeight = getDeclarationValue(ruleData.block.children, "font-weight") || "400";
|
|
165
168
|
const fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
|
|
166
169
|
const fontVariant = getDeclarationValue(ruleData.block.children, "font-variant") || "normal";
|
|
170
|
+
const unicodeRange = getDeclarationValue(ruleData.block.children, "unicode-range");
|
|
167
171
|
fontWeight.split(",").forEach(weightValue =>
|
|
168
|
-
fontsInfo.declared.push({ fontFamily, fontWeight: helper.getFontWeight(helper.removeQuotes(weightValue)), fontStyle, fontVariant }));
|
|
172
|
+
fontsInfo.declared.push({ fontFamily, fontWeight: helper.getFontWeight(helper.removeQuotes(weightValue)), fontStyle, fontVariant, unicodeRange, ruleData }));
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
}
|
|
@@ -292,19 +296,24 @@ function splitValues(value) {
|
|
|
292
296
|
return values;
|
|
293
297
|
}
|
|
294
298
|
|
|
295
|
-
function filterUnusedFonts(cssRules, declaredFonts, unusedFonts, filteredUsedFonts, docChars) {
|
|
299
|
+
function filterUnusedFonts(cssRules, declaredFonts, unusedFonts, filteredUsedFonts, docChars, usedFontsCharacters) {
|
|
296
300
|
const removedRules = [];
|
|
297
301
|
for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
|
|
298
302
|
const ruleData = cssRule.data;
|
|
299
303
|
if (ruleData.type == "Atrule" && ruleData.name == "import" && ruleData.prelude && ruleData.prelude.children && ruleData.prelude.children.head.data.importedChildren) {
|
|
300
|
-
filterUnusedFonts(ruleData.prelude.children.head.data.importedChildren, declaredFonts, unusedFonts, filteredUsedFonts, docChars);
|
|
304
|
+
filterUnusedFonts(ruleData.prelude.children.head.data.importedChildren, declaredFonts, unusedFonts, filteredUsedFonts, docChars, usedFontsCharacters);
|
|
301
305
|
} else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports" || ruleData.name == "layer" || ruleData.name == "container") && ruleData.block && ruleData.block.children) {
|
|
302
|
-
filterUnusedFonts(ruleData.block.children, declaredFonts, unusedFonts, filteredUsedFonts, docChars);
|
|
306
|
+
filterUnusedFonts(ruleData.block.children, declaredFonts, unusedFonts, filteredUsedFonts, docChars, usedFontsCharacters);
|
|
303
307
|
} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
|
|
304
308
|
const fontFamily = helper.normalizeFontFamily(getDeclarationValue(ruleData.block.children, "font-family"));
|
|
305
309
|
if (fontFamily) {
|
|
306
310
|
const unicodeRange = getDeclarationValue(ruleData.block.children, "unicode-range");
|
|
307
|
-
|
|
311
|
+
const laterUnicodeRanges = getLaterUnicodeRanges(declaredFonts, ruleData);
|
|
312
|
+
if (unusedFonts.find(fontInfo => fontInfo.fontFamily == fontFamily) ||
|
|
313
|
+
!testUnicodeRange(docChars, unicodeRange) ||
|
|
314
|
+
!testReachableUnicodeRange(docChars, unicodeRange, laterUnicodeRanges) ||
|
|
315
|
+
!testDrawnUnicodeRange(ruleData, fontFamily, unicodeRange, usedFontsCharacters) ||
|
|
316
|
+
!testUsedFont(ruleData, fontFamily, declaredFonts, filteredUsedFonts)) {
|
|
308
317
|
removedRules.push(cssRule);
|
|
309
318
|
}
|
|
310
319
|
}
|
|
@@ -322,6 +331,71 @@ function filterUnusedFonts(cssRules, declaredFonts, unusedFonts, filteredUsedFon
|
|
|
322
331
|
removedRules.forEach(cssRule => cssRules.remove(cssRule));
|
|
323
332
|
}
|
|
324
333
|
|
|
334
|
+
function getUsedFontsCharacters(options) {
|
|
335
|
+
const usedFontsCharacters = new Map();
|
|
336
|
+
if (options.usedFontsCharacters && options.usedFontsCharacters.length) {
|
|
337
|
+
options.usedFontsCharacters.forEach(([fontFamily, fontStyle, ranges, unknown]) => {
|
|
338
|
+
let buckets = usedFontsCharacters.get(fontFamily);
|
|
339
|
+
if (!buckets) {
|
|
340
|
+
buckets = [];
|
|
341
|
+
usedFontsCharacters.set(fontFamily, buckets);
|
|
342
|
+
}
|
|
343
|
+
buckets.push({ fontStyle, ranges: ranges || [], unknown: Boolean(unknown) });
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return usedFontsCharacters;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function testDrawnUnicodeRange(ruleData, familyName, unicodeRange, usedFontsCharacters) {
|
|
350
|
+
if (!unicodeRange || !usedFontsCharacters || !usedFontsCharacters.size) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
const buckets = usedFontsCharacters.get(familyName);
|
|
354
|
+
if (!buckets || !buckets.length) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
const fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
|
|
358
|
+
if (!VALID_FONT_STYLES.find(rule => fontStyle.trim().match(rule))) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
const matchedBuckets = buckets.filter(bucket => testFontStyle(bucket.fontStyle, fontStyle));
|
|
362
|
+
if (!matchedBuckets.length || matchedBuckets.find(bucket => bucket.unknown)) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
if (!matchedBuckets.find(bucket => bucket.ranges.length)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
const ranges = parseUnicodeRanges(unicodeRange);
|
|
369
|
+
if (!ranges.length) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return Boolean(matchedBuckets.find(bucket =>
|
|
373
|
+
bucket.ranges.find(drawnRange =>
|
|
374
|
+
ranges.find(range => testDrawnGlyph(drawnRange, range)))));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// A tab or a newline is recorded as drawn like any other character, but neither can ever select a
|
|
378
|
+
// glyph, so a face whose unicode-range meets the page in nothing else is dead however the page is
|
|
379
|
+
// laid out. Google Fonts symbol subsets are exactly that shape: measured on a capture of
|
|
380
|
+
// techcrunch.com, two Roboto faces worth 23,505 bytes matched the document in U+0009 and U+000A and
|
|
381
|
+
// nothing more.
|
|
382
|
+
function testDrawnGlyph(drawnRange, range) {
|
|
383
|
+
const fromCharCode = Math.max(drawnRange[0], range[0]);
|
|
384
|
+
const toCharCode = Math.min(drawnRange[1], range[1]);
|
|
385
|
+
if (fromCharCode > toCharCode) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
if (fromCharCode > MAX_NON_GLYPH_CHAR_CODE || toCharCode > MAX_NON_GLYPH_CHAR_CODE) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
for (let charCode = fromCharCode; charCode <= toCharCode; charCode++) {
|
|
392
|
+
if (!NON_GLYPH_CHAR_CODES.includes(charCode)) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
325
399
|
function testUsedFont(ruleData, familyName, declaredFonts, filteredUsedFonts) {
|
|
326
400
|
let test;
|
|
327
401
|
const optionalUsedFonts = filteredUsedFonts && filteredUsedFonts.get(familyName);
|
|
@@ -389,7 +463,7 @@ function testFontweight(fontWeight, usedFontWeights) {
|
|
|
389
463
|
for (const fontWeightValue of fontWeight.split(",")) {
|
|
390
464
|
let { min: fontWeightMin, max: fontWeightMax } = parseFontWeight(fontWeightValue);
|
|
391
465
|
if (!fontWeightMax) {
|
|
392
|
-
fontWeightMax =
|
|
466
|
+
fontWeightMax = fontWeightMin;
|
|
393
467
|
}
|
|
394
468
|
test = test || usedFontWeights.find(usedFontWeight => {
|
|
395
469
|
let { min: usedFontWeightMin, max: usedFontWeightMax } = parseFontWeight(usedFontWeight);
|
|
@@ -631,6 +705,64 @@ function testUnicodeRange(docCharCodes, unicodeRange) {
|
|
|
631
705
|
return true;
|
|
632
706
|
}
|
|
633
707
|
|
|
708
|
+
function getLaterUnicodeRanges(declaredFonts, ruleData) {
|
|
709
|
+
const index = declaredFonts.findIndex(fontInfo => fontInfo.ruleData == ruleData);
|
|
710
|
+
if (index == -1) {
|
|
711
|
+
return [];
|
|
712
|
+
}
|
|
713
|
+
const { fontFamily, fontWeight, fontStyle } = declaredFonts[index];
|
|
714
|
+
return declaredFonts
|
|
715
|
+
.slice(index + 1)
|
|
716
|
+
.filter(fontInfo => fontInfo.ruleData != ruleData &&
|
|
717
|
+
fontInfo.fontFamily == fontFamily &&
|
|
718
|
+
fontInfo.fontWeight == fontWeight &&
|
|
719
|
+
fontInfo.fontStyle == fontStyle)
|
|
720
|
+
.map(fontInfo => fontInfo.unicodeRange);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function testReachableUnicodeRange(docCharCodes, unicodeRange, laterUnicodeRanges) {
|
|
724
|
+
if (!laterUnicodeRanges.length) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
const laterRanges = helper.flatten(laterUnicodeRanges.map(laterUnicodeRange => parseUnicodeRanges(laterUnicodeRange)));
|
|
728
|
+
if (!laterRanges.length) {
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
const ranges = parseUnicodeRanges(unicodeRange);
|
|
732
|
+
return Boolean(docCharCodes.find(charCode =>
|
|
733
|
+
(!ranges.length || ranges.find(range => testCharCodeInRange(charCode, range))) &&
|
|
734
|
+
!laterRanges.find(range => testCharCodeInRange(charCode, range))));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function testCharCodeInRange(charCode, range) {
|
|
738
|
+
return charCode >= range[0] && charCode <= range[1];
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function parseUnicodeRanges(unicodeRange) {
|
|
742
|
+
const ranges = [];
|
|
743
|
+
if (unicodeRange) {
|
|
744
|
+
unicodeRange.split(REGEXP_COMMA).forEach(rangeValue => {
|
|
745
|
+
const range = rangeValue.split(REGEXP_DASH);
|
|
746
|
+
let min, max;
|
|
747
|
+
if (range.length == 2) {
|
|
748
|
+
min = transformRange(range[0]);
|
|
749
|
+
max = transformRange(range[1]);
|
|
750
|
+
} else if (range.length == 1 && range[0]) {
|
|
751
|
+
if (range[0].includes("?")) {
|
|
752
|
+
min = transformRange(range[0].replace(REGEXP_QUESTION_MARK, "0"));
|
|
753
|
+
max = transformRange(range[0].replace(REGEXP_QUESTION_MARK, "F"));
|
|
754
|
+
} else {
|
|
755
|
+
min = max = transformRange(range[0]);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (Number.isInteger(min) && Number.isInteger(max)) {
|
|
759
|
+
ranges.push([min, max]);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
return ranges;
|
|
764
|
+
}
|
|
765
|
+
|
|
634
766
|
function transformRange(range) {
|
|
635
767
|
range = range.replace(REGEXP_STARTS_U_PLUS, "");
|
|
636
768
|
return parseInt(range, 16);
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
import * as cssTree from "./../vendor/css-tree.js";
|
|
25
25
|
import { computeMaxSpecificity } from "./css-specificity.js";
|
|
26
26
|
import { parsePrelude } from "./css-scope-prelude-parser.js";
|
|
27
|
-
import { sanitizeSelector, matchUnqueryablePseudoClass } from "./css-selector-sanitizer.js";
|
|
27
|
+
import { sanitizeSelector, matchUnqueryableAttributeSelector, matchUnqueryablePseudoClass } from "./css-selector-sanitizer.js";
|
|
28
28
|
|
|
29
29
|
const DEBUG = false;
|
|
30
30
|
|
|
31
31
|
const PSEUDO_ELEMENT_SYNONYMS = new Set(["after", "before", "first-letter", "first-line"]);
|
|
32
|
+
const FUNCTIONAL_PSEUDO_CLASS_NAMES = new Set(["not", "is", "where", "has"]);
|
|
32
33
|
const MEDIA_AT_RULE_NAME = "media";
|
|
33
34
|
const SUPPORTS_AT_RULE_NAME = "supports";
|
|
34
35
|
const CONDITIONAL_AT_RULE_NAMES = new Set([MEDIA_AT_RULE_NAME, SUPPORTS_AT_RULE_NAME, "container"]);
|
|
@@ -36,6 +37,7 @@ const RULE_TYPE = "Rule";
|
|
|
36
37
|
const AT_RULE_TYPE = "Atrule";
|
|
37
38
|
const NESTING_SELECTOR_TYPE = "NestingSelector";
|
|
38
39
|
const PSEUDO_CLASS_SELECTOR_TYPE = "PseudoClassSelector";
|
|
40
|
+
const ATTRIBUTE_SELECTOR_TYPE = "AttributeSelector";
|
|
39
41
|
const DECLARATION_TYPE = "Declaration";
|
|
40
42
|
const RAW_TYPE = "Raw";
|
|
41
43
|
const VALUE_TYPE = "Value";
|
|
@@ -390,17 +392,20 @@ function processSelectors(ruleData, processingContext, docContext) {
|
|
|
390
392
|
for (let selector = ruleData.prelude.children.head, selectorIndex = 0; selector; selector = selector.next, selectorIndex++) {
|
|
391
393
|
const {
|
|
392
394
|
startsWithCombinator,
|
|
393
|
-
hasUnqueryableSelector
|
|
395
|
+
hasUnqueryableSelector,
|
|
396
|
+
hasNestedUnqueryablePseudoClass
|
|
394
397
|
} = analyzeSelector(selector.data);
|
|
395
398
|
if (hasUnqueryableSelector) {
|
|
396
399
|
ruleData.hasUnqueryableSelector = true;
|
|
397
400
|
}
|
|
398
401
|
registerSelector(selector, ruleData, processingContext, docContext);
|
|
399
|
-
if (!
|
|
402
|
+
if (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length) {
|
|
400
403
|
const matchedElements = matchElements(selector, ancestorsSelectors, processingContext.scopeStack, docContext);
|
|
401
404
|
if (matchedElements.length) {
|
|
402
|
-
|
|
403
|
-
|
|
405
|
+
if (!hasUnqueryableSelector) {
|
|
406
|
+
updateMatchingSelectors(matchedElements, selector, docContext);
|
|
407
|
+
}
|
|
408
|
+
} else if (!hasNestedUnqueryablePseudoClass) {
|
|
404
409
|
removedSelectors.push(selector);
|
|
405
410
|
}
|
|
406
411
|
}
|
|
@@ -410,21 +415,42 @@ function processSelectors(ruleData, processingContext, docContext) {
|
|
|
410
415
|
|
|
411
416
|
function analyzeSelector(selector) {
|
|
412
417
|
let hasUnqueryableSelector = false;
|
|
418
|
+
let hasNestedUnqueryablePseudoClass = false;
|
|
413
419
|
let startsWithCombinator = false;
|
|
420
|
+
let functionalPseudoClassDepth = 0;
|
|
414
421
|
cssTree.walk(selector, {
|
|
415
422
|
enter(node) {
|
|
416
423
|
if (node.type === PSEUDO_ELEMENT_SELECTOR_TYPE) {
|
|
417
424
|
hasUnqueryableSelector = true;
|
|
425
|
+
if (functionalPseudoClassDepth) {
|
|
426
|
+
hasNestedUnqueryablePseudoClass = true;
|
|
427
|
+
}
|
|
418
428
|
} else if (node.type === PSEUDO_CLASS_SELECTOR_TYPE) {
|
|
419
429
|
if (PSEUDO_ELEMENT_SYNONYMS.has(node.name) || matchUnqueryablePseudoClass(node)) {
|
|
420
430
|
hasUnqueryableSelector = true;
|
|
431
|
+
if (functionalPseudoClassDepth) {
|
|
432
|
+
hasNestedUnqueryablePseudoClass = true;
|
|
433
|
+
}
|
|
421
434
|
}
|
|
435
|
+
if (FUNCTIONAL_PSEUDO_CLASS_NAMES.has(node.name.toLowerCase())) {
|
|
436
|
+
functionalPseudoClassDepth++;
|
|
437
|
+
}
|
|
438
|
+
} else if (node.type === ATTRIBUTE_SELECTOR_TYPE && matchUnqueryableAttributeSelector(node)) {
|
|
439
|
+
hasUnqueryableSelector = true;
|
|
440
|
+
if (functionalPseudoClassDepth) {
|
|
441
|
+
hasNestedUnqueryablePseudoClass = true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
leave(node) {
|
|
446
|
+
if (node.type === PSEUDO_CLASS_SELECTOR_TYPE && FUNCTIONAL_PSEUDO_CLASS_NAMES.has(node.name.toLowerCase())) {
|
|
447
|
+
functionalPseudoClassDepth--;
|
|
422
448
|
}
|
|
423
449
|
}
|
|
424
450
|
});
|
|
425
451
|
const firstChild = selector.children.head.data;
|
|
426
452
|
startsWithCombinator = firstChild && firstChild.type === COMBINATOR_NAME;
|
|
427
|
-
return { hasUnqueryableSelector, startsWithCombinator };
|
|
453
|
+
return { hasUnqueryableSelector, hasNestedUnqueryablePseudoClass, startsWithCombinator };
|
|
428
454
|
}
|
|
429
455
|
|
|
430
456
|
function updateMatchingSelectors(matchedElements, selector, docContext) {
|
|
@@ -47,7 +47,11 @@ const FUNCTIONAL_PSEUDO_CLASSES = [
|
|
|
47
47
|
"where",
|
|
48
48
|
"has"
|
|
49
49
|
];
|
|
50
|
+
const STATE_ATTRIBUTE_NAMES = [
|
|
51
|
+
"open"
|
|
52
|
+
];
|
|
50
53
|
export {
|
|
54
|
+
matchUnqueryableAttributeSelector,
|
|
51
55
|
matchUnqueryablePseudoClass,
|
|
52
56
|
sanitizeSelector,
|
|
53
57
|
};
|
|
@@ -97,6 +101,10 @@ function normalizeSelectorNode(selector, ancestors) {
|
|
|
97
101
|
if (matchUnqueryablePseudoClass(childNode)) {
|
|
98
102
|
removeNode(selector.children, current);
|
|
99
103
|
}
|
|
104
|
+
} else if (childNode.type === "AttributeSelector") {
|
|
105
|
+
if (matchUnqueryableAttributeSelector(childNode)) {
|
|
106
|
+
removeNode(selector.children, current);
|
|
107
|
+
}
|
|
100
108
|
} else if (childNode.type === "Selector") {
|
|
101
109
|
normalizeSelectorNode(childNode, ancestors);
|
|
102
110
|
}
|
|
@@ -112,6 +120,10 @@ function removeNode(list, item) {
|
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
|
|
123
|
+
function matchUnqueryableAttributeSelector(attributeSelector) {
|
|
124
|
+
return Boolean(attributeSelector.name) && STATE_ATTRIBUTE_NAMES.includes(attributeSelector.name.name.toLowerCase());
|
|
125
|
+
}
|
|
126
|
+
|
|
115
127
|
function matchUnqueryablePseudoClass(pseudoClass) {
|
|
116
128
|
const name = pseudoClass.name.toLowerCase();
|
|
117
129
|
return pseudoClass.children ? (
|
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import {
|
|
27
27
|
configure,
|
|
28
28
|
TextReader,
|
|
29
|
+
TextWriter,
|
|
29
30
|
Uint8ArrayReader,
|
|
30
31
|
Uint8ArrayWriter,
|
|
31
32
|
ZipReader
|
|
@@ -40,6 +41,7 @@ const browser = globalThis.browser;
|
|
|
40
41
|
|
|
41
42
|
const PAGES_PREFIX = "pages/";
|
|
42
43
|
const PAGES_FILENAME = "sfz-pages.json";
|
|
44
|
+
const MANIFEST_FILENAME = "manifest.json";
|
|
43
45
|
const TOC_FILENAME = "sfz-toc.html";
|
|
44
46
|
const TOC_TITLE = "Table of contents";
|
|
45
47
|
const TOC_STYLE = "body{font-family:system-ui,sans-serif;margin:2em auto;max-width:40em;padding:0 1em;background-color:#fff;color:#000}" +
|
|
@@ -102,6 +104,12 @@ async function createPagesArchive(pages, options) {
|
|
|
102
104
|
for (const entry of await zipReader.getEntries()) {
|
|
103
105
|
const filename = pagePath + entry.filename;
|
|
104
106
|
const rawData = entry.directory ? undefined : await entry.getData(new Uint8ArrayWriter(), { passThrough: true, checkCrc32: false });
|
|
107
|
+
if (entry.filename == MANIFEST_FILENAME && !entry.encrypted) {
|
|
108
|
+
const { archiveTime } = JSON.parse(await entry.getData(new TextWriter()));
|
|
109
|
+
if (archiveTime) {
|
|
110
|
+
manifest.pages[pageIndex].archiveTime = archiveTime;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
105
113
|
const canonicalFilename = writtenEntries && findDuplicate(writtenEntries, filename, entry, rawData);
|
|
106
114
|
if (canonicalFilename === undefined) {
|
|
107
115
|
await zipWriter.add(filename, entry.directory ? null : new Uint8ArrayReader(rawData), {
|
|
@@ -95,6 +95,7 @@ const MAX_ZIP_COMMENT_LENGTH = 65535;
|
|
|
95
95
|
const PDF_ENTRY_FILENAME = "page.pdf";
|
|
96
96
|
const PRESCAN_WINDOW_LENGTH = 1024;
|
|
97
97
|
const PNG_TEXT_CHUNK_HEADER_LENGTH = 12;
|
|
98
|
+
const PNG_LENGTH_PADDING_LENGTH = 1;
|
|
98
99
|
const PNG_ZIP_CHUNK_TYPE_KEYWORD = new Uint8Array([0x74, 0x45, 0x58, 0x74, 0x5a, 0x49, 0x50, 0]);
|
|
99
100
|
const MAX_HIDDEN_PNG_CHUNK_LENGTH = 0x2D000000;
|
|
100
101
|
const WRAPPER_PATTERN_WINDOW_LENGTH = 12;
|
|
@@ -277,7 +278,7 @@ async function buildArchive(pageData, options, script, entriesData, zipWriterOpt
|
|
|
277
278
|
const payloadView = new DataView(payload.buffer);
|
|
278
279
|
words.forEach((word, indexWord) => payloadView.setUint32(indexWord * 4, word, true));
|
|
279
280
|
extraData = "<sfz-extra-data>" + base64Encode(deflateRaw(payload)) + "</sfz-extra-data>";
|
|
280
|
-
if (options.preventAppendedData || extraData.length > getMaxAppendedDataLength(options) - pageContent.length - endTags.length - (options.embeddedImage ? PNG_IEND_LENGTH + PNG_CHUNK_CRC_LENGTH : 0)) {
|
|
281
|
+
if (options.preventAppendedData || extraData.length > getMaxAppendedDataLength(options) - pageContent.length - endTags.length - (options.embeddedImage ? PNG_IEND_LENGTH + PNG_CHUNK_CRC_LENGTH + PNG_LENGTH_PADDING_LENGTH : 0)) {
|
|
281
282
|
if (!options.extraDataSize) {
|
|
282
283
|
options.preventAppendedData = true;
|
|
283
284
|
options.extraDataSize = getReservationSize(extraData.length);
|
|
@@ -288,7 +289,11 @@ async function buildArchive(pageData, options, script, entriesData, zipWriterOpt
|
|
|
288
289
|
}
|
|
289
290
|
}
|
|
290
291
|
pageContent += endTags;
|
|
291
|
-
|
|
292
|
+
let pageContentData = new TextEncoder().encode(pageContent);
|
|
293
|
+
if (options.embeddedImage && !isChunkLengthHidden(data, embeddedImageDataOffset, zipDataWriter.offset + pageContentData.length - embeddedImageDataOffset - 4, imageChunk.tagIndex)) {
|
|
294
|
+
pageContentData = concatArrays(pageContentData, new Uint8Array(PNG_LENGTH_PADDING_LENGTH).fill(0x20));
|
|
295
|
+
}
|
|
296
|
+
await writeData(zipDataWriter.writable, pageContentData);
|
|
292
297
|
}
|
|
293
298
|
await zipDataWriter.writable.close();
|
|
294
299
|
const pageContent = await zipDataWriter.getData();
|
|
@@ -337,6 +342,15 @@ function isDeclaredLengthHidden(pageContent, zipDataEnd, appendedDataLength, opt
|
|
|
337
342
|
return !containsDataPattern(tail, EMBEDDED_DATA_PATTERNS[tagIndex]);
|
|
338
343
|
}
|
|
339
344
|
|
|
345
|
+
function isChunkLengthHidden(data, embeddedImageDataOffset, chunkLength, tagIndex) {
|
|
346
|
+
const lengthOffset = embeddedImageDataOffset - 4;
|
|
347
|
+
const window = concatArrays(
|
|
348
|
+
data.subarray(Math.max(0, lengthOffset - WRAPPER_PATTERN_WINDOW_LENGTH), lengthOffset),
|
|
349
|
+
getLength(chunkLength),
|
|
350
|
+
data.subarray(embeddedImageDataOffset, embeddedImageDataOffset + PNG_ZIP_CHUNK_TYPE_KEYWORD.length));
|
|
351
|
+
return !containsDataPattern(window, EMBEDDED_DATA_PATTERNS[tagIndex]);
|
|
352
|
+
}
|
|
353
|
+
|
|
340
354
|
function getCRC32(data, indexData = 0) {
|
|
341
355
|
const crcArray = new Uint8Array(4);
|
|
342
356
|
setUint32(crcArray, getCRC32Value(data, indexData));
|
|
@@ -723,7 +737,7 @@ function getImageHTMLChunk(pageData, options, lastModDate) {
|
|
|
723
737
|
findEmbeddedDataTagIndex(wrappedData, tagIndex) != tagIndex) {
|
|
724
738
|
tagIndex = findEmbeddedDataTagIndex(embeddedImageData, tagIndex + 1);
|
|
725
739
|
} else {
|
|
726
|
-
return { endTag, startHTMLData, htmlData, htmlDataCRC };
|
|
740
|
+
return { tagIndex, endTag, startHTMLData, htmlData, htmlDataCRC };
|
|
727
741
|
}
|
|
728
742
|
}
|
|
729
743
|
}
|
|
@@ -798,6 +812,7 @@ function isCompressibleContentType(contentType) {
|
|
|
798
812
|
async function getContent() {
|
|
799
813
|
const BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
800
814
|
const DATA_IDENTIFIER = "sfz-data";
|
|
815
|
+
const WRAPPER_TAG_NAMES = ["script", "style", "noframes", "noembed", "iframe", "xmp", "svg", "plaintext"];
|
|
801
816
|
const { Blob, XMLHttpRequest, NodeFilter, document, zip, location } = globalThis;
|
|
802
817
|
const characterMap = new Map([
|
|
803
818
|
[65533, 0], [8364, 128], [8218, 130], [402, 131], [8222, 132], [8230, 133], [8224, 134], [8225, 135], [710, 136], [8240, 137],
|
|
@@ -902,15 +917,22 @@ async function getContent() {
|
|
|
902
917
|
if (zipDataElement) {
|
|
903
918
|
const inflatedPayload = zip.inflateRaw(base64Decode(zipDataElement.textContent));
|
|
904
919
|
const payload = new DataView(inflatedPayload.buffer, inflatedPayload.byteOffset, inflatedPayload.length & -4);
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
920
|
+
const candidates = Array.from(document.querySelectorAll("[id=" + DATA_IDENTIFIER + "]")).filter(element => WRAPPER_TAG_NAMES.includes(element.localName));
|
|
921
|
+
let startIndex = 0;
|
|
922
|
+
if (!candidates.length) {
|
|
923
|
+
const walker = document.createTreeWalker(document, NodeFilter.SHOW_COMMENT);
|
|
924
|
+
while (walker.nextNode()) {
|
|
925
|
+
if (walker.currentNode.data.startsWith(DATA_IDENTIFIER)) {
|
|
926
|
+
candidates.push(walker.currentNode);
|
|
927
|
+
}
|
|
913
928
|
}
|
|
929
|
+
startIndex = DATA_IDENTIFIER.length;
|
|
930
|
+
}
|
|
931
|
+
if (candidates.length > 1) {
|
|
932
|
+
throw new Error("Multiple zip data candidates found");
|
|
933
|
+
}
|
|
934
|
+
if (candidates.length) {
|
|
935
|
+
return decodeZipData(candidates[0], payload, startIndex);
|
|
914
936
|
}
|
|
915
937
|
}
|
|
916
938
|
throw new Error("Extra zip data not found");
|
|
@@ -231,6 +231,7 @@ function initResponse(message) {
|
|
|
231
231
|
frameData.posters = messageFrameData.posters;
|
|
232
232
|
frameData.videos = messageFrameData.videos;
|
|
233
233
|
frameData.usedFonts = messageFrameData.usedFonts;
|
|
234
|
+
frameData.usedFontsCharacters = messageFrameData.usedFontsCharacters;
|
|
234
235
|
frameData.shadowRoots = messageFrameData.shadowRoots;
|
|
235
236
|
frameData.processed = messageFrameData.processed;
|
|
236
237
|
frameData.scrollPosition = messageFrameData.scrollPosition;
|
|
@@ -453,6 +454,7 @@ function getFrameData(document, win, windowId, options, scrolling) {
|
|
|
453
454
|
posters: docData.posters,
|
|
454
455
|
videos: docData.videos,
|
|
455
456
|
usedFonts: docData.usedFonts,
|
|
457
|
+
usedFontsCharacters: docData.usedFontsCharacters,
|
|
456
458
|
shadowRoots: docData.shadowRoots,
|
|
457
459
|
scrollPosition: docData.scrollPosition,
|
|
458
460
|
scrolling,
|
|
@@ -586,7 +586,9 @@
|
|
|
586
586
|
if (adoptedStylesheetsData.has(stylesheet)) {
|
|
587
587
|
return adoptedStylesheetsData.get(stylesheet);
|
|
588
588
|
} else {
|
|
589
|
-
|
|
589
|
+
const text = Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
|
|
590
|
+
adoptedStylesheetsData.set(stylesheet, text);
|
|
591
|
+
return text;
|
|
590
592
|
}
|
|
591
593
|
});
|
|
592
594
|
if (adoptedStyleSheets.length) {
|