single-file-core 1.5.81 → 1.5.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/index.js +1 -0
- package/core/lib/processor-helper-common.js +2 -2
- package/modules/css-rules-minifier.js +304 -235
- package/modules/css-scope-prelude-parser.js +32 -48
- package/modules/css-selector-sanitizer.js +43 -62
- package/modules/css-specificity.js +4 -1
- package/package.json +1 -1
- package/processors/compression/compression.js +6 -6
- package/single-file-infobar.js +9 -5
- package/single-file.js +1 -0
package/core/index.js
CHANGED
|
@@ -678,6 +678,7 @@ class Processor {
|
|
|
678
678
|
replaceEmojisInFilename: this.options.replaceEmojisInFilename,
|
|
679
679
|
compressContent: this.options.compressContent,
|
|
680
680
|
selfExtractingArchive: this.options.selfExtractingArchive,
|
|
681
|
+
disableCompression: this.options.disableCompression,
|
|
681
682
|
extractDataFromPage: this.options.extractDataFromPage,
|
|
682
683
|
referrer: this.options.referrer,
|
|
683
684
|
title: this.options.title,
|
|
@@ -98,8 +98,8 @@ class ProcessorHelperCommon {
|
|
|
98
98
|
["embed[src*=\".svg\"]", "src"],
|
|
99
99
|
["video[poster]", "poster"],
|
|
100
100
|
["*[background]", "background"],
|
|
101
|
-
["image", "xlink:href"],
|
|
102
|
-
["image", "href"]
|
|
101
|
+
["image, feImage", "xlink:href"],
|
|
102
|
+
["image, feImage", "href"]
|
|
103
103
|
];
|
|
104
104
|
if (options.blockImages) {
|
|
105
105
|
doc.querySelectorAll("svg").forEach(element => element.remove());
|
|
@@ -24,12 +24,14 @@
|
|
|
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,
|
|
27
|
+
import { sanitizeSelector, matchUnqueryablePseudoClass } from "./css-selector-sanitizer.js";
|
|
28
28
|
|
|
29
29
|
const DEBUG = false;
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
31
|
+
const PSEUDO_ELEMENT_SYNONYMS = new Set(["after", "before", "first-letter", "first-line"]);
|
|
32
|
+
const MEDIA_AT_RULE_NAME = "media";
|
|
33
|
+
const SUPPORTS_AT_RULE_NAME = "supports";
|
|
34
|
+
const CONDITIONAL_AT_RULE_NAMES = new Set([MEDIA_AT_RULE_NAME, SUPPORTS_AT_RULE_NAME, "container"]);
|
|
33
35
|
const RULE_TYPE = "Rule";
|
|
34
36
|
const AT_RULE_TYPE = "Atrule";
|
|
35
37
|
const NESTING_SELECTOR_TYPE = "NestingSelector";
|
|
@@ -51,7 +53,6 @@ const SELECTOR_CONTEXT = "selector";
|
|
|
51
53
|
const DECLARATION_LIST_CONTEXT = "declarationList";
|
|
52
54
|
const PARSE_CSS_ERROR_MESSAGE = "Failed to parse CSS";
|
|
53
55
|
const QSA_ERROR_MESSAGE = "Failed to match selector";
|
|
54
|
-
const ROOT_PSEUDO_CLASS = ":root";
|
|
55
56
|
const PRELUDE_SEPARATOR = ",";
|
|
56
57
|
const NESTING_SELECTOR = "&";
|
|
57
58
|
const VENDOR_PREFIX = "-";
|
|
@@ -84,9 +85,8 @@ function process(doc, stylesheets) {
|
|
|
84
85
|
selectorData: new Map(),
|
|
85
86
|
selectorTexts: new Map(),
|
|
86
87
|
preludeTexts: new Map(),
|
|
87
|
-
scopeRoots: new Map(),
|
|
88
|
-
scopeSpecificities: new Map(),
|
|
89
88
|
rulesCounter: 0,
|
|
89
|
+
scopeIdCounter: 0
|
|
90
90
|
};
|
|
91
91
|
collectLayerOrder(stylesheets, docContext);
|
|
92
92
|
buildEffectiveLayerOrder(docContext);
|
|
@@ -124,10 +124,11 @@ function minifyRules(stylesheets, docContext) {
|
|
|
124
124
|
stylesheets.forEach((stylesheetInfo, key) => {
|
|
125
125
|
if (!stylesheetInfo.scoped && stylesheetInfo.stylesheet && !key.urlNode) {
|
|
126
126
|
if (hasChildNodes(stylesheetInfo.stylesheet)) {
|
|
127
|
-
const topConditionalStack = stylesheetInfo.mediaText ? [{ name:
|
|
127
|
+
const topConditionalStack = stylesheetInfo.mediaText ? [{ name: MEDIA_AT_RULE_NAME, prelude: stylesheetInfo.mediaText }] : [];
|
|
128
128
|
minifyStylesheetRules(stylesheetInfo.stylesheet.children, stylesheets, {
|
|
129
129
|
ancestorsSelectors: [],
|
|
130
130
|
layerStack: [],
|
|
131
|
+
scopeStack: [],
|
|
131
132
|
conditionalStack: topConditionalStack
|
|
132
133
|
}, docContext);
|
|
133
134
|
}
|
|
@@ -218,12 +219,12 @@ function minifyRule(ruleData, cssRule, stylesheets, processingContext, removedRu
|
|
|
218
219
|
|
|
219
220
|
function minifyImportRule(ruleData, _cssRule, stylesheets, processingContext, _removedRules, docContext) {
|
|
220
221
|
const urlNode = ruleData.prelude.children.head.data;
|
|
221
|
-
const topConditionalStack = urlNode.importedMediaText ? [{ name:
|
|
222
|
+
const topConditionalStack = urlNode.importedMediaText ? [{ name: MEDIA_AT_RULE_NAME, prelude: urlNode.importedMediaText }] : [];
|
|
222
223
|
if (urlNode.importedLayerName !== undefined) {
|
|
223
|
-
topConditionalStack.push({ name:
|
|
224
|
+
topConditionalStack.push({ name: LAYER_NAME, prelude: urlNode.importedLayerName });
|
|
224
225
|
}
|
|
225
226
|
if (urlNode.importedSupportsCondition !== undefined) {
|
|
226
|
-
topConditionalStack.push({ name:
|
|
227
|
+
topConditionalStack.push({ name: SUPPORTS_AT_RULE_NAME, prelude: urlNode.importedSupportsCondition });
|
|
227
228
|
}
|
|
228
229
|
minifyStylesheetRules(urlNode.importedChildren, stylesheets, {
|
|
229
230
|
...processingContext,
|
|
@@ -243,16 +244,24 @@ function minifyLayerRule(ruleData, cssRule, stylesheets, processingContext, remo
|
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
function minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
let scopeContext;
|
|
248
|
+
try {
|
|
249
|
+
const parsedPrelude = parsePrelude(ruleData.prelude);
|
|
250
|
+
scopeContext = buildScopeContext(parsedPrelude, processingContext, docContext);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (DEBUG) {
|
|
253
|
+
// eslint-disable-next-line no-console
|
|
254
|
+
console.error(PARSE_CSS_ERROR_MESSAGE, { ruleData, error });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (!scopeContext) {
|
|
258
|
+
docContext.stats.discarded++;
|
|
259
|
+
removedRules.add(cssRule);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
250
262
|
const newProcessingContext = {
|
|
251
263
|
...processingContext,
|
|
252
|
-
|
|
253
|
-
scopeIncludeLists: [...(processingContext.scopeIncludeLists || []), includeLists],
|
|
254
|
-
scopeExclusionLists: [...(processingContext.scopeExclusionLists || []), excludeLists],
|
|
255
|
-
scopeNestingLevel: (processingContext.scopeNestingLevel || 0) + 1
|
|
264
|
+
scopeStack: [...(processingContext.scopeStack || []), scopeContext]
|
|
256
265
|
};
|
|
257
266
|
expandRawCssRules(ruleData);
|
|
258
267
|
minifyStylesheetRules(ruleData.block.children, stylesheets, newProcessingContext, docContext);
|
|
@@ -262,6 +271,67 @@ function minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, remo
|
|
|
262
271
|
}
|
|
263
272
|
}
|
|
264
273
|
|
|
274
|
+
function buildScopeContext(parsedPrelude, processingContext, docContext) {
|
|
275
|
+
const scopeStack = processingContext.scopeStack || [];
|
|
276
|
+
const includeSelectors = parsedPrelude && parsedPrelude.include ? parsedPrelude.include : [];
|
|
277
|
+
let rootElements = [];
|
|
278
|
+
if (includeSelectors.length) {
|
|
279
|
+
rootElements = collectScopeRootElements(includeSelectors, scopeStack, docContext);
|
|
280
|
+
} else if (scopeStack.length) {
|
|
281
|
+
rootElements = Array.from(scopeStack[scopeStack.length - 1].rootElements);
|
|
282
|
+
} else {
|
|
283
|
+
rootElements = getDefaultScopeRoots(docContext);
|
|
284
|
+
}
|
|
285
|
+
const uniqueRoots = Array.from(new Set(rootElements.filter(Boolean)));
|
|
286
|
+
if (!uniqueRoots.length) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const boundaryElements = collectScopeBoundaryElements(parsedPrelude.exclude || [], uniqueRoots, docContext);
|
|
290
|
+
return {
|
|
291
|
+
id: docContext.scopeIdCounter++,
|
|
292
|
+
rootElements: new Set(uniqueRoots),
|
|
293
|
+
stopElements: boundaryElements,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function collectScopeRootElements(includeSelectors, scopeStack, docContext) {
|
|
298
|
+
const roots = new Set();
|
|
299
|
+
includeSelectors.forEach(selectorInfo => {
|
|
300
|
+
const selectorText = sanitizeSelector(selectorInfo, null, docContext);
|
|
301
|
+
const matchedNodes = querySelectorAll(docContext.doc, selectorText);
|
|
302
|
+
filterElementsByScopes(matchedNodes, scopeStack).forEach(match => roots.add(match));
|
|
303
|
+
});
|
|
304
|
+
return Array.from(roots);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function collectScopeBoundaryElements(excludeSelectors, rootElements, docContext) {
|
|
308
|
+
const boundaries = new Set();
|
|
309
|
+
if (!excludeSelectors.length || !rootElements.length) {
|
|
310
|
+
return boundaries;
|
|
311
|
+
}
|
|
312
|
+
excludeSelectors.forEach(selectorInfo => {
|
|
313
|
+
const selectorText = sanitizeSelector(selectorInfo, null, docContext);
|
|
314
|
+
rootElements.forEach(root => {
|
|
315
|
+
matchSelectorWithinRoot(root, selectorText).forEach(node => boundaries.add(node));
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
return boundaries;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function getDefaultScopeRoots(docContext) {
|
|
322
|
+
const roots = [];
|
|
323
|
+
if (docContext.doc && docContext.doc.documentElement) {
|
|
324
|
+
roots.push(docContext.doc.documentElement);
|
|
325
|
+
}
|
|
326
|
+
if (!roots.length && docContext.doc && docContext.doc.body) {
|
|
327
|
+
roots.push(docContext.doc.body);
|
|
328
|
+
}
|
|
329
|
+
if (!roots.length && docContext.doc && docContext.doc.children) {
|
|
330
|
+
roots.push(...Array.from(docContext.doc.children).filter(node => node.nodeType === 1));
|
|
331
|
+
}
|
|
332
|
+
return roots;
|
|
333
|
+
}
|
|
334
|
+
|
|
265
335
|
function minifyAtRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
|
|
266
336
|
const newConditionalStack = buildConditionalStack(processingContext.conditionalStack, ruleData, docContext);
|
|
267
337
|
const newProcessingContext = { ...processingContext, conditionalStack: newConditionalStack };
|
|
@@ -288,16 +358,17 @@ function processSelectors(ruleData, processingContext, docContext) {
|
|
|
288
358
|
for (let selector = ruleData.prelude.children.head, selectorIndex = 0; selector; selector = selector.next, selectorIndex++) {
|
|
289
359
|
const {
|
|
290
360
|
startsWithCombinator,
|
|
291
|
-
|
|
292
|
-
hasDynamicStatePseudoClass,
|
|
293
|
-
scopeRelative
|
|
361
|
+
hasUnqueryableSelector
|
|
294
362
|
} = analyzeSelector(selector.data);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
363
|
+
if (hasUnqueryableSelector) {
|
|
364
|
+
ruleData.hasUnqueryableSelector = true;
|
|
365
|
+
}
|
|
366
|
+
registerSelector(selector, ruleData, processingContext, docContext);
|
|
367
|
+
if (!hasUnqueryableSelector && (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length)) {
|
|
368
|
+
const matchedElements = matchElements(selector, ancestorsSelectors, processingContext.scopeStack, docContext);
|
|
369
|
+
if (matchedElements.length) {
|
|
299
370
|
updateMatchingSelectors(matchedElements, selector, docContext);
|
|
300
|
-
} else
|
|
371
|
+
} else {
|
|
301
372
|
removedSelectors.push(selector);
|
|
302
373
|
}
|
|
303
374
|
}
|
|
@@ -306,31 +377,22 @@ function processSelectors(ruleData, processingContext, docContext) {
|
|
|
306
377
|
}
|
|
307
378
|
|
|
308
379
|
function analyzeSelector(selector) {
|
|
309
|
-
let
|
|
310
|
-
let hasDynamicStatePseudoClass = false;
|
|
311
|
-
let hasNestingOrScope = false;
|
|
380
|
+
let hasUnqueryableSelector = false;
|
|
312
381
|
let startsWithCombinator = false;
|
|
313
382
|
cssTree.walk(selector, {
|
|
314
383
|
enter(node) {
|
|
315
384
|
if (node.type === PSEUDO_ELEMENT_SELECTOR_TYPE) {
|
|
316
|
-
|
|
385
|
+
hasUnqueryableSelector = true;
|
|
317
386
|
} else if (node.type === PSEUDO_CLASS_SELECTOR_TYPE) {
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
} else if (DYNAMIC_STATE_PSEUDO_CLASSES.includes(node.name)) {
|
|
321
|
-
hasDynamicStatePseudoClass = true;
|
|
322
|
-
} else if (node.name === SCOPE_NAME) {
|
|
323
|
-
hasNestingOrScope = true;
|
|
387
|
+
if (PSEUDO_ELEMENT_SYNONYMS.has(node.name) || matchUnqueryablePseudoClass(node)) {
|
|
388
|
+
hasUnqueryableSelector = true;
|
|
324
389
|
}
|
|
325
|
-
} else if (node.type === NESTING_SELECTOR_TYPE) {
|
|
326
|
-
hasNestingOrScope = true;
|
|
327
390
|
}
|
|
328
391
|
}
|
|
329
392
|
});
|
|
330
393
|
const firstChild = selector.children.head.data;
|
|
331
394
|
startsWithCombinator = firstChild && firstChild.type === COMBINATOR_NAME;
|
|
332
|
-
|
|
333
|
-
return { hasCanonicalPseudoElement, hasDynamicStatePseudoClass, startsWithCombinator, scopeRelative };
|
|
395
|
+
return { hasUnqueryableSelector, startsWithCombinator };
|
|
334
396
|
}
|
|
335
397
|
|
|
336
398
|
function updateMatchingSelectors(matchedElements, selector, docContext) {
|
|
@@ -352,24 +414,18 @@ function processNestedRules(ruleData, stylesheets, processingContext, docContext
|
|
|
352
414
|
minifyStylesheetRules(ruleData.block.children, stylesheets, newProcessingContext, docContext);
|
|
353
415
|
}
|
|
354
416
|
|
|
355
|
-
function registerSelector(selector, ruleData,
|
|
417
|
+
function registerSelector(selector, ruleData, processingContext, docContext) {
|
|
356
418
|
const {
|
|
357
419
|
ancestorsSelectors,
|
|
358
420
|
layerStack,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
scopeExclusionLists,
|
|
362
|
-
scopeNestingLevel
|
|
421
|
+
scopeStack,
|
|
422
|
+
conditionalStack
|
|
363
423
|
} = processingContext;
|
|
364
424
|
docContext.selectorData.set(selector, {
|
|
365
|
-
specificity: computeMaxSpecificity(selector.data, ancestorsSelectors),
|
|
425
|
+
specificity: computeMaxSpecificity(selector.data, ancestorsSelectors, scopeStack),
|
|
366
426
|
rule: ruleData,
|
|
367
427
|
layerStack,
|
|
368
|
-
conditionalStack
|
|
369
|
-
scopeIncludeLists,
|
|
370
|
-
scopeExclusionLists,
|
|
371
|
-
scopeNestingLevel,
|
|
372
|
-
scopeRelative
|
|
428
|
+
conditionalStack
|
|
373
429
|
});
|
|
374
430
|
}
|
|
375
431
|
|
|
@@ -414,35 +470,44 @@ function collectDeclarationItemsForElement(element, docContext) {
|
|
|
414
470
|
for (let declaration = declarations.head; declaration; declaration = declaration.next) {
|
|
415
471
|
const { type, value } = declaration.data;
|
|
416
472
|
if (type === DECLARATION_TYPE && value) {
|
|
417
|
-
|
|
418
|
-
const isSingleValue = value.type === VALUE_TYPE &&
|
|
419
|
-
hasChildNodes(value) &&
|
|
420
|
-
value.children.length == 1 &&
|
|
421
|
-
value.children.head.data.name;
|
|
422
|
-
const isVendorValue = isSingleValue && value.children.head.data.name.startsWith(VENDOR_PREFIX);
|
|
423
|
-
const isInvalidValue = isSingleValue && INVALID_CSS_ESCAPE_TEST.test(value.children.head.data.name);
|
|
424
|
-
if (!isRawValue && !isVendorValue && !isInvalidValue) {
|
|
425
|
-
allDeclarations.push({
|
|
426
|
-
declaration,
|
|
427
|
-
selector,
|
|
428
|
-
effectiveSpecificity: computeEffectiveSpecificity(
|
|
429
|
-
docContext.selectorData.get(selector), element, docContext),
|
|
430
|
-
isInline: false
|
|
431
|
-
});
|
|
432
|
-
}
|
|
473
|
+
addDeclaration(declaration, docContext.selectorData.get(selector).specificity, false, selector);
|
|
433
474
|
}
|
|
434
475
|
}
|
|
435
476
|
}
|
|
436
477
|
});
|
|
437
478
|
const inlineDeclarations = getInlineStyleDeclarations(element);
|
|
438
479
|
for (const declaration of inlineDeclarations) {
|
|
439
|
-
|
|
440
|
-
declaration: declaration.declaration,
|
|
441
|
-
effectiveSpecificity: declaration.effectiveSpecificity,
|
|
442
|
-
isInline: true
|
|
443
|
-
});
|
|
480
|
+
addDeclaration(declaration.declaration, declaration.specificity, true);
|
|
444
481
|
}
|
|
445
482
|
return allDeclarations;
|
|
483
|
+
|
|
484
|
+
function addDeclaration(declaration, specificity, isInline, selector) {
|
|
485
|
+
const { property, value } = declaration.data;
|
|
486
|
+
const isRawValue = value.type === RAW_TYPE;
|
|
487
|
+
const hasValueChildNodes = hasChildNodes(value) || value.type === RAW_TYPE;
|
|
488
|
+
let isInvalidValue;
|
|
489
|
+
if (value.type === VALUE_TYPE &&
|
|
490
|
+
hasValueChildNodes &&
|
|
491
|
+
value.children.size == 1) {
|
|
492
|
+
if (value.children.head.data.name) {
|
|
493
|
+
isInvalidValue = value.children.head.data.name.startsWith(VENDOR_PREFIX) || INVALID_CSS_ESCAPE_TEST.test(value.children.head.data.name);
|
|
494
|
+
} if (!property.startsWith(VENDOR_PREFIX) && value.children.head.data.value) {
|
|
495
|
+
try {
|
|
496
|
+
isInvalidValue = !cssTree.lexer.matchProperty(property, value).matched;
|
|
497
|
+
} catch {
|
|
498
|
+
// ignored
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (hasValueChildNodes && !isRawValue && !isInvalidValue) {
|
|
503
|
+
allDeclarations.push({
|
|
504
|
+
declaration,
|
|
505
|
+
selector,
|
|
506
|
+
specificity,
|
|
507
|
+
isInline
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
446
511
|
}
|
|
447
512
|
|
|
448
513
|
function getConditionalStackForSelector(selector, docContext) {
|
|
@@ -456,132 +521,169 @@ function getConditionalStackForSelector(selector, docContext) {
|
|
|
456
521
|
return conditionalStack;
|
|
457
522
|
}
|
|
458
523
|
|
|
459
|
-
function matchElements(selector, ancestorsSelectors, docContext) {
|
|
524
|
+
function matchElements(selector, ancestorsSelectors, scopeStack, docContext) {
|
|
460
525
|
const selectorText = createSelectorText(selector, ancestorsSelectors, docContext);
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
526
|
+
const cacheKey = createScopeCacheKey(selectorText, scopeStack);
|
|
527
|
+
const cachedNodes = docContext.matchedSelectors.get(cacheKey);
|
|
528
|
+
if (cachedNodes) {
|
|
529
|
+
return cachedNodes;
|
|
530
|
+
}
|
|
531
|
+
let nodes;
|
|
532
|
+
if (scopeStack && scopeStack.length) {
|
|
533
|
+
nodes = matchElementsInScope(selectorText, scopeStack);
|
|
534
|
+
nodes = filterElementsByScopes(nodes, scopeStack);
|
|
467
535
|
} else {
|
|
468
|
-
|
|
469
|
-
return collectScopedMatches(cacheKey, selector, docContext);
|
|
470
|
-
} else {
|
|
471
|
-
const nodes = querySelectorAll(docContext.doc, selectorText, docContext.scopeRoots);
|
|
472
|
-
docContext.matchedSelectors.set(cacheKey, nodes);
|
|
473
|
-
return nodes;
|
|
474
|
-
}
|
|
536
|
+
nodes = querySelectorAll(docContext.doc, selectorText);
|
|
475
537
|
}
|
|
538
|
+
docContext.matchedSelectors.set(cacheKey, nodes);
|
|
539
|
+
return nodes;
|
|
476
540
|
}
|
|
477
541
|
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
selectorText = combineSelectorWithAncestors(selector.data, ancestorsSelectors, docContext);
|
|
482
|
-
const combinedAst = parseCss(selectorText, SELECTOR_LIST_CONTEXT);
|
|
483
|
-
selectorText = sanitizeSelector({ data: combinedAst }, ancestorsSelectors, docContext);
|
|
542
|
+
function createScopeCacheKey(selectorText, scopeStack) {
|
|
543
|
+
if (!scopeStack || !scopeStack.length) {
|
|
544
|
+
return selectorText;
|
|
484
545
|
}
|
|
485
|
-
|
|
486
|
-
|
|
546
|
+
const signature = scopeStack.map(scope => scope.id).join(CONTEXT_KEY_SEPARATOR);
|
|
547
|
+
return `${selectorText}${CONTEXT_KEY_SEPARATOR}${signature}`;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function matchElementsInScope(selectorText, scopeStack) {
|
|
551
|
+
const currentScope = scopeStack[scopeStack.length - 1];
|
|
552
|
+
const roots = Array.from(currentScope.rootElements);
|
|
553
|
+
if (!roots.length) {
|
|
554
|
+
return [];
|
|
487
555
|
}
|
|
488
|
-
|
|
556
|
+
const matchedNodes = new Set();
|
|
557
|
+
roots.forEach(root => {
|
|
558
|
+
matchSelectorWithinRoot(root, selectorText).forEach(node => matchedNodes.add(node));
|
|
559
|
+
});
|
|
560
|
+
return Array.from(matchedNodes);
|
|
489
561
|
}
|
|
490
562
|
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
563
|
+
function matchSelectorWithinRoot(root, selectorText) {
|
|
564
|
+
const matchedNodes = new Set();
|
|
565
|
+
if (!root || root.nodeType !== 1) {
|
|
566
|
+
return matchedNodes;
|
|
567
|
+
}
|
|
568
|
+
if (matches(root, selectorText)) {
|
|
569
|
+
matchedNodes.add(root);
|
|
570
|
+
}
|
|
571
|
+
let nodes;
|
|
572
|
+
try {
|
|
573
|
+
nodes = root.querySelectorAll(selectorText);
|
|
574
|
+
} catch {
|
|
575
|
+
if (DEBUG) {
|
|
576
|
+
// eslint-disable-next-line no-console
|
|
577
|
+
console.error(QSA_ERROR_MESSAGE, { root, selectorText });
|
|
578
|
+
}
|
|
579
|
+
nodes = matchByTraversal([root], selectorText, true);
|
|
580
|
+
}
|
|
581
|
+
for (const node of nodes) {
|
|
582
|
+
matchedNodes.add(node);
|
|
504
583
|
}
|
|
584
|
+
return Array.from(matchedNodes);
|
|
505
585
|
}
|
|
506
586
|
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
587
|
+
function matches(element, selectorText) {
|
|
588
|
+
if (!element || element.nodeType !== 1) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
return element.matches(selectorText);
|
|
593
|
+
} catch {
|
|
594
|
+
return false;
|
|
515
595
|
}
|
|
516
|
-
const matchedElements = Array.from(matchedSet);
|
|
517
|
-
docContext.matchedSelectors.set(cacheKey, matchedElements);
|
|
518
|
-
return matchedElements;
|
|
519
596
|
}
|
|
520
597
|
|
|
521
|
-
function
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
598
|
+
function matchByTraversal(roots, selectorText, skipFirst) {
|
|
599
|
+
const results = [];
|
|
600
|
+
const visited = new Set();
|
|
601
|
+
const stack = [];
|
|
602
|
+
roots.forEach(root => {
|
|
603
|
+
if (root && root.nodeType === 1) {
|
|
604
|
+
stack.push({ node: root, include: !skipFirst });
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
while (stack.length) {
|
|
608
|
+
const { node, include } = stack.pop();
|
|
609
|
+
if (!node || visited.has(node)) {
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
visited.add(node);
|
|
613
|
+
if (include && matches(node, selectorText)) {
|
|
614
|
+
results.push(node);
|
|
615
|
+
}
|
|
616
|
+
for (let child = node.firstElementChild; child; child = child.nextElementSibling) {
|
|
617
|
+
stack.push({ node: child, include: true });
|
|
532
618
|
}
|
|
533
619
|
}
|
|
620
|
+
return results;
|
|
534
621
|
}
|
|
535
622
|
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
623
|
+
function getTraversalRoots(root) {
|
|
624
|
+
if (!root) {
|
|
625
|
+
return [];
|
|
626
|
+
}
|
|
627
|
+
if (root.nodeType === 9 || root.nodeType === 11) {
|
|
628
|
+
const roots = [];
|
|
629
|
+
if (root.documentElement) {
|
|
630
|
+
roots.push(root.documentElement);
|
|
631
|
+
}
|
|
632
|
+
if (root.body && (!roots.length || root.body !== roots[0])) {
|
|
633
|
+
roots.push(root.body);
|
|
541
634
|
}
|
|
635
|
+
if (!roots.length && root.children) {
|
|
636
|
+
roots.push(...Array.from(root.children).filter(node => node.nodeType === 1));
|
|
637
|
+
}
|
|
638
|
+
return roots;
|
|
542
639
|
}
|
|
543
|
-
return
|
|
640
|
+
return [root];
|
|
544
641
|
}
|
|
545
642
|
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
const headData = head.data;
|
|
561
|
-
if (headData && headData.type === COMBINATOR_NAME) {
|
|
562
|
-
const scope = { type: PSEUDO_CLASS_SELECTOR_TYPE, name: SCOPE_NAME };
|
|
563
|
-
childData.children.insertData(scope, head);
|
|
564
|
-
}
|
|
643
|
+
function filterElementsByScopes(elements, scopeStack) {
|
|
644
|
+
if (!scopeStack || !scopeStack.length) {
|
|
645
|
+
return elements;
|
|
646
|
+
}
|
|
647
|
+
return elements.filter(element => isElementWithinScopes(element, scopeStack));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function isElementWithinScopes(element, scopeStack) {
|
|
651
|
+
if (!element) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
for (let index = 0; index < scopeStack.length; index++) {
|
|
655
|
+
if (!isElementWithinScope(element, scopeStack[index])) {
|
|
656
|
+
return false;
|
|
565
657
|
}
|
|
566
658
|
}
|
|
567
|
-
return
|
|
659
|
+
return true;
|
|
568
660
|
}
|
|
569
661
|
|
|
570
|
-
function
|
|
571
|
-
let
|
|
572
|
-
|
|
573
|
-
|
|
662
|
+
function isElementWithinScope(element, scopeContext) {
|
|
663
|
+
let current = element;
|
|
664
|
+
while (current && current.nodeType === 1) {
|
|
665
|
+
if (scopeContext.stopElements && scopeContext.stopElements.has(current)) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
if (scopeContext.rootElements.has(current)) {
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
current = current.parentElement;
|
|
574
672
|
}
|
|
575
|
-
return
|
|
673
|
+
return false;
|
|
576
674
|
}
|
|
577
675
|
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
676
|
+
function createSelectorText(selector, ancestorsSelectors, docContext) {
|
|
677
|
+
let selectorText;
|
|
678
|
+
if (ancestorsSelectors && ancestorsSelectors.length) {
|
|
679
|
+
selectorText = combineSelectorWithAncestors(selector.data, ancestorsSelectors, docContext);
|
|
680
|
+
const combinedAst = parseCss(selectorText, SELECTOR_LIST_CONTEXT);
|
|
681
|
+
selectorText = sanitizeSelector({ data: combinedAst }, ancestorsSelectors, docContext);
|
|
682
|
+
}
|
|
683
|
+
if (!selectorText) {
|
|
684
|
+
selectorText = sanitizeSelector(selector, ancestorsSelectors, docContext);
|
|
583
685
|
}
|
|
584
|
-
return
|
|
686
|
+
return selectorText;
|
|
585
687
|
}
|
|
586
688
|
|
|
587
689
|
function compareDeclarations(declarationA, declarationB, docContext) {
|
|
@@ -599,8 +701,8 @@ function compareDeclarations(declarationA, declarationB, docContext) {
|
|
|
599
701
|
if (layerComparison !== 0) {
|
|
600
702
|
return importantA ? -layerComparison : layerComparison;
|
|
601
703
|
}
|
|
602
|
-
const specificityA = declarationA.
|
|
603
|
-
const specificityB = declarationB.
|
|
704
|
+
const specificityA = declarationA.specificity;
|
|
705
|
+
const specificityB = declarationB.specificity;
|
|
604
706
|
if (specificityA.a !== specificityB.a) {
|
|
605
707
|
return specificityA.a - specificityB.a;
|
|
606
708
|
}
|
|
@@ -615,8 +717,8 @@ function compareDeclarations(declarationA, declarationB, docContext) {
|
|
|
615
717
|
}
|
|
616
718
|
return 0;
|
|
617
719
|
} else {
|
|
618
|
-
const specificityA = declarationA.
|
|
619
|
-
const specificityB = declarationB.
|
|
720
|
+
const specificityA = declarationA.specificity;
|
|
721
|
+
const specificityB = declarationB.specificity;
|
|
620
722
|
if (specificityA.a !== specificityB.a) {
|
|
621
723
|
return specificityA.a - specificityB.a;
|
|
622
724
|
}
|
|
@@ -718,7 +820,7 @@ function removeLosingDeclarations(winningDeclarations, docContext) {
|
|
|
718
820
|
if (declaration.data.type === DECLARATION_TYPE) {
|
|
719
821
|
allDeclarations.set(declaration, declarations);
|
|
720
822
|
const { property, value } = declaration.data;
|
|
721
|
-
if (property && property.startsWith(CUSTOM_PROPERTY_PREFIX) || (value && value.type === RAW_TYPE)) {
|
|
823
|
+
if (property && property.startsWith(CUSTOM_PROPERTY_PREFIX) || (value && value.type === RAW_TYPE) || cssRule.hasUnqueryableSelector) {
|
|
722
824
|
protectedDeclarations.add(declaration);
|
|
723
825
|
}
|
|
724
826
|
}
|
|
@@ -822,36 +924,6 @@ function combineSelectors(parentSelectorText, childSelectorText) {
|
|
|
822
924
|
return cssTree.generate(combinedSelector);
|
|
823
925
|
}
|
|
824
926
|
|
|
825
|
-
function computeEffectiveSpecificity(selectorData, element, docContext) {
|
|
826
|
-
const baseSpecificity = selectorData.specificity;
|
|
827
|
-
let effectiveSpecificity = { a: baseSpecificity.a, b: baseSpecificity.b, c: baseSpecificity.c };
|
|
828
|
-
const includeLists = selectorData && selectorData.scopeIncludeLists && selectorData.scopeIncludeLists.length ? selectorData.scopeIncludeLists[selectorData.scopeIncludeLists.length - 1] : [];
|
|
829
|
-
if (includeLists && includeLists.length) {
|
|
830
|
-
for (const includeSelector of includeLists) {
|
|
831
|
-
const roots = getScopeRoots(includeSelector, docContext);
|
|
832
|
-
if (roots.some(root => root.contains(element))) {
|
|
833
|
-
const includeSpecificity = getIncludeSpecificity(includeSelector, docContext);
|
|
834
|
-
effectiveSpecificity = {
|
|
835
|
-
a: effectiveSpecificity.a + includeSpecificity.a,
|
|
836
|
-
b: effectiveSpecificity.b + includeSpecificity.b,
|
|
837
|
-
c: effectiveSpecificity.c + includeSpecificity.c
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return effectiveSpecificity;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
function getIncludeSpecificity(includeSelector, docContext) {
|
|
846
|
-
let specificity = docContext.scopeSpecificities.get(includeSelector);
|
|
847
|
-
if (!specificity) {
|
|
848
|
-
const selector = parseCss(includeSelector);
|
|
849
|
-
specificity = computeMaxSpecificity(selector, []);
|
|
850
|
-
docContext.scopeSpecificities.set(includeSelector, specificity);
|
|
851
|
-
}
|
|
852
|
-
return specificity;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
927
|
function hasChildNodes(node) {
|
|
856
928
|
return Boolean(node && node.children && node.children.head);
|
|
857
929
|
}
|
|
@@ -883,42 +955,39 @@ function parseCss(text, context = SELECTOR_CONTEXT) {
|
|
|
883
955
|
return cssTree.parse(text, options);
|
|
884
956
|
}
|
|
885
957
|
|
|
886
|
-
function querySelectorAll(root,
|
|
887
|
-
if (
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (DEBUG) {
|
|
902
|
-
// eslint-disable-next-line no-console
|
|
903
|
-
console.warn(QSA_ERROR_MESSAGE, selector, root.tagName ? root.tagName : EMPTY_STRING);
|
|
904
|
-
}
|
|
905
|
-
rootCache.set(selector, []);
|
|
906
|
-
return [];
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
} else {
|
|
910
|
-
try {
|
|
911
|
-
return Array.from(root.querySelectorAll(selector));
|
|
912
|
-
} catch {
|
|
913
|
-
if (DEBUG) {
|
|
914
|
-
// eslint-disable-next-line no-console
|
|
915
|
-
console.warn(QSA_ERROR_MESSAGE, selector);
|
|
916
|
-
}
|
|
917
|
-
return [];
|
|
958
|
+
function querySelectorAll(root, selectorText) {
|
|
959
|
+
if (!root) {
|
|
960
|
+
return [];
|
|
961
|
+
}
|
|
962
|
+
const isDocumentNode = root.nodeType === 9 || root.nodeType === 11;
|
|
963
|
+
const hasScopePseudo = Boolean(selectorText && selectorText.indexOf(":scope") !== -1);
|
|
964
|
+
if (isDocumentNode && hasScopePseudo) {
|
|
965
|
+
return matchDocumentScopeSelector(root, selectorText);
|
|
966
|
+
}
|
|
967
|
+
try {
|
|
968
|
+
return Array.from(root.querySelectorAll(selectorText));
|
|
969
|
+
} catch {
|
|
970
|
+
if (DEBUG) {
|
|
971
|
+
// eslint-disable-next-line no-console
|
|
972
|
+
console.warn(QSA_ERROR_MESSAGE, selectorText, root.nodeType === 1 && root.tagName ? root.tagName : EMPTY_STRING);
|
|
918
973
|
}
|
|
974
|
+
const traversalRoots = getTraversalRoots(root);
|
|
975
|
+
return matchByTraversal(traversalRoots, selectorText, false);
|
|
919
976
|
}
|
|
920
977
|
}
|
|
921
978
|
|
|
979
|
+
function matchDocumentScopeSelector(root, selectorText) {
|
|
980
|
+
const traversalRoots = getTraversalRoots(root);
|
|
981
|
+
if (!traversalRoots.length) {
|
|
982
|
+
return [];
|
|
983
|
+
}
|
|
984
|
+
const matchedNodes = new Set();
|
|
985
|
+
traversalRoots.forEach(scopeRoot => {
|
|
986
|
+
matchSelectorWithinRoot(scopeRoot, selectorText).forEach(node => matchedNodes.add(node));
|
|
987
|
+
});
|
|
988
|
+
return Array.from(matchedNodes);
|
|
989
|
+
}
|
|
990
|
+
|
|
922
991
|
function getInlineStyleDeclarations(element) {
|
|
923
992
|
const style = element.getAttribute(STYLE_ATTRIBUTE_NAME);
|
|
924
993
|
if (style) {
|
|
@@ -933,7 +1002,7 @@ function getInlineStyleDeclarations(element) {
|
|
|
933
1002
|
if (node.data.type === DECLARATION_TYPE) {
|
|
934
1003
|
declarations.push({
|
|
935
1004
|
declaration: node,
|
|
936
|
-
|
|
1005
|
+
specificity: { a: 1, b: 0, c: 0 },
|
|
937
1006
|
isInline: true
|
|
938
1007
|
});
|
|
939
1008
|
}
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* notice and a URL through which recipients can access the Corresponding
|
|
21
21
|
* Source.
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
import * as cssTree from "./../vendor/css-tree.js";
|
|
23
|
+
import * as cssTree from "../vendor/css-tree.js";
|
|
25
24
|
|
|
26
25
|
const CANONICAL_PSEUDO_ELEMENT_NAMES = new Set(["after", "before", "first-letter", "first-line", "placeholder", "selection", "part", "marker"]);
|
|
27
26
|
|
|
@@ -34,52 +33,13 @@ function parsePrelude(prelude) {
|
|
|
34
33
|
return { include: [], exclude: [] };
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const preludeText = cssTree.generate(prelude).trim();
|
|
41
|
-
if (!preludeText) return { include: [], exclude: [] };
|
|
42
|
-
|
|
43
|
-
// Split on top-level ' to ' (case-insensitive) — join remaining parts if multiple 'to' appear
|
|
44
|
-
const parts = preludeText.split(/\s+to\s+/i);
|
|
45
|
-
const includeText = parts[0].trim();
|
|
46
|
-
const excludeText = parts.length > 1 ? parts.slice(1).join(" to ").trim() : "";
|
|
47
|
-
|
|
48
|
-
function parseSelectorList(text) {
|
|
49
|
-
if (!text) return [];
|
|
50
|
-
// Strip balanced outer parentheses that css-tree may produce in generated preludes
|
|
51
|
-
function stripOuterParens(s) {
|
|
52
|
-
let str = s.trim();
|
|
53
|
-
while (str.length >= 2 && str[0] === "(" && str[str.length - 1] === ")") {
|
|
54
|
-
// ensure they are balanced pairs for the whole string
|
|
55
|
-
let depth = 0;
|
|
56
|
-
let balanced = true;
|
|
57
|
-
for (let i = 0; i < str.length; i++) {
|
|
58
|
-
if (str[i] === "(") depth++;
|
|
59
|
-
else if (str[i] === ")") depth--;
|
|
60
|
-
if (depth === 0 && i < str.length - 1) { balanced = false; break; }
|
|
61
|
-
}
|
|
62
|
-
if (!balanced) break;
|
|
63
|
-
str = str.substring(1, str.length - 1).trim();
|
|
64
|
-
}
|
|
65
|
-
return str;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const cleaned = stripOuterParens(text);
|
|
69
|
-
// css-tree expects a selectorList context
|
|
70
|
-
const ast = cssTree.parse(cleaned, { context: "selectorList" });
|
|
71
|
-
const selectors = [];
|
|
72
|
-
if (ast && ast.children) {
|
|
73
|
-
for (let node = ast.children.head; node; node = node.next) {
|
|
74
|
-
const sel = node.data;
|
|
75
|
-
selectors.push({ ast: sel, text: cssTree.generate(sel) });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return selectors;
|
|
36
|
+
const scopeNode = findScopeNode(prelude);
|
|
37
|
+
if (!scopeNode) {
|
|
38
|
+
return { include: [], exclude: [] };
|
|
79
39
|
}
|
|
80
40
|
|
|
81
|
-
const include =
|
|
82
|
-
const exclude =
|
|
41
|
+
const include = extractSelectorList(scopeNode.root);
|
|
42
|
+
const exclude = extractSelectorList(scopeNode.limit);
|
|
83
43
|
|
|
84
44
|
// Validate: pseudo-elements are not allowed in scope start/end selectors
|
|
85
45
|
function containsPseudoElement(selectorAst) {
|
|
@@ -105,15 +65,39 @@ function parsePrelude(prelude) {
|
|
|
105
65
|
}
|
|
106
66
|
|
|
107
67
|
for (const s of include) {
|
|
108
|
-
if (containsPseudoElement(s.
|
|
68
|
+
if (containsPseudoElement(s.data)) {
|
|
109
69
|
throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-start)");
|
|
110
70
|
}
|
|
111
71
|
}
|
|
112
72
|
for (const s of exclude) {
|
|
113
|
-
if (containsPseudoElement(s.
|
|
73
|
+
if (containsPseudoElement(s.data)) {
|
|
114
74
|
throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-end)");
|
|
115
75
|
}
|
|
116
76
|
}
|
|
117
77
|
|
|
118
78
|
return { include, exclude };
|
|
119
79
|
}
|
|
80
|
+
|
|
81
|
+
function findScopeNode(prelude) {
|
|
82
|
+
if (!prelude || !prelude.children) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
for (let node = prelude.children.head; node; node = node.next) {
|
|
86
|
+
if (node.data && node.data.type === "Scope") {
|
|
87
|
+
return node.data;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractSelectorList(selectorList) {
|
|
94
|
+
if (!selectorList || !selectorList.children) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const selectors = [];
|
|
98
|
+
for (let node = selectorList.children.head; node; node = node.next) {
|
|
99
|
+
const selector = node.data;
|
|
100
|
+
selectors.push({ data: selector, text: cssTree.generate(selector) });
|
|
101
|
+
}
|
|
102
|
+
return selectors;
|
|
103
|
+
}
|
|
@@ -23,70 +23,35 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import * as cssTree from "./../vendor/css-tree.js";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
26
|
+
|
|
27
|
+
const TREE_STRUCTURAL_PSEUDO_CLASSES = [
|
|
28
|
+
"root",
|
|
29
|
+
"empty",
|
|
30
|
+
"first-child",
|
|
31
|
+
"last-child",
|
|
32
|
+
"only-child",
|
|
33
|
+
"first-of-type",
|
|
34
|
+
"last-of-type",
|
|
35
|
+
"only-of-type"
|
|
36
|
+
];
|
|
37
|
+
const TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES = [
|
|
38
|
+
"nth-child",
|
|
39
|
+
"nth-last-child",
|
|
36
40
|
"heading",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"seeking",
|
|
46
|
-
"stalled",
|
|
47
|
-
"volume-locked",
|
|
48
|
-
"after",
|
|
49
|
-
"before",
|
|
50
|
-
"visited",
|
|
51
|
-
"link",
|
|
52
|
-
"any-link",
|
|
53
|
-
"local-link",
|
|
54
|
-
"target",
|
|
55
|
-
"scope",
|
|
56
|
-
"hover",
|
|
57
|
-
"active",
|
|
58
|
-
"focus",
|
|
59
|
-
"focus-within",
|
|
60
|
-
"focus-visible",
|
|
61
|
-
"target-current",
|
|
62
|
-
"enabled",
|
|
63
|
-
"disabled",
|
|
64
|
-
"read-only",
|
|
65
|
-
"read-write",
|
|
66
|
-
"placeholder-shown",
|
|
67
|
-
"autofill",
|
|
68
|
-
"default",
|
|
69
|
-
"checked",
|
|
70
|
-
"indeterminate",
|
|
71
|
-
"blank",
|
|
72
|
-
"valid",
|
|
73
|
-
"invalid",
|
|
74
|
-
"in-range",
|
|
75
|
-
"out-of-range",
|
|
76
|
-
"required",
|
|
77
|
-
"optional",
|
|
78
|
-
"user-valid",
|
|
79
|
-
"user-invalid"
|
|
41
|
+
"nth-of-type",
|
|
42
|
+
"nth-last-of-type"
|
|
43
|
+
];
|
|
44
|
+
const FUNCTIONAL_PSEUDO_CLASSES = [
|
|
45
|
+
"not",
|
|
46
|
+
"is",
|
|
47
|
+
"where",
|
|
48
|
+
"has"
|
|
80
49
|
];
|
|
81
|
-
|
|
82
50
|
export {
|
|
83
|
-
|
|
51
|
+
matchUnqueryablePseudoClass,
|
|
84
52
|
sanitizeSelector,
|
|
85
53
|
};
|
|
86
|
-
|
|
87
|
-
* Sanitize a selector AST into a QSA-safe selector string.
|
|
88
|
-
* Optional `ancestors` array may be provided to expand nesting selectors (`&`).
|
|
89
|
-
*/
|
|
54
|
+
|
|
90
55
|
function sanitizeSelector(selector, ancestors, docContext) {
|
|
91
56
|
if (!docContext.normalizedSelectorText) {
|
|
92
57
|
docContext.normalizedSelectorText = new WeakMap();
|
|
@@ -103,6 +68,7 @@ function sanitizeSelector(selector, ancestors, docContext) {
|
|
|
103
68
|
docContext.normalizedSelectorText.set(selector, normalized);
|
|
104
69
|
return normalized;
|
|
105
70
|
}
|
|
71
|
+
|
|
106
72
|
function normalizeSelectorNode(selector, ancestors) {
|
|
107
73
|
let current = selector.children.head;
|
|
108
74
|
while (current) {
|
|
@@ -128,9 +94,8 @@ function normalizeSelectorNode(selector, ancestors) {
|
|
|
128
94
|
} else if (childNode.type === "PseudoElementSelector") {
|
|
129
95
|
selector.children.remove(current);
|
|
130
96
|
} else if (childNode.type === "PseudoClassSelector") {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
selector.children.remove(current);
|
|
97
|
+
if (matchUnqueryablePseudoClass(childNode)) {
|
|
98
|
+
removeNode(selector.children, current);
|
|
134
99
|
}
|
|
135
100
|
} else if (childNode.type === "Selector") {
|
|
136
101
|
normalizeSelectorNode(childNode, ancestors);
|
|
@@ -138,3 +103,19 @@ function normalizeSelectorNode(selector, ancestors) {
|
|
|
138
103
|
current = next;
|
|
139
104
|
}
|
|
140
105
|
}
|
|
106
|
+
|
|
107
|
+
function removeNode(list, item) {
|
|
108
|
+
if (item.prev == null || item.prev.data.type == "Combinator" || item.prev.data.type == "WhiteSpace") {
|
|
109
|
+
list.replace(item, cssTree.parse("*", { context: "selector" }).children.head);
|
|
110
|
+
} else {
|
|
111
|
+
list.remove(item);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function matchUnqueryablePseudoClass(pseudoClass) {
|
|
116
|
+
const name = pseudoClass.name.toLowerCase();
|
|
117
|
+
return pseudoClass.children ? (
|
|
118
|
+
!TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES.includes(name) &&
|
|
119
|
+
!FUNCTIONAL_PSEUDO_CLASSES.includes(name)
|
|
120
|
+
) : !TREE_STRUCTURAL_PSEUDO_CLASSES.includes(name);
|
|
121
|
+
}
|
|
@@ -139,7 +139,7 @@ function getMaxSpecificityFromList(selectorList) {
|
|
|
139
139
|
return maxSpec;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function computeMaxSpecificity(selector, ancestorsSelectors) {
|
|
142
|
+
function computeMaxSpecificity(selector, ancestorsSelectors, scopeStack) {
|
|
143
143
|
// If no ancestors provided, keep existing behavior
|
|
144
144
|
if (!ancestorsSelectors || !ancestorsSelectors.length) {
|
|
145
145
|
let maxSpecificity = { a: 0, b: 0, c: 0 };
|
|
@@ -162,6 +162,9 @@ function computeMaxSpecificity(selector, ancestorsSelectors) {
|
|
|
162
162
|
stack.pop();
|
|
163
163
|
}
|
|
164
164
|
});
|
|
165
|
+
if (scopeStack && scopeStack.length) {
|
|
166
|
+
maxSpecificity.b++;
|
|
167
|
+
}
|
|
165
168
|
return maxSpecificity;
|
|
166
169
|
}
|
|
167
170
|
|
package/package.json
CHANGED
|
@@ -133,7 +133,7 @@ async function process(pageData, options, lastModDate = new Date()) {
|
|
|
133
133
|
const startOffset = zipDataWriter.offset;
|
|
134
134
|
pageData.url = options.url;
|
|
135
135
|
pageData.archiveTime = (new Date()).toISOString();
|
|
136
|
-
await addPageResources(zipWriter, pageData, { password: options.password }, options.createRootDirectory ? String(Date.now()) + "_" + (options.tabId || 0) + "/" : "", options.url);
|
|
136
|
+
await addPageResources(zipWriter, pageData, { password: options.password, disableCompression: options.disableCompression }, options.createRootDirectory ? String(Date.now()) + "_" + (options.tabId || 0) + "/" : "", options.url);
|
|
137
137
|
const data = await zipWriter.close(null, { preventClose: true });
|
|
138
138
|
if (options.selfExtractingArchive) {
|
|
139
139
|
const insertionsCRLF = [];
|
|
@@ -393,25 +393,25 @@ async function addPageResources(zipWriter, pageData, options, prefixName, url) {
|
|
|
393
393
|
}, null, 2);
|
|
394
394
|
await Promise.all([
|
|
395
395
|
Promise.all([
|
|
396
|
-
addFile(zipWriter, prefixName, { name: "index.html", extension: ".html", content: pageData.content, url, password: options.password }),
|
|
397
|
-
addFile(zipWriter, prefixName, { name: "manifest.json", extension: ".json", content: jsonContent, password: options.password })
|
|
396
|
+
addFile(zipWriter, prefixName, { name: "index.html", extension: ".html", content: pageData.content, url, password: options.password }, options.disableCompression),
|
|
397
|
+
addFile(zipWriter, prefixName, { name: "manifest.json", extension: ".json", content: jsonContent, password: options.password }, options.disableCompression)
|
|
398
398
|
]),
|
|
399
399
|
Promise.all(Object.keys(pageData.resources).map(async resourceType =>
|
|
400
400
|
Promise.all(pageData.resources[resourceType].map(data => {
|
|
401
401
|
if (resourceType == "frames") {
|
|
402
402
|
return addPageResources(zipWriter, data, options, prefixName + data.name, data.url);
|
|
403
403
|
} else {
|
|
404
|
-
return addFile(zipWriter, prefixName, data,
|
|
404
|
+
return addFile(zipWriter, prefixName, data, options.disableCompression);
|
|
405
405
|
}
|
|
406
406
|
}))
|
|
407
407
|
))
|
|
408
408
|
]);
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
async function addFile(zipWriter, prefixName, data) {
|
|
411
|
+
async function addFile(zipWriter, prefixName, data, disableCompresson) {
|
|
412
412
|
const dataReader = typeof data.content == "string" ? new TextReader(data.content) : new BlobReader(new Blob([new Uint8Array(data.content)]));
|
|
413
413
|
const options = { comment: data.url && data.url.startsWith("data:") ? "data:" : data.url, password: data.password, bufferedWrite: true };
|
|
414
|
-
if (NO_COMPRESSION_EXTENSIONS.includes(data.extension)) {
|
|
414
|
+
if (NO_COMPRESSION_EXTENSIONS.includes(data.extension) || disableCompresson) {
|
|
415
415
|
options.level = 0;
|
|
416
416
|
}
|
|
417
417
|
await zipWriter.add(prefixName + data.name, dataReader, options);
|
package/single-file-infobar.js
CHANGED
|
@@ -29,17 +29,21 @@ import { appendInfobar, refreshInfobarInfo, extractInfobarData } from "./core/in
|
|
|
29
29
|
|
|
30
30
|
const browser = globalThis.browser;
|
|
31
31
|
const MutationObserver = globalThis.MutationObserver;
|
|
32
|
+
let mutationObserver;
|
|
32
33
|
init();
|
|
33
34
|
|
|
34
35
|
function init() {
|
|
35
36
|
if (globalThis.window == globalThis.top) {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
document.addEventListener("single-file-display-infobar", displayIcon, false);
|
|
38
|
+
if (document.documentElement.getAttribute("data-sfz") == "" && !mutationObserver) {
|
|
39
|
+
mutationObserver = new MutationObserver(init).observe(document, { childList: true });
|
|
38
40
|
} else {
|
|
39
|
-
|
|
41
|
+
if (document.readyState == "loading") {
|
|
42
|
+
document.addEventListener("DOMContentLoaded", displayIcon, false);
|
|
43
|
+
} else {
|
|
44
|
+
displayIcon();
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
|
-
document.addEventListener("single-file-display-infobar", displayIcon, false);
|
|
42
|
-
new MutationObserver(init).observe(document, { childList: true });
|
|
43
47
|
}
|
|
44
48
|
if (globalThis.singlefile) {
|
|
45
49
|
globalThis.singlefile.infobar = {
|
package/single-file.js
CHANGED
|
@@ -116,6 +116,7 @@ async function getPageData(options = {}, initOptions, doc, win) {
|
|
|
116
116
|
url: options.url,
|
|
117
117
|
createRootDirectory: options.createRootDirectory,
|
|
118
118
|
selfExtractingArchive: options.selfExtractingArchive,
|
|
119
|
+
disableCompression: options.disableCompression,
|
|
119
120
|
extractDataFromPage: options.extractDataFromPage,
|
|
120
121
|
preventAppendedData: options.preventAppendedData,
|
|
121
122
|
insertCanonicalLink: options.insertCanonicalLink,
|