single-file-core 1.5.80 → 1.5.82
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.
|
@@ -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,36 @@ 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 { value } = declaration.data;
|
|
486
|
+
const isRawValue = value.type === RAW_TYPE;
|
|
487
|
+
const hasValueChildNodes = hasChildNodes(value) || value.type === RAW_TYPE;
|
|
488
|
+
const isSingleValue = value.type === VALUE_TYPE &&
|
|
489
|
+
hasValueChildNodes &&
|
|
490
|
+
value.children.length == 1 &&
|
|
491
|
+
value.children.head.data.name;
|
|
492
|
+
const isVendorValue = isSingleValue && value.children.head.data.name.startsWith(VENDOR_PREFIX);
|
|
493
|
+
const isInvalidValue = isSingleValue && INVALID_CSS_ESCAPE_TEST.test(value.children.head.data.name);
|
|
494
|
+
if (hasValueChildNodes && !isRawValue && !isVendorValue && !isInvalidValue) {
|
|
495
|
+
allDeclarations.push({
|
|
496
|
+
declaration,
|
|
497
|
+
selector,
|
|
498
|
+
specificity,
|
|
499
|
+
isInline
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
446
503
|
}
|
|
447
504
|
|
|
448
505
|
function getConditionalStackForSelector(selector, docContext) {
|
|
@@ -456,132 +513,169 @@ function getConditionalStackForSelector(selector, docContext) {
|
|
|
456
513
|
return conditionalStack;
|
|
457
514
|
}
|
|
458
515
|
|
|
459
|
-
function matchElements(selector, ancestorsSelectors, docContext) {
|
|
516
|
+
function matchElements(selector, ancestorsSelectors, scopeStack, docContext) {
|
|
460
517
|
const selectorText = createSelectorText(selector, ancestorsSelectors, docContext);
|
|
461
|
-
const
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
518
|
+
const cacheKey = createScopeCacheKey(selectorText, scopeStack);
|
|
519
|
+
const cachedNodes = docContext.matchedSelectors.get(cacheKey);
|
|
520
|
+
if (cachedNodes) {
|
|
521
|
+
return cachedNodes;
|
|
522
|
+
}
|
|
523
|
+
let nodes;
|
|
524
|
+
if (scopeStack && scopeStack.length) {
|
|
525
|
+
nodes = matchElementsInScope(selectorText, scopeStack);
|
|
526
|
+
nodes = filterElementsByScopes(nodes, scopeStack);
|
|
467
527
|
} 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
|
-
}
|
|
528
|
+
nodes = querySelectorAll(docContext.doc, selectorText);
|
|
475
529
|
}
|
|
530
|
+
docContext.matchedSelectors.set(cacheKey, nodes);
|
|
531
|
+
return nodes;
|
|
476
532
|
}
|
|
477
533
|
|
|
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);
|
|
534
|
+
function createScopeCacheKey(selectorText, scopeStack) {
|
|
535
|
+
if (!scopeStack || !scopeStack.length) {
|
|
536
|
+
return selectorText;
|
|
484
537
|
}
|
|
485
|
-
|
|
486
|
-
|
|
538
|
+
const signature = scopeStack.map(scope => scope.id).join(CONTEXT_KEY_SEPARATOR);
|
|
539
|
+
return `${selectorText}${CONTEXT_KEY_SEPARATOR}${signature}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function matchElementsInScope(selectorText, scopeStack) {
|
|
543
|
+
const currentScope = scopeStack[scopeStack.length - 1];
|
|
544
|
+
const roots = Array.from(currentScope.rootElements);
|
|
545
|
+
if (!roots.length) {
|
|
546
|
+
return [];
|
|
487
547
|
}
|
|
488
|
-
|
|
548
|
+
const matchedNodes = new Set();
|
|
549
|
+
roots.forEach(root => {
|
|
550
|
+
matchSelectorWithinRoot(root, selectorText).forEach(node => matchedNodes.add(node));
|
|
551
|
+
});
|
|
552
|
+
return Array.from(matchedNodes);
|
|
489
553
|
}
|
|
490
554
|
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
555
|
+
function matchSelectorWithinRoot(root, selectorText) {
|
|
556
|
+
const matchedNodes = new Set();
|
|
557
|
+
if (!root || root.nodeType !== 1) {
|
|
558
|
+
return matchedNodes;
|
|
559
|
+
}
|
|
560
|
+
if (matches(root, selectorText)) {
|
|
561
|
+
matchedNodes.add(root);
|
|
562
|
+
}
|
|
563
|
+
let nodes;
|
|
564
|
+
try {
|
|
565
|
+
nodes = root.querySelectorAll(selectorText);
|
|
566
|
+
} catch {
|
|
567
|
+
if (DEBUG) {
|
|
568
|
+
// eslint-disable-next-line no-console
|
|
569
|
+
console.error(QSA_ERROR_MESSAGE, { root, selectorText });
|
|
570
|
+
}
|
|
571
|
+
nodes = matchByTraversal([root], selectorText, true);
|
|
504
572
|
}
|
|
573
|
+
for (const node of nodes) {
|
|
574
|
+
matchedNodes.add(node);
|
|
575
|
+
}
|
|
576
|
+
return Array.from(matchedNodes);
|
|
505
577
|
}
|
|
506
578
|
|
|
507
|
-
function
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
579
|
+
function matches(element, selectorText) {
|
|
580
|
+
if (!element || element.nodeType !== 1) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
try {
|
|
584
|
+
return element.matches(selectorText);
|
|
585
|
+
} catch {
|
|
586
|
+
return false;
|
|
515
587
|
}
|
|
516
|
-
const matchedElements = Array.from(matchedSet);
|
|
517
|
-
docContext.matchedSelectors.set(cacheKey, matchedElements);
|
|
518
|
-
return matchedElements;
|
|
519
588
|
}
|
|
520
589
|
|
|
521
|
-
function
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
590
|
+
function matchByTraversal(roots, selectorText, skipFirst) {
|
|
591
|
+
const results = [];
|
|
592
|
+
const visited = new Set();
|
|
593
|
+
const stack = [];
|
|
594
|
+
roots.forEach(root => {
|
|
595
|
+
if (root && root.nodeType === 1) {
|
|
596
|
+
stack.push({ node: root, include: !skipFirst });
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
while (stack.length) {
|
|
600
|
+
const { node, include } = stack.pop();
|
|
601
|
+
if (!node || visited.has(node)) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
visited.add(node);
|
|
605
|
+
if (include && matches(node, selectorText)) {
|
|
606
|
+
results.push(node);
|
|
607
|
+
}
|
|
608
|
+
for (let child = node.firstElementChild; child; child = child.nextElementSibling) {
|
|
609
|
+
stack.push({ node: child, include: true });
|
|
532
610
|
}
|
|
533
611
|
}
|
|
612
|
+
return results;
|
|
534
613
|
}
|
|
535
614
|
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
615
|
+
function getTraversalRoots(root) {
|
|
616
|
+
if (!root) {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
if (root.nodeType === 9 || root.nodeType === 11) {
|
|
620
|
+
const roots = [];
|
|
621
|
+
if (root.documentElement) {
|
|
622
|
+
roots.push(root.documentElement);
|
|
623
|
+
}
|
|
624
|
+
if (root.body && (!roots.length || root.body !== roots[0])) {
|
|
625
|
+
roots.push(root.body);
|
|
541
626
|
}
|
|
627
|
+
if (!roots.length && root.children) {
|
|
628
|
+
roots.push(...Array.from(root.children).filter(node => node.nodeType === 1));
|
|
629
|
+
}
|
|
630
|
+
return roots;
|
|
542
631
|
}
|
|
543
|
-
return
|
|
632
|
+
return [root];
|
|
544
633
|
}
|
|
545
634
|
|
|
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
|
-
}
|
|
635
|
+
function filterElementsByScopes(elements, scopeStack) {
|
|
636
|
+
if (!scopeStack || !scopeStack.length) {
|
|
637
|
+
return elements;
|
|
638
|
+
}
|
|
639
|
+
return elements.filter(element => isElementWithinScopes(element, scopeStack));
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function isElementWithinScopes(element, scopeStack) {
|
|
643
|
+
if (!element) {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
for (let index = 0; index < scopeStack.length; index++) {
|
|
647
|
+
if (!isElementWithinScope(element, scopeStack[index])) {
|
|
648
|
+
return false;
|
|
565
649
|
}
|
|
566
650
|
}
|
|
567
|
-
return
|
|
651
|
+
return true;
|
|
568
652
|
}
|
|
569
653
|
|
|
570
|
-
function
|
|
571
|
-
let
|
|
572
|
-
|
|
573
|
-
|
|
654
|
+
function isElementWithinScope(element, scopeContext) {
|
|
655
|
+
let current = element;
|
|
656
|
+
while (current && current.nodeType === 1) {
|
|
657
|
+
if (scopeContext.stopElements && scopeContext.stopElements.has(current)) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
if (scopeContext.rootElements.has(current)) {
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
current = current.parentElement;
|
|
574
664
|
}
|
|
575
|
-
return
|
|
665
|
+
return false;
|
|
576
666
|
}
|
|
577
667
|
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
668
|
+
function createSelectorText(selector, ancestorsSelectors, docContext) {
|
|
669
|
+
let selectorText;
|
|
670
|
+
if (ancestorsSelectors && ancestorsSelectors.length) {
|
|
671
|
+
selectorText = combineSelectorWithAncestors(selector.data, ancestorsSelectors, docContext);
|
|
672
|
+
const combinedAst = parseCss(selectorText, SELECTOR_LIST_CONTEXT);
|
|
673
|
+
selectorText = sanitizeSelector({ data: combinedAst }, ancestorsSelectors, docContext);
|
|
674
|
+
}
|
|
675
|
+
if (!selectorText) {
|
|
676
|
+
selectorText = sanitizeSelector(selector, ancestorsSelectors, docContext);
|
|
583
677
|
}
|
|
584
|
-
return
|
|
678
|
+
return selectorText;
|
|
585
679
|
}
|
|
586
680
|
|
|
587
681
|
function compareDeclarations(declarationA, declarationB, docContext) {
|
|
@@ -599,8 +693,8 @@ function compareDeclarations(declarationA, declarationB, docContext) {
|
|
|
599
693
|
if (layerComparison !== 0) {
|
|
600
694
|
return importantA ? -layerComparison : layerComparison;
|
|
601
695
|
}
|
|
602
|
-
const specificityA = declarationA.
|
|
603
|
-
const specificityB = declarationB.
|
|
696
|
+
const specificityA = declarationA.specificity;
|
|
697
|
+
const specificityB = declarationB.specificity;
|
|
604
698
|
if (specificityA.a !== specificityB.a) {
|
|
605
699
|
return specificityA.a - specificityB.a;
|
|
606
700
|
}
|
|
@@ -615,8 +709,8 @@ function compareDeclarations(declarationA, declarationB, docContext) {
|
|
|
615
709
|
}
|
|
616
710
|
return 0;
|
|
617
711
|
} else {
|
|
618
|
-
const specificityA = declarationA.
|
|
619
|
-
const specificityB = declarationB.
|
|
712
|
+
const specificityA = declarationA.specificity;
|
|
713
|
+
const specificityB = declarationB.specificity;
|
|
620
714
|
if (specificityA.a !== specificityB.a) {
|
|
621
715
|
return specificityA.a - specificityB.a;
|
|
622
716
|
}
|
|
@@ -718,7 +812,7 @@ function removeLosingDeclarations(winningDeclarations, docContext) {
|
|
|
718
812
|
if (declaration.data.type === DECLARATION_TYPE) {
|
|
719
813
|
allDeclarations.set(declaration, declarations);
|
|
720
814
|
const { property, value } = declaration.data;
|
|
721
|
-
if (property && property.startsWith(CUSTOM_PROPERTY_PREFIX) || (value && value.type === RAW_TYPE)) {
|
|
815
|
+
if (property && property.startsWith(CUSTOM_PROPERTY_PREFIX) || (value && value.type === RAW_TYPE) || cssRule.hasUnqueryableSelector) {
|
|
722
816
|
protectedDeclarations.add(declaration);
|
|
723
817
|
}
|
|
724
818
|
}
|
|
@@ -822,36 +916,6 @@ function combineSelectors(parentSelectorText, childSelectorText) {
|
|
|
822
916
|
return cssTree.generate(combinedSelector);
|
|
823
917
|
}
|
|
824
918
|
|
|
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
919
|
function hasChildNodes(node) {
|
|
856
920
|
return Boolean(node && node.children && node.children.head);
|
|
857
921
|
}
|
|
@@ -883,42 +947,39 @@ function parseCss(text, context = SELECTOR_CONTEXT) {
|
|
|
883
947
|
return cssTree.parse(text, options);
|
|
884
948
|
}
|
|
885
949
|
|
|
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 [];
|
|
950
|
+
function querySelectorAll(root, selectorText) {
|
|
951
|
+
if (!root) {
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
const isDocumentNode = root.nodeType === 9 || root.nodeType === 11;
|
|
955
|
+
const hasScopePseudo = Boolean(selectorText && selectorText.indexOf(":scope") !== -1);
|
|
956
|
+
if (isDocumentNode && hasScopePseudo) {
|
|
957
|
+
return matchDocumentScopeSelector(root, selectorText);
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
return Array.from(root.querySelectorAll(selectorText));
|
|
961
|
+
} catch {
|
|
962
|
+
if (DEBUG) {
|
|
963
|
+
// eslint-disable-next-line no-console
|
|
964
|
+
console.warn(QSA_ERROR_MESSAGE, selectorText, root.nodeType === 1 && root.tagName ? root.tagName : EMPTY_STRING);
|
|
918
965
|
}
|
|
966
|
+
const traversalRoots = getTraversalRoots(root);
|
|
967
|
+
return matchByTraversal(traversalRoots, selectorText, false);
|
|
919
968
|
}
|
|
920
969
|
}
|
|
921
970
|
|
|
971
|
+
function matchDocumentScopeSelector(root, selectorText) {
|
|
972
|
+
const traversalRoots = getTraversalRoots(root);
|
|
973
|
+
if (!traversalRoots.length) {
|
|
974
|
+
return [];
|
|
975
|
+
}
|
|
976
|
+
const matchedNodes = new Set();
|
|
977
|
+
traversalRoots.forEach(scopeRoot => {
|
|
978
|
+
matchSelectorWithinRoot(scopeRoot, selectorText).forEach(node => matchedNodes.add(node));
|
|
979
|
+
});
|
|
980
|
+
return Array.from(matchedNodes);
|
|
981
|
+
}
|
|
982
|
+
|
|
922
983
|
function getInlineStyleDeclarations(element) {
|
|
923
984
|
const style = element.getAttribute(STYLE_ATTRIBUTE_NAME);
|
|
924
985
|
if (style) {
|
|
@@ -933,7 +994,7 @@ function getInlineStyleDeclarations(element) {
|
|
|
933
994
|
if (node.data.type === DECLARATION_TYPE) {
|
|
934
995
|
declarations.push({
|
|
935
996
|
declaration: node,
|
|
936
|
-
|
|
997
|
+
specificity: { a: 1, b: 0, c: 0 },
|
|
937
998
|
isInline: true
|
|
938
999
|
});
|
|
939
1000
|
}
|
|
@@ -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();
|
|
@@ -94,7 +59,7 @@ function sanitizeSelector(selector, ancestors, docContext) {
|
|
|
94
59
|
if (docContext.normalizedSelectorText.has(selector)) {
|
|
95
60
|
return docContext.normalizedSelectorText.get(selector);
|
|
96
61
|
}
|
|
97
|
-
const ast = cssTree.
|
|
62
|
+
const ast = cssTree.parse(cssTree.generate(selector.data), { context: "selectorList" });
|
|
98
63
|
normalizeSelectorNode(ast, ancestors);
|
|
99
64
|
let normalized = cssTree.generate(ast);
|
|
100
65
|
if (!normalized || !normalized.trim()) {
|
|
@@ -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,11 +94,28 @@ 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
|
}
|
|
100
|
+
} else if (childNode.type === "Selector") {
|
|
101
|
+
normalizeSelectorNode(childNode, ancestors);
|
|
135
102
|
}
|
|
136
103
|
current = next;
|
|
137
104
|
}
|
|
138
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
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 = {
|