single-file-core 1.6.9 → 1.6.11

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.
@@ -23,9 +23,11 @@
23
23
  */
24
24
 
25
25
  import * as cssTree from "./../vendor/css-tree.js";
26
+ import { decodeName } from "./css-identifier.js";
26
27
 
27
28
  const TREE_STRUCTURAL_PSEUDO_CLASSES = [
28
29
  "root",
30
+ "scope",
29
31
  "empty",
30
32
  "first-child",
31
33
  "last-child",
@@ -47,6 +49,10 @@ const FUNCTIONAL_PSEUDO_CLASSES = [
47
49
  "where",
48
50
  "has"
49
51
  ];
52
+ const MATCHING_FUNCTIONAL_PSEUDO_CLASSES = [
53
+ "is",
54
+ "where"
55
+ ];
50
56
  const STATE_ATTRIBUTE_NAMES = [
51
57
  "open"
52
58
  ];
@@ -56,15 +62,15 @@ export {
56
62
  sanitizeSelector,
57
63
  };
58
64
 
59
- function sanitizeSelector(selector, ancestors, docContext) {
65
+ function sanitizeSelector(selector, docContext) {
60
66
  if (!docContext.normalizedSelectorText) {
61
67
  docContext.normalizedSelectorText = new WeakMap();
62
68
  }
63
69
  if (docContext.normalizedSelectorText.has(selector)) {
64
70
  return docContext.normalizedSelectorText.get(selector);
65
71
  }
66
- const ast = cssTree.parse(cssTree.generate(selector.data), { context: "selectorList" });
67
- normalizeSelectorNode(ast, ancestors);
72
+ const ast = copySelectorNode(selector.data);
73
+ normalizeSelectorNode(ast);
68
74
  let normalized = cssTree.generate(ast);
69
75
  if (!normalized || !normalized.trim()) {
70
76
  normalized = "*";
@@ -73,26 +79,19 @@ function sanitizeSelector(selector, ancestors, docContext) {
73
79
  return normalized;
74
80
  }
75
81
 
76
- function normalizeSelectorNode(selector, ancestors) {
82
+ function copySelectorNode(node) {
83
+ // A node with no children is a Raw one, which clone would hand back raw where the round-trip
84
+ // parses it into the selector list the rest of this module walks.
85
+ return node.children ? cssTree.clone(node) : cssTree.parse(cssTree.generate(node), { context: "selectorList" });
86
+ }
87
+
88
+ function normalizeSelectorNode(selector) {
77
89
  let current = selector.children.head;
78
90
  while (current) {
79
91
  const next = current.next;
80
92
  const childNode = current.data;
81
93
  if (childNode.type === "NestingSelector") {
82
- if (ancestors && ancestors.length) {
83
- const lastAncestor = ancestors[ancestors.length - 1];
84
- let ancestorAst = lastAncestor && lastAncestor.data ? lastAncestor.data : lastAncestor;
85
- if (ancestorAst && ancestorAst.type === "SelectorList" && ancestorAst.children && ancestorAst.children.tail) {
86
- ancestorAst = ancestorAst.children.tail.data;
87
- }
88
- if (ancestorAst && ancestorAst.children) {
89
- for (let a = ancestorAst.children.head; a; a = a.next) {
90
- const cloned = cssTree.clone(a.data);
91
- selector.children.insertData(cloned, current);
92
- }
93
- selector.children.remove(current);
94
- }
95
- }
94
+ selector.children.replace(current, cssTree.parse(":scope", { context: "selector" }).children.head);
96
95
  } else if (childNode.type === "TypeSelector" && typeof childNode.name === "string" && childNode.name.includes("|")) {
97
96
  childNode.name = childNode.name.substring(childNode.name.lastIndexOf("|") + 1);
98
97
  } else if (childNode.type === "PseudoElementSelector") {
@@ -100,13 +99,15 @@ function normalizeSelectorNode(selector, ancestors) {
100
99
  } else if (childNode.type === "PseudoClassSelector") {
101
100
  if (matchUnqueryablePseudoClass(childNode)) {
102
101
  removeNode(selector.children, current);
102
+ } else if (childNode.children && MATCHING_FUNCTIONAL_PSEUDO_CLASSES.includes(childNode.name.toLowerCase())) {
103
+ normalizeSelectorNode(childNode.children.head.data);
103
104
  }
104
105
  } else if (childNode.type === "AttributeSelector") {
105
106
  if (matchUnqueryableAttributeSelector(childNode)) {
106
107
  removeNode(selector.children, current);
107
108
  }
108
109
  } else if (childNode.type === "Selector") {
109
- normalizeSelectorNode(childNode, ancestors);
110
+ normalizeSelectorNode(childNode);
110
111
  }
111
112
  current = next;
112
113
  }
@@ -121,13 +122,16 @@ function removeNode(list, item) {
121
122
  }
122
123
 
123
124
  function matchUnqueryableAttributeSelector(attributeSelector) {
124
- return Boolean(attributeSelector.name) && STATE_ATTRIBUTE_NAMES.includes(attributeSelector.name.name.toLowerCase());
125
+ return Boolean(attributeSelector.name) && STATE_ATTRIBUTE_NAMES.includes(decodeName(attributeSelector.name.name));
125
126
  }
126
127
 
127
128
  function matchUnqueryablePseudoClass(pseudoClass) {
128
- const name = pseudoClass.name.toLowerCase();
129
+ // A functional pseudo-class is compared by spelling on purpose: css-tree recognises the known
130
+ // names by spelling too, so it parses the argument of an escaped one as Raw rather than as a
131
+ // selector list, and reading `:i\73 (…)` as `:is(…)` would hand the rest of the pass an argument
132
+ // it cannot walk. Leaving it unrecognised keeps it unqueryable, which is the safe answer.
129
133
  return pseudoClass.children ? (
130
- !TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES.includes(name) &&
131
- !FUNCTIONAL_PSEUDO_CLASSES.includes(name)
132
- ) : !TREE_STRUCTURAL_PSEUDO_CLASSES.includes(name);
134
+ !TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES.includes(pseudoClass.name.toLowerCase()) &&
135
+ !FUNCTIONAL_PSEUDO_CLASSES.includes(pseudoClass.name.toLowerCase())
136
+ ) : !TREE_STRUCTURAL_PSEUDO_CLASSES.includes(decodeName(pseudoClass.name));
133
137
  }
@@ -91,12 +91,7 @@ function computeSpecificity(selector, specificity = { a: 0, b: 0, c: 0 }) {
91
91
  }
92
92
 
93
93
  // Regular pseudo-classes contribute to 'b'
94
- // Exception: :scope is treated as a type selector (contributes to 'c')
95
- if (pseudoName === "scope") {
96
- specificity.c++;
97
- } else {
98
- specificity.b++;
99
- }
94
+ specificity.b++;
100
95
  break;
101
96
  }
102
97
 
@@ -139,75 +134,25 @@ function getMaxSpecificityFromList(selectorList) {
139
134
  return maxSpec;
140
135
  }
141
136
 
142
- function computeMaxSpecificity(selector, ancestorsSelectors, scopeStack) {
143
- // If no ancestors provided, keep existing behavior
144
- if (!ancestorsSelectors || !ancestorsSelectors.length) {
145
- let maxSpecificity = { a: 0, b: 0, c: 0 };
146
- const stack = [];
147
- cssTree.walk(selector, {
148
- enter(node) {
149
- stack.push(node);
150
- if (node.type === "Selector") {
151
- const insideWhere = stack.some(n => n.type === "PseudoClassSelector" && n.name === "where");
152
- if (insideWhere) return;
153
- const specificity = computeSpecificity(node);
154
- if (specificity.a > maxSpecificity.a ||
155
- (specificity.a === maxSpecificity.a && specificity.b > maxSpecificity.b) ||
156
- (specificity.a === maxSpecificity.a && specificity.b === maxSpecificity.b && specificity.c > maxSpecificity.c)) {
157
- maxSpecificity = specificity;
158
- }
159
- }
160
- },
161
- leave() {
162
- stack.pop();
163
- }
164
- });
165
- if (scopeStack && scopeStack.length) {
166
- maxSpecificity.b++;
167
- }
168
- return maxSpecificity;
169
- }
170
-
171
- // When ancestors are provided, compute specificity for every expanded selector
172
- // Build context strings for ancestors (similar to combineWithAncestors behavior)
173
- const childText = cssTree.generate(selector);
174
- let contexts = [""];
175
- ancestorsSelectors.forEach(selectorList => {
176
- if (!selectorList || !selectorList.children || !selectorList.children.size) return;
177
- const parentSelectors = selectorList.children.toArray();
178
- const nextContexts = [];
179
- contexts.forEach(context => parentSelectors.forEach(parentSelector => {
180
- const parentText = cssTree.generate(parentSelector);
181
- const combined = context ? context + " " + parentText : parentText;
182
- if (!nextContexts.includes(combined)) nextContexts.push(combined);
183
- }));
184
- if (nextContexts.length) contexts = nextContexts;
185
- });
186
-
187
- function combineStrings(context, child) {
188
- if (!context) return child;
189
- if (!child) return context;
190
- if (child.indexOf("&") !== -1) return child.split("&").join(context);
191
- return context + " " + child;
192
- }
193
-
137
+ function computeMaxSpecificity(selector) {
194
138
  let maxSpecificity = { a: 0, b: 0, c: 0 };
195
- const seen = new Set();
196
- contexts.forEach(context => {
197
- const full = combineStrings(context, childText);
198
- if (seen.has(full)) return;
199
- seen.add(full);
200
- try {
201
- const parsed = cssTree.parse(full, { context: "selectorList" });
202
- // reuse original logic to compute max specificity over parsed AST
203
- const spec = computeMaxSpecificity(parsed);
204
- if (spec.a > maxSpecificity.a ||
205
- (spec.a === maxSpecificity.a && spec.b > maxSpecificity.b) ||
206
- (spec.a === maxSpecificity.a && spec.b === maxSpecificity.b && spec.c > maxSpecificity.c)) {
207
- maxSpecificity = spec;
139
+ const stack = [];
140
+ cssTree.walk(selector, {
141
+ enter(node) {
142
+ stack.push(node);
143
+ if (node.type === "Selector") {
144
+ const insideWhere = stack.some(n => n.type === "PseudoClassSelector" && n.name.toLowerCase() === "where");
145
+ if (insideWhere) return;
146
+ const specificity = computeSpecificity(node);
147
+ if (specificity.a > maxSpecificity.a ||
148
+ (specificity.a === maxSpecificity.a && specificity.b > maxSpecificity.b) ||
149
+ (specificity.a === maxSpecificity.a && specificity.b === maxSpecificity.b && specificity.c > maxSpecificity.c)) {
150
+ maxSpecificity = specificity;
151
+ }
208
152
  }
209
- } catch {
210
- // ignore parse errors and continue
153
+ },
154
+ leave() {
155
+ stack.pop();
211
156
  }
212
157
  });
213
158
  return maxSpecificity;
@@ -17336,6 +17336,7 @@ function getFilteredOptions(options) {
17336
17336
  delete filteredOptions.stylesheets;
17337
17337
  delete filteredOptions.images;
17338
17338
  delete filteredOptions.posters;
17339
+ delete filteredOptions.generatedDataURIs;
17339
17340
  delete filteredOptions.videos;
17340
17341
  delete filteredOptions.shadowRoots;
17341
17342
  delete filteredOptions.referrer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",