single-file-core 1.6.8 → 1.6.10

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.
@@ -25,14 +25,17 @@ 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
27
  import { sanitizeSelector, matchUnqueryableAttributeSelector, matchUnqueryablePseudoClass } from "./css-selector-sanitizer.js";
28
+ import { ESCAPE_CHARACTER, decodeIdentifier, decodeName } from "./css-identifier.js";
28
29
 
29
30
  const DEBUG = false;
30
31
 
31
32
  const PSEUDO_ELEMENT_SYNONYMS = new Set(["after", "before", "first-letter", "first-line"]);
32
- const FUNCTIONAL_PSEUDO_CLASS_NAMES = new Set(["not", "is", "where", "has"]);
33
+ const FUNCTIONAL_PSEUDO_CLASS_NAMES = new Set(["not", "is", "where", "has", "nth-child", "nth-last-child"]);
33
34
  const MEDIA_AT_RULE_NAME = "media";
34
35
  const SUPPORTS_AT_RULE_NAME = "supports";
35
- const CONDITIONAL_AT_RULE_NAMES = new Set([MEDIA_AT_RULE_NAME, SUPPORTS_AT_RULE_NAME, "container"]);
36
+ const STARTING_STYLE_AT_RULE_NAME = "starting-style";
37
+ const CONDITIONAL_AT_RULE_NAMES = new Set([MEDIA_AT_RULE_NAME, SUPPORTS_AT_RULE_NAME, "container", STARTING_STYLE_AT_RULE_NAME]);
38
+ const UNCERTAIN_CONDITIONAL_AT_RULE_NAMES = new Set([MEDIA_AT_RULE_NAME, SUPPORTS_AT_RULE_NAME, STARTING_STYLE_AT_RULE_NAME]);
36
39
  const RULE_TYPE = "Rule";
37
40
  const AT_RULE_TYPE = "Atrule";
38
41
  const NESTING_SELECTOR_TYPE = "NestingSelector";
@@ -41,6 +44,7 @@ const ATTRIBUTE_SELECTOR_TYPE = "AttributeSelector";
41
44
  const DECLARATION_TYPE = "Declaration";
42
45
  const RAW_TYPE = "Raw";
43
46
  const VALUE_TYPE = "Value";
47
+ const IDENTIFIER_TYPE = "Identifier";
44
48
  const PSEUDO_ELEMENT_SELECTOR_TYPE = "PseudoElementSelector";
45
49
  const LAYER_NAME = "layer";
46
50
  const SCOPE_NAME = "scope";
@@ -48,34 +52,61 @@ const IMPORT_NAME = "import";
48
52
  const FONT_FACE_NAME = "font-face";
49
53
  const KEYFRAMES_NAME = "keyframes";
50
54
  const COMBINATOR_NAME = "Combinator";
55
+ const TYPE_SELECTOR_TYPE = "TypeSelector";
51
56
  const STYLE_ATTRIBUTE_NAME = "style";
52
57
  const SELECTOR_LIST_CONTEXT = "selectorList";
58
+ const SCOPE_PRELUDE_OPTIONS = { context: "atrulePrelude", atrule: SCOPE_NAME };
53
59
  const STYLESHEET_CONTEXT = "stylesheet";
54
60
  const SELECTOR_CONTEXT = "selector";
55
61
  const DECLARATION_LIST_CONTEXT = "declarationList";
56
62
  const UNKNOWN_PROPERTY_ERROR_NAME = "SyntaxReferenceError";
63
+ const VALUE_MISMATCH_ERROR_NAME = "SyntaxMatchError";
64
+ const VAR_FUNCTION_NAME = "var";
65
+ const FUNCTION_TYPE = "Function";
66
+ const VALIDITY_VALID = "valid";
67
+ const VALIDITY_UNKNOWN = "unknown";
68
+ const VALIDITY_INVALID = "invalid";
57
69
  const PARSE_CSS_ERROR_MESSAGE = "Failed to parse CSS";
58
70
  const QSA_ERROR_MESSAGE = "Failed to match selector";
59
71
  const PRELUDE_SEPARATOR = ",";
60
- const NESTING_SELECTOR = "&";
72
+ const DECLARATION_SEPARATOR = ":";
73
+ const IS_PSEUDO_CLASS_PREFIX = ":is(";
74
+ const IS_PSEUDO_CLASS_SUFFIX = ")";
75
+ const NO_ELEMENT_SELECTOR = ":not(*)";
61
76
  const SCOPE_PSEUDO_CLASS = ":scope";
77
+ const SCOPE_PSEUDO_CLASS_NAME = "scope";
78
+ const DESCENDANT_COMBINATOR = " ";
79
+ const NAMESPACE_SEPARATOR = "|";
80
+ const SELECTOR_SUPPORTS_PREFIX = "selector(";
81
+ const SELECTOR_SUPPORTS_SUFFIX = ")";
62
82
  const VENDOR_PREFIX = "-";
63
83
  const CUSTOM_PROPERTY_PREFIX = "--";
64
84
  const LAYER_NAME_SEPARATOR = ".";
65
- const DECLARATION_KEY_SEPARATOR = ":";
85
+ const LAYER_NAME_KEY_SEPARATOR = "\u0000";
66
86
  const CONTEXT_KEY_SEPARATOR = "|";
87
+ const REVERT_LAYER_KEYWORD = "revert-layer";
88
+ const REVERT_LAYER_TEST = /(^|[^-\w])revert-layer([^-\w]|$)/i;
89
+ const NAMESPACE_AT_RULE_NAME = "namespace";
90
+ const URL_TYPE = "Url";
91
+ const STRING_TYPE = "String";
92
+ const UNSCOPED_PROXIMITY = Infinity;
67
93
  const BLOCK_OPEN = "{";
68
94
  const BLOCK_CLOSE = "}";
69
95
  const EMPTY_STRING = "";
70
96
  const CSS_IMPORTANCE_NOT_IMPORTANT = 0;
71
97
  const CSS_IMPORTANCE_IMPORTANT = 1;
72
98
  const INVALID_CSS_ESCAPE_TEST = /\\(?![0-9a-fA-F]{1,6}\s|[^0-9a-zA-Z])/;
73
- const ANONYMOUS_LAYER_PLACEHOLDER = "\u0000";
99
+ const ANONYMOUS_LAYER_PREFIX = "\u0000";
100
+ const UNCERTAIN_LAYER_ORDER = null;
101
+ const UNDECLARED_LAYER_POSITION = Infinity;
74
102
 
75
103
  export {
76
104
  process,
77
105
  isUnsupportedPropertyValue,
78
- isUnsupportedVendorValue
106
+ getValueValidity,
107
+ VALIDITY_VALID,
108
+ VALIDITY_UNKNOWN,
109
+ VALIDITY_INVALID
79
110
  };
80
111
 
81
112
  function isUnsupportedPropertyValue(property, value) {
@@ -83,28 +114,41 @@ function isUnsupportedPropertyValue(property, value) {
83
114
  return Boolean(!match.matched && match.error && match.error.name !== UNKNOWN_PROPERTY_ERROR_NAME);
84
115
  }
85
116
 
86
- // A vendor-prefixed VALUE is dropped only when this browser cannot actually use it. Dropping every
87
- // one of them was the overreach: `display:-ms-flexbox` is genuinely dead here and worth removing,
88
- // but `display:-webkit-box` is alive and load-bearing — `-webkit-line-clamp` does nothing without
89
- // it, so a page keeps its clamp rules and silently stops clamping. On anandabazar.com that expanded
90
- // 80 clamped headlines by a line each and moved the page 150px, with nothing wrong-looking left in
91
- // the saved CSS. Four of the five sites in a fifteen-site sweep that use line-clamp were affected.
92
- function isUnsupportedVendorValue(property, name) {
93
- if (!name.startsWith(VENDOR_PREFIX)) {
94
- return false;
117
+ function getValueValidity(property, value) {
118
+ const singleNode = value.children.size === 1 ? value.children.head.data : null;
119
+ const name = singleNode && typeof singleNode.name === "string" ? singleNode.name : null;
120
+ if (name && INVALID_CSS_ESCAPE_TEST.test(name)) {
121
+ return VALIDITY_INVALID;
95
122
  }
96
- // unknown fails open, the same rule the unknown-property branch follows: a dropped valid
97
- // declaration breaks rendering, a kept invalid one is ignored. With no browser to ask, keep.
98
- if (!globalThis.CSS || !globalThis.CSS.supports) {
99
- return false;
123
+ const isVendorValue = Boolean(name && name.startsWith(VENDOR_PREFIX));
124
+ if (globalThis.CSS && globalThis.CSS.supports) {
125
+ let supported;
126
+ try {
127
+ supported = globalThis.CSS.supports(property, cssTree.generate(value));
128
+ } catch {
129
+ return VALIDITY_UNKNOWN;
130
+ }
131
+ if (supported) {
132
+ return VALIDITY_VALID;
133
+ }
134
+ return isVendorValue ? VALIDITY_INVALID : VALIDITY_UNKNOWN;
100
135
  }
136
+ if (cssTree.find(value, node => node.type === FUNCTION_TYPE && decodeName(node.name) === VAR_FUNCTION_NAME)) {
137
+ return VALIDITY_VALID;
138
+ }
139
+ let match;
101
140
  try {
102
- return !globalThis.CSS.supports(property, name);
103
- // eslint-disable-next-line no-unused-vars
104
- } catch (error) {
105
- // a value CSS.supports will not even take as an argument tells us nothing either way
106
- return false;
141
+ match = cssTree.lexer.matchProperty(property, value);
142
+ } catch {
143
+ return VALIDITY_UNKNOWN;
144
+ }
145
+ if (match.matched) {
146
+ return VALIDITY_VALID;
147
+ }
148
+ if (match.error && match.error.name === VALUE_MISMATCH_ERROR_NAME && !isVendorValue && !property.startsWith(VENDOR_PREFIX)) {
149
+ return VALIDITY_INVALID;
107
150
  }
151
+ return VALIDITY_UNKNOWN;
108
152
  }
109
153
 
110
154
  function process(doc, stylesheets) {
@@ -112,19 +156,30 @@ function process(doc, stylesheets) {
112
156
  doc,
113
157
  stats: { processed: 0, discarded: 0 },
114
158
  matchedElements: new Set(),
159
+ revertLayerElements: new Set(),
160
+ revertLayerRules: new WeakMap(),
161
+ revertLayerDeclarations: new WeakMap(),
162
+ hasIndeterminateRevertLayer: false,
115
163
  matchedSelectors: new Map(),
116
164
  matchingSelectors: new Map(),
165
+ scopeProximities: new Map(),
166
+ scopeProximityKeys: new Map(),
117
167
  layerDeclarationCounter: 0,
118
- layerDeclarations: [],
168
+ anonymousLayerCounter: 0,
119
169
  layerOrder: new Map(),
170
+ layerComparisons: new Map(),
120
171
  selectorData: new Map(),
121
172
  selectorTexts: new Map(),
173
+ nestingParentTexts: new Map(),
174
+ nestingParentNodes: new Map(),
175
+ scopedSelectorTexts: new Map(),
176
+ supportedSelectors: new Map(),
177
+ valueValidities: new Map(),
122
178
  preludeTexts: new Map(),
123
179
  rulesCounter: 0,
124
180
  scopeIdCounter: 0
125
181
  };
126
182
  collectLayerOrder(stylesheets, docContext);
127
- buildEffectiveLayerOrder(docContext);
128
183
  minifyRules(stylesheets, docContext);
129
184
  computeCascade(docContext);
130
185
  removeEmptyRules(stylesheets, docContext);
@@ -135,36 +190,27 @@ function collectLayerOrder(stylesheets, docContext) {
135
190
  stylesheets.forEach((stylesheetInfo, key) => {
136
191
  if (!stylesheetInfo.scoped && stylesheetInfo.stylesheet && !key.urlNode) {
137
192
  if (hasChildNodes(stylesheetInfo.stylesheet)) {
138
- collectStylesheetLayerOrder(stylesheetInfo.stylesheet.children, { layerStack: [], conditionalStack: [] }, docContext);
193
+ collectStylesheetLayerOrder(stylesheetInfo.stylesheet.children, { layerStack: [], conditionalStack: getTopConditionalStack(stylesheetInfo) }, docContext);
139
194
  }
140
195
  }
141
196
  });
142
197
  }
143
198
 
144
- function buildEffectiveLayerOrder(docContext) {
145
- const layerNames = [];
146
- for (let indexDeclaration = 0; indexDeclaration < docContext.layerDeclarations.length; indexDeclaration++) {
147
- const declaration = docContext.layerDeclarations[indexDeclaration];
148
- layerNames.push(declaration.name);
149
- }
150
- for (let indexLayerName = 0; indexLayerName < layerNames.length; indexLayerName++) {
151
- const name = layerNames[indexLayerName];
152
- if (!docContext.layerOrder.has(name)) {
153
- docContext.layerOrder.set(name, docContext.layerOrder.size);
154
- }
155
- }
199
+ function getTopConditionalStack(stylesheetInfo) {
200
+ return stylesheetInfo.mediaText ? [{ name: MEDIA_AT_RULE_NAME, prelude: stylesheetInfo.mediaText }] : [];
156
201
  }
157
202
 
158
203
  function minifyRules(stylesheets, docContext) {
159
204
  stylesheets.forEach((stylesheetInfo, key) => {
160
205
  if (!stylesheetInfo.scoped && stylesheetInfo.stylesheet && !key.urlNode) {
161
206
  if (hasChildNodes(stylesheetInfo.stylesheet)) {
162
- const topConditionalStack = stylesheetInfo.mediaText ? [{ name: MEDIA_AT_RULE_NAME, prelude: stylesheetInfo.mediaText }] : [];
163
207
  minifyStylesheetRules(stylesheetInfo.stylesheet.children, stylesheets, {
164
208
  ancestorsSelectors: [],
165
209
  layerStack: [],
166
210
  scopeStack: [],
167
- conditionalStack: topConditionalStack
211
+ conditionalStack: getTopConditionalStack(stylesheetInfo),
212
+ ownerElement: getStylesheetOwnerElement(key),
213
+ hasDefaultNamespace: hasDefaultNamespace(stylesheetInfo.stylesheet.children)
168
214
  }, docContext);
169
215
  }
170
216
  }
@@ -174,7 +220,9 @@ function minifyRules(stylesheets, docContext) {
174
220
  function computeCascade(docContext) {
175
221
  const winningDeclarations = new Set();
176
222
  docContext.matchedElements.forEach(element => computeCascadedStylesForElement(element, winningDeclarations, docContext));
177
- removeLosingDeclarations(winningDeclarations, docContext);
223
+ if (!docContext.hasIndeterminateRevertLayer) {
224
+ removeLosingDeclarations(winningDeclarations, docContext);
225
+ }
178
226
  }
179
227
 
180
228
  function removeEmptyRules(stylesheets, docContext) {
@@ -191,8 +239,10 @@ function collectStylesheetLayerOrder(cssRules, layerContext, docContext) {
191
239
  const { layerStack, conditionalStack } = layerContext;
192
240
  for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
193
241
  const ruleData = cssRule.data;
194
- if (ruleData.type === AT_RULE_TYPE && ruleData.name === LAYER_NAME) {
242
+ if (ruleData.type === AT_RULE_TYPE && decodeName(ruleData.name) === LAYER_NAME) {
195
243
  collectStylesheetLayerRule(ruleData, layerStack, conditionalStack, docContext);
244
+ } else if (isImportRule(ruleData)) {
245
+ collectImportLayerOrder(ruleData, layerContext, docContext);
196
246
  } else if (ruleData.type === AT_RULE_TYPE && hasChildNodes(ruleData.block)) {
197
247
  const newConditionalStack = buildConditionalStack(conditionalStack, ruleData, docContext);
198
248
  collectStylesheetLayerOrder(ruleData.block.children, { layerStack, conditionalStack: newConditionalStack }, docContext);
@@ -204,48 +254,136 @@ function collectStylesheetLayerOrder(cssRules, layerContext, docContext) {
204
254
 
205
255
  function collectStylesheetLayerRule(ruleData, layerStack, conditionalStack, docContext) {
206
256
  if (ruleData.block) {
207
- const layerName = getPreludeText(ruleData.prelude, docContext);
208
- registerLayerDeclaration(layerStack, layerName, conditionalStack, docContext);
209
- collectStylesheetLayerOrder(ruleData.block.children, { layerStack: [...layerStack, layerName], conditionalStack }, docContext);
257
+ const layerSegments = getLayerSegments(ruleData, docContext);
258
+ registerLayerDeclaration(layerStack, layerSegments, conditionalStack, docContext);
259
+ collectStylesheetLayerOrder(ruleData.block.children, { layerStack: [...layerStack, ...layerSegments], conditionalStack }, docContext);
210
260
  } else if (ruleData.prelude) {
211
- const layerNames = getPreludeText(ruleData.prelude, docContext).split(PRELUDE_SEPARATOR);
212
- layerNames.forEach(layerName => registerLayerDeclaration(layerStack, layerName, conditionalStack, docContext));
261
+ getPreludeText(ruleData.prelude, docContext).split(PRELUDE_SEPARATOR).forEach(layerName => registerLayerDeclaration(layerStack, splitLayerName(layerName), conditionalStack, docContext));
213
262
  }
214
263
  }
215
264
 
265
+ function collectImportLayerOrder(ruleData, layerContext, docContext) {
266
+ const urlNode = ruleData.prelude.children.head.data;
267
+ const conditionalStack = buildImportConditionalStack(layerContext.conditionalStack, urlNode);
268
+ const layerSegments = getImportLayerSegments(ruleData, urlNode, docContext);
269
+ let { layerStack } = layerContext;
270
+ if (layerSegments) {
271
+ registerLayerDeclaration(layerStack, layerSegments, conditionalStack, docContext);
272
+ layerStack = [...layerStack, ...layerSegments];
273
+ }
274
+ collectStylesheetLayerOrder(urlNode.importedChildren, { layerStack, conditionalStack }, docContext);
275
+ }
276
+
277
+ function getLayerSegments(ruleData, docContext) {
278
+ if (!ruleData.layerSegments) {
279
+ ruleData.layerSegments = ruleData.prelude ? splitLayerName(getPreludeText(ruleData.prelude, docContext)) : [createAnonymousLayerSegment(docContext)];
280
+ }
281
+ return ruleData.layerSegments;
282
+ }
283
+
284
+ function splitLayerName(layerName) {
285
+ const name = layerName.trim();
286
+ const segments = [];
287
+ let segment = EMPTY_STRING;
288
+ for (let index = 0; index < name.length; index++) {
289
+ const character = name.charAt(index);
290
+ if (character === ESCAPE_CHARACTER && index + 1 < name.length) {
291
+ segment += character + name.charAt(index + 1);
292
+ index++;
293
+ } else if (character === LAYER_NAME_SEPARATOR) {
294
+ segments.push(decodeIdentifier(segment));
295
+ segment = EMPTY_STRING;
296
+ } else {
297
+ segment += character;
298
+ }
299
+ }
300
+ segments.push(decodeIdentifier(segment));
301
+ return segments;
302
+ }
303
+
304
+ function createAnonymousLayerSegment(docContext) {
305
+ return ANONYMOUS_LAYER_PREFIX + docContext.anonymousLayerCounter++;
306
+ }
307
+
216
308
  function buildConditionalStack(conditionalStack, ruleData, docContext) {
217
- const isConditional = CONDITIONAL_AT_RULE_NAMES.has(ruleData.name);
309
+ const isConditional = CONDITIONAL_AT_RULE_NAMES.has(decodeName(ruleData.name));
218
310
  return isConditional
219
311
  ? [...conditionalStack, { name: ruleData.name, prelude: getPreludeText(ruleData.prelude, docContext) }]
220
312
  : conditionalStack;
221
313
  }
222
314
 
223
- function registerLayerDeclaration(layerStack, layerName, conditionalStack, docContext) {
224
- const fullLayerName = getFullLayerName([...layerStack, layerName]);
225
- docContext.layerDeclarations.push({
226
- name: fullLayerName,
227
- order: docContext.layerDeclarationCounter++,
228
- conditionalStack: conditionalStack.slice()
229
- });
315
+ function buildImportConditionalStack(conditionalStack, urlNode) {
316
+ const importConditionalStack = [...conditionalStack];
317
+ if (urlNode.importedMediaText) {
318
+ importConditionalStack.push({ name: MEDIA_AT_RULE_NAME, prelude: urlNode.importedMediaText });
319
+ }
320
+ if (urlNode.importedSupportsCondition !== undefined) {
321
+ importConditionalStack.push({ name: SUPPORTS_AT_RULE_NAME, prelude: urlNode.importedSupportsCondition });
322
+ }
323
+ return importConditionalStack;
324
+ }
325
+
326
+ function getImportLayerSegments(ruleData, urlNode, docContext) {
327
+ if (urlNode.importedLayerName !== undefined) {
328
+ return splitLayerName(urlNode.importedLayerName);
329
+ }
330
+ if (!urlNode.anonymousLayerSegments) {
331
+ const layerKeyword = cssTree.find(ruleData.prelude, node => node.type === IDENTIFIER_TYPE && decodeName(node.name) === LAYER_NAME);
332
+ urlNode.anonymousLayerSegments = layerKeyword ? [createAnonymousLayerSegment(docContext)] : null;
333
+ }
334
+ return urlNode.anonymousLayerSegments;
335
+ }
336
+
337
+ function isImportRule(ruleData) {
338
+ return ruleData.type === AT_RULE_TYPE && decodeName(ruleData.name) === IMPORT_NAME && hasChildNodes(ruleData.prelude) && Boolean(ruleData.prelude.children.head.data.importedChildren);
339
+ }
340
+
341
+ function registerLayerDeclaration(layerStack, layerSegments, conditionalStack, docContext) {
342
+ const position = docContext.layerDeclarationCounter++;
343
+ const certain = !conditionalStack.some(context => UNCERTAIN_CONDITIONAL_AT_RULE_NAMES.has(decodeName(context.name)));
344
+ const segments = [...layerStack, ...layerSegments];
345
+ for (let length = layerStack.length + 1; length <= segments.length; length++) {
346
+ const fullLayerName = getFullLayerName(segments.slice(0, length));
347
+ const layerPosition = docContext.layerOrder.get(fullLayerName);
348
+ if (!layerPosition) {
349
+ docContext.layerOrder.set(fullLayerName, { first: position, firstCertain: certain ? position : UNDECLARED_LAYER_POSITION });
350
+ } else if (certain && layerPosition.firstCertain === UNDECLARED_LAYER_POSITION) {
351
+ layerPosition.firstCertain = position;
352
+ }
353
+ }
230
354
  }
231
355
 
232
356
  function minifyStylesheetRules(cssRules, stylesheets, processingContext, docContext) {
233
357
  const removedRules = new Set();
358
+ let nestedRuleSeen = false;
359
+ let declarationsOrder;
234
360
  for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
235
361
  docContext.stats.processed++;
236
- minifyRule(cssRule.data, cssRule, stylesheets, processingContext, removedRules, docContext);
362
+ if (cssRule.data.type === DECLARATION_TYPE) {
363
+ if (nestedRuleSeen) {
364
+ if (declarationsOrder === undefined) {
365
+ declarationsOrder = docContext.rulesCounter++;
366
+ }
367
+ cssRule.data.order = declarationsOrder;
368
+ }
369
+ } else {
370
+ minifyRule(cssRule.data, cssRule, stylesheets, processingContext, removedRules, docContext);
371
+ nestedRuleSeen = true;
372
+ declarationsOrder = undefined;
373
+ }
237
374
  }
238
375
  removedRules.forEach(cssRule => cssRules.remove(cssRule));
239
376
  }
240
377
 
241
378
  function minifyRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
242
- if (ruleData.type === AT_RULE_TYPE && ruleData.name === IMPORT_NAME && hasChildNodes(ruleData.prelude) && ruleData.prelude.children.head.data.importedChildren) {
379
+ const atRuleName = decodeName(ruleData.name);
380
+ if (isImportRule(ruleData)) {
243
381
  minifyImportRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext);
244
- } else if (ruleData.type === AT_RULE_TYPE && ruleData.name === LAYER_NAME && hasChildNodes(ruleData.block)) {
382
+ } else if (ruleData.type === AT_RULE_TYPE && atRuleName === LAYER_NAME && hasChildNodes(ruleData.block)) {
245
383
  minifyLayerRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext);
246
- } else if (ruleData.type === AT_RULE_TYPE && ruleData.name === SCOPE_NAME && hasChildNodes(ruleData.block)) {
384
+ } else if (ruleData.type === AT_RULE_TYPE && atRuleName === SCOPE_NAME && hasChildNodes(ruleData.block)) {
247
385
  minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext);
248
- } else if (ruleData.type === AT_RULE_TYPE && ruleData.name !== FONT_FACE_NAME && ruleData.name !== KEYFRAMES_NAME && !ruleData.name.startsWith(VENDOR_PREFIX) && hasChildNodes(ruleData.block)) {
386
+ } else if (ruleData.type === AT_RULE_TYPE && atRuleName !== FONT_FACE_NAME && atRuleName !== KEYFRAMES_NAME && !atRuleName.startsWith(VENDOR_PREFIX) && hasChildNodes(ruleData.block)) {
249
387
  minifyAtRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext);
250
388
  } else if (ruleData.type === RULE_TYPE && hasChildNodes(ruleData.prelude)) {
251
389
  minifyStylesheetRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext);
@@ -254,34 +392,52 @@ function minifyRule(ruleData, cssRule, stylesheets, processingContext, removedRu
254
392
 
255
393
  function minifyImportRule(ruleData, _cssRule, stylesheets, processingContext, _removedRules, docContext) {
256
394
  const urlNode = ruleData.prelude.children.head.data;
257
- const topConditionalStack = urlNode.importedMediaText ? [{ name: MEDIA_AT_RULE_NAME, prelude: urlNode.importedMediaText }] : [];
258
- if (urlNode.importedLayerName !== undefined) {
259
- topConditionalStack.push({ name: LAYER_NAME, prelude: urlNode.importedLayerName });
260
- }
261
- if (urlNode.importedSupportsCondition !== undefined) {
262
- topConditionalStack.push({ name: SUPPORTS_AT_RULE_NAME, prelude: urlNode.importedSupportsCondition });
263
- }
395
+ const conditionalStack = buildImportConditionalStack(processingContext.conditionalStack, urlNode);
396
+ const layerSegments = getImportLayerSegments(ruleData, urlNode, docContext);
397
+ const layerStack = layerSegments ? [...processingContext.layerStack, ...layerSegments] : processingContext.layerStack;
264
398
  minifyStylesheetRules(urlNode.importedChildren, stylesheets, {
265
399
  ...processingContext,
266
- conditionalStack: topConditionalStack
400
+ layerStack,
401
+ conditionalStack,
402
+ hasDefaultNamespace: processingContext.hasDefaultNamespace || hasDefaultNamespace(urlNode.importedChildren)
267
403
  }, docContext);
268
404
  }
269
405
 
270
406
  function minifyLayerRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
271
- const layerName = getPreludeText(ruleData.prelude, docContext);
272
- const newProcessingContext = { ...processingContext, layerStack: [...processingContext.layerStack, layerName] };
407
+ const layerSegments = getLayerSegments(ruleData, docContext);
408
+ const layerStack = [...processingContext.layerStack, ...layerSegments];
409
+ if (!docContext.layerOrder.has(getFullLayerName(layerStack))) {
410
+ registerLayerDeclaration(processingContext.layerStack, layerSegments, processingContext.conditionalStack, docContext);
411
+ }
412
+ const newProcessingContext = { ...processingContext, layerStack };
273
413
  expandRawCssRules(ruleData);
274
414
  minifyStylesheetRules(ruleData.block.children, stylesheets, newProcessingContext, docContext);
275
415
  if (!hasChildNodes(ruleData.block)) {
416
+ removeEmptyLayerRule(ruleData, cssRule, removedRules, docContext);
417
+ }
418
+ }
419
+
420
+ function removeEmptyLayerRule(ruleData, cssRule, removedRules, docContext) {
421
+ if (hasChildNodes(ruleData.prelude)) {
422
+ ruleData.block = null;
423
+ } else {
276
424
  docContext.stats.discarded++;
277
425
  removedRules.add(cssRule);
278
426
  }
279
427
  }
280
428
 
429
+ function getScopePrelude(ruleData) {
430
+ const { prelude } = ruleData;
431
+ if (prelude && prelude.type === RAW_TYPE) {
432
+ return cssTree.parse(cssTree.generate(prelude), SCOPE_PRELUDE_OPTIONS);
433
+ }
434
+ return prelude;
435
+ }
436
+
281
437
  function minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
282
438
  let scopeContext;
283
439
  try {
284
- const parsedPrelude = parsePrelude(ruleData.prelude);
440
+ const parsedPrelude = parsePrelude(getScopePrelude(ruleData));
285
441
  scopeContext = buildScopeContext(parsedPrelude, processingContext, docContext);
286
442
  } catch (error) {
287
443
  if (DEBUG) {
@@ -296,7 +452,9 @@ function minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, remo
296
452
  }
297
453
  const newProcessingContext = {
298
454
  ...processingContext,
299
- scopeStack: [...(processingContext.scopeStack || []), scopeContext]
455
+ scopeStack: [...(processingContext.scopeStack || []), scopeContext],
456
+ hasUnqueryableSelector: processingContext.hasUnqueryableSelector || scopeContext.hasUnqueryableSelector,
457
+ hasNestedUnqueryablePseudoClass: processingContext.hasNestedUnqueryablePseudoClass || scopeContext.hasNestedUnqueryablePseudoClass
300
458
  };
301
459
  expandRawCssRules(ruleData);
302
460
  minifyStylesheetRules(ruleData.block.children, stylesheets, newProcessingContext, docContext);
@@ -309,50 +467,102 @@ function minifyScopeRule(ruleData, cssRule, stylesheets, processingContext, remo
309
467
  function buildScopeContext(parsedPrelude, processingContext, docContext) {
310
468
  const scopeStack = processingContext.scopeStack || [];
311
469
  const includeSelectors = parsedPrelude && parsedPrelude.include ? parsedPrelude.include : [];
470
+ const excludeSelectors = parsedPrelude && parsedPrelude.exclude ? parsedPrelude.exclude : [];
471
+ const includeAnalysis = analyzeScopeSelectors(includeSelectors, processingContext.hasDefaultNamespace);
472
+ const excludeAnalysis = analyzeScopeSelectors(excludeSelectors, processingContext.hasDefaultNamespace);
312
473
  let rootElements = [];
313
474
  if (includeSelectors.length) {
314
475
  rootElements = collectScopeRootElements(includeSelectors, scopeStack, docContext);
315
- } else if (scopeStack.length) {
316
- rootElements = Array.from(scopeStack[scopeStack.length - 1].rootElements);
476
+ if (!rootElements.length && includeAnalysis.hasNestedUnqueryablePseudoClass) {
477
+ rootElements = scopeStack.length ? Array.from(scopeStack[scopeStack.length - 1].rootElements) : getDefaultScopeRoots(docContext);
478
+ }
317
479
  } else {
318
- rootElements = getDefaultScopeRoots(docContext);
480
+ rootElements = getImplicitScopeRoots(processingContext.ownerElement, docContext);
319
481
  }
320
482
  const uniqueRoots = Array.from(new Set(rootElements.filter(Boolean)));
321
483
  if (!uniqueRoots.length) {
322
484
  return null;
323
485
  }
324
- const boundaryElements = collectScopeBoundaryElements(parsedPrelude.exclude || [], uniqueRoots, docContext);
486
+ const boundaryElements = collectScopeBoundaryElements(excludeSelectors, uniqueRoots, docContext, processingContext.hasDefaultNamespace);
325
487
  return {
326
488
  id: docContext.scopeIdCounter++,
327
489
  rootElements: new Set(uniqueRoots),
328
490
  stopElements: boundaryElements,
491
+ hasUnqueryableSelector: includeAnalysis.hasUnqueryableSelector || excludeAnalysis.hasUnqueryableSelector,
492
+ hasNestedUnqueryablePseudoClass: includeAnalysis.hasNestedUnqueryablePseudoClass
329
493
  };
330
494
  }
331
495
 
496
+ function analyzeScopeSelectors(selectors, defaultNamespace) {
497
+ const analysis = { hasUnqueryableSelector: false, hasNestedUnqueryablePseudoClass: false };
498
+ selectors.forEach(selectorInfo => {
499
+ const { hasUnqueryableSelector, hasNestedUnqueryablePseudoClass } = analyzeSelector(selectorInfo.data, defaultNamespace);
500
+ analysis.hasUnqueryableSelector ||= hasUnqueryableSelector;
501
+ analysis.hasNestedUnqueryablePseudoClass ||= hasNestedUnqueryablePseudoClass;
502
+ });
503
+ return analysis;
504
+ }
505
+
332
506
  function collectScopeRootElements(includeSelectors, scopeStack, docContext) {
333
507
  const roots = new Set();
334
508
  includeSelectors.forEach(selectorInfo => {
335
- const selectorText = sanitizeSelector(selectorInfo, null, docContext);
336
- const matchedNodes = querySelectorAll(docContext.doc, selectorText);
509
+ const selectorText = sanitizeSelector(selectorInfo, docContext);
510
+ const matchedNodes = scopeStack.length
511
+ ? matchElementsInScope(getScopedSelectorText(selectorText, docContext), scopeStack)
512
+ : querySelectorAll(docContext.doc, selectorText);
337
513
  filterElementsByScopes(matchedNodes, scopeStack).forEach(match => roots.add(match));
338
514
  });
339
515
  return Array.from(roots);
340
516
  }
341
517
 
342
- function collectScopeBoundaryElements(excludeSelectors, rootElements, docContext) {
343
- const boundaries = new Set();
518
+ function collectScopeBoundaryElements(excludeSelectors, rootElements, docContext, defaultNamespace) {
519
+ const boundaries = new Map();
344
520
  if (!excludeSelectors.length || !rootElements.length) {
345
521
  return boundaries;
346
522
  }
347
523
  excludeSelectors.forEach(selectorInfo => {
348
- const selectorText = sanitizeSelector(selectorInfo, null, docContext);
349
- rootElements.forEach(root => {
350
- matchSelectorWithinRoot(root, selectorText).forEach(node => boundaries.add(node));
351
- });
524
+ const { hasUnqueryableSelector, hasNestedUnqueryablePseudoClass } = analyzeSelector(selectorInfo.data, defaultNamespace);
525
+ if (!hasUnqueryableSelector && !hasNestedUnqueryablePseudoClass) {
526
+ const selectorText = getScopedSelectorText(sanitizeSelector(selectorInfo, docContext), docContext);
527
+ rootElements.forEach(root => {
528
+ const rootBoundaries = boundaries.get(root) || new Set();
529
+ matchSelectorWithinRoot(root, selectorText).forEach(node => rootBoundaries.add(node));
530
+ if (rootBoundaries.size) {
531
+ boundaries.set(root, rootBoundaries);
532
+ }
533
+ });
534
+ }
352
535
  });
353
536
  return boundaries;
354
537
  }
355
538
 
539
+ function hasDefaultNamespace(cssRules) {
540
+ for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
541
+ const ruleData = cssRule.data;
542
+ if (ruleData.type === AT_RULE_TYPE && ruleData.name && decodeName(ruleData.name) === NAMESPACE_AT_RULE_NAME) {
543
+ const prelude = ruleData.prelude && ruleData.prelude.children && ruleData.prelude.children.head;
544
+ if (prelude && (prelude.data.type === URL_TYPE || prelude.data.type === STRING_TYPE)) {
545
+ return true;
546
+ }
547
+ }
548
+ }
549
+ return false;
550
+ }
551
+
552
+ function getStylesheetOwnerElement(key) {
553
+ if (key && key.element) {
554
+ return key.element;
555
+ }
556
+ return key && key.nodeType === 1 ? key : null;
557
+ }
558
+
559
+ function getImplicitScopeRoots(ownerElement, docContext) {
560
+ if (ownerElement && ownerElement.parentElement) {
561
+ return [ownerElement.parentElement];
562
+ }
563
+ return getDefaultScopeRoots(docContext);
564
+ }
565
+
356
566
  function getDefaultScopeRoots(docContext) {
357
567
  const roots = [];
358
568
  if (docContext.doc && docContext.doc.documentElement) {
@@ -380,7 +590,11 @@ function minifyAtRule(ruleData, cssRule, stylesheets, processingContext, removed
380
590
 
381
591
  function minifyStylesheetRule(ruleData, cssRule, stylesheets, processingContext, removedRules, docContext) {
382
592
  ruleData.order = docContext.rulesCounter++;
383
- const removedSelectors = processSelectors(ruleData, processingContext, docContext);
593
+ if (!isPreludeSupported(ruleData.prelude, docContext)) {
594
+ return;
595
+ }
596
+ expandRawCssRules(ruleData);
597
+ const removedSelectors = getRemovableSelectors(ruleData, processSelectors(ruleData, processingContext, docContext), docContext);
384
598
  const wasDiscarded = removeUnmatchedSelectors(ruleData, removedSelectors, removedRules, cssRule, docContext);
385
599
  if (!wasDiscarded && hasChildNodes(ruleData.block)) {
386
600
  processNestedRules(ruleData, stylesheets, processingContext, docContext);
@@ -391,32 +605,70 @@ function processSelectors(ruleData, processingContext, docContext) {
391
605
  const removedSelectors = [];
392
606
  const { ancestorsSelectors, scopeStack } = processingContext;
393
607
  for (let selector = ruleData.prelude.children.head, selectorIndex = 0; selector; selector = selector.next, selectorIndex++) {
394
- const {
395
- startsWithCombinator,
396
- hasUnqueryableSelector,
397
- hasNestedUnqueryablePseudoClass
398
- } = analyzeSelector(selector.data);
608
+ const analysis = analyzeSelector(selector.data, processingContext.hasDefaultNamespace);
609
+ const { startsWithCombinator } = analysis;
610
+ const hasUnqueryableSelector = analysis.hasUnqueryableSelector || Boolean(processingContext.hasUnqueryableSelector);
611
+ const hasNestedUnqueryablePseudoClass = analysis.hasNestedUnqueryablePseudoClass || Boolean(processingContext.hasNestedUnqueryablePseudoClass);
399
612
  if (hasUnqueryableSelector) {
400
613
  ruleData.hasUnqueryableSelector = true;
401
614
  }
402
- registerSelector(selector, ruleData, processingContext, docContext);
403
- if (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length) {
404
- const relativeToScope = startsWithCombinator && Boolean(scopeStack && scopeStack.length);
405
- const matchedElements = matchElements(selector, ancestorsSelectors, scopeStack, docContext, relativeToScope);
406
- if (matchedElements.length) {
407
- if (!hasUnqueryableSelector) {
408
- updateMatchingSelectors(matchedElements, selector, docContext);
409
- }
410
- } else if (!hasNestedUnqueryablePseudoClass) {
411
- removedSelectors.push(selector);
615
+ if (hasNestedUnqueryablePseudoClass) {
616
+ ruleData.hasNestedUnqueryablePseudoClass = true;
617
+ }
618
+ const nestedSelector = getNestedSelector(selector, ancestorsSelectors, docContext);
619
+ registerSelector(selector, nestedSelector, ruleData, processingContext, docContext);
620
+ const relativeToScope = startsWithCombinator && !(ancestorsSelectors && ancestorsSelectors.length) && Boolean(scopeStack && scopeStack.length);
621
+ const matchedElements = matchElements(selector, nestedSelector, scopeStack, docContext, relativeToScope);
622
+ if (hasNestedUnqueryablePseudoClass) {
623
+ registerIndeterminateRevertLayer(ruleData, docContext);
624
+ }
625
+ if (matchedElements.length) {
626
+ if (hasUnqueryableSelector) {
627
+ registerRevertLayerElements(ruleData, matchedElements, docContext);
628
+ } else {
629
+ updateMatchingSelectors(matchedElements, selector, docContext);
412
630
  }
631
+ } else if (!hasNestedUnqueryablePseudoClass) {
632
+ removedSelectors.push(selector);
413
633
  }
414
634
  }
415
635
  return removedSelectors;
416
636
  }
417
637
 
418
- function analyzeSelector(selector) {
419
- let hasUnqueryableSelector = false;
638
+ function getRemovableSelectors(ruleData, removedSelectors, docContext) {
639
+ if (!removedSelectors.length || removedSelectors.length === ruleData.prelude.children.size || !hasNestedRules(ruleData)) {
640
+ return removedSelectors;
641
+ }
642
+ const removedSet = new Set(removedSelectors);
643
+ let keptSpecificity = { a: 0, b: 0, c: 0 };
644
+ for (let selector = ruleData.prelude.children.head; selector; selector = selector.next) {
645
+ if (!removedSet.has(selector)) {
646
+ const { specificity } = docContext.selectorData.get(selector);
647
+ if (compareSpecificities(specificity, keptSpecificity) > 0) {
648
+ keptSpecificity = specificity;
649
+ }
650
+ }
651
+ }
652
+ return removedSelectors.filter(selector => compareSpecificities(docContext.selectorData.get(selector).specificity, keptSpecificity) <= 0);
653
+ }
654
+
655
+ function hasNestedRules(ruleData) {
656
+ if (hasChildNodes(ruleData.block)) {
657
+ for (let child = ruleData.block.children.head; child; child = child.next) {
658
+ if (child.data.type !== DECLARATION_TYPE) {
659
+ return true;
660
+ }
661
+ }
662
+ }
663
+ return false;
664
+ }
665
+
666
+ function compareSpecificities(specificityA, specificityB) {
667
+ return (specificityA.a - specificityB.a) || (specificityA.b - specificityB.b) || (specificityA.c - specificityB.c);
668
+ }
669
+
670
+ function analyzeSelector(selector, defaultNamespace) {
671
+ let hasUnqueryableSelector = Boolean(defaultNamespace);
420
672
  let hasNestedUnqueryablePseudoClass = false;
421
673
  let startsWithCombinator = false;
422
674
  let functionalPseudoClassDepth = 0;
@@ -428,7 +680,7 @@ function analyzeSelector(selector) {
428
680
  hasNestedUnqueryablePseudoClass = true;
429
681
  }
430
682
  } else if (node.type === PSEUDO_CLASS_SELECTOR_TYPE) {
431
- if (PSEUDO_ELEMENT_SYNONYMS.has(node.name) || matchUnqueryablePseudoClass(node)) {
683
+ if (PSEUDO_ELEMENT_SYNONYMS.has(decodeName(node.name)) || matchUnqueryablePseudoClass(node)) {
432
684
  hasUnqueryableSelector = true;
433
685
  if (functionalPseudoClassDepth) {
434
686
  hasNestedUnqueryablePseudoClass = true;
@@ -442,6 +694,11 @@ function analyzeSelector(selector) {
442
694
  if (functionalPseudoClassDepth) {
443
695
  hasNestedUnqueryablePseudoClass = true;
444
696
  }
697
+ } else if (node.type === TYPE_SELECTOR_TYPE && typeof node.name === "string" && node.name.includes(NAMESPACE_SEPARATOR)) {
698
+ hasUnqueryableSelector = true;
699
+ if (functionalPseudoClassDepth) {
700
+ hasNestedUnqueryablePseudoClass = true;
701
+ }
445
702
  }
446
703
  },
447
704
  leave(node) {
@@ -455,6 +712,45 @@ function analyzeSelector(selector) {
455
712
  return { hasUnqueryableSelector, hasNestedUnqueryablePseudoClass, startsWithCombinator };
456
713
  }
457
714
 
715
+ function isPreludeSupported(prelude, docContext) {
716
+ if (!globalThis.CSS || !globalThis.CSS.supports) {
717
+ return true;
718
+ }
719
+ for (let selector = prelude.children.head; selector; selector = selector.next) {
720
+ if (!isSelectorSupported(selector.data, docContext)) {
721
+ return false;
722
+ }
723
+ }
724
+ return true;
725
+ }
726
+
727
+ function isSelectorSupported(selector, docContext) {
728
+ const selectorNode = cssTree.clone(selector);
729
+ cssTree.walk(selectorNode, {
730
+ visit: TYPE_SELECTOR_TYPE,
731
+ enter(node) {
732
+ if (typeof node.name === "string" && node.name.includes(NAMESPACE_SEPARATOR)) {
733
+ node.name = node.name.substring(node.name.lastIndexOf(NAMESPACE_SEPARATOR) + 1);
734
+ }
735
+ }
736
+ });
737
+ let selectorText = cssTree.generate(selectorNode);
738
+ const firstChild = selectorNode.children && selectorNode.children.head && selectorNode.children.head.data;
739
+ if (firstChild && firstChild.type === COMBINATOR_NAME) {
740
+ selectorText = SCOPE_PSEUDO_CLASS + selectorText;
741
+ }
742
+ if (!docContext.supportedSelectors.has(selectorText)) {
743
+ let supported;
744
+ try {
745
+ supported = globalThis.CSS.supports(SELECTOR_SUPPORTS_PREFIX + selectorText + SELECTOR_SUPPORTS_SUFFIX);
746
+ } catch {
747
+ supported = true;
748
+ }
749
+ docContext.supportedSelectors.set(selectorText, supported);
750
+ }
751
+ return docContext.supportedSelectors.get(selectorText);
752
+ }
753
+
458
754
  function updateMatchingSelectors(matchedElements, selector, docContext) {
459
755
  matchedElements.forEach(element => {
460
756
  docContext.matchedElements.add(element);
@@ -469,28 +765,31 @@ function updateMatchingSelectors(matchedElements, selector, docContext) {
469
765
  }
470
766
 
471
767
  function processNestedRules(ruleData, stylesheets, processingContext, docContext) {
472
- expandRawCssRules(ruleData);
473
- const newProcessingContext = { ...processingContext, ancestorsSelectors: [...processingContext.ancestorsSelectors, ruleData.prelude] };
768
+ const newProcessingContext = {
769
+ ...processingContext,
770
+ ancestorsSelectors: [...processingContext.ancestorsSelectors, ruleData.prelude],
771
+ hasUnqueryableSelector: Boolean(ruleData.hasUnqueryableSelector),
772
+ hasNestedUnqueryablePseudoClass: Boolean(ruleData.hasNestedUnqueryablePseudoClass)
773
+ };
474
774
  minifyStylesheetRules(ruleData.block.children, stylesheets, newProcessingContext, docContext);
475
775
  }
476
776
 
477
- function registerSelector(selector, ruleData, processingContext, docContext) {
777
+ function registerSelector(selector, nestedSelector, ruleData, processingContext, docContext) {
478
778
  const {
479
- ancestorsSelectors,
480
779
  layerStack,
481
780
  scopeStack,
482
781
  conditionalStack
483
782
  } = processingContext;
484
783
  docContext.selectorData.set(selector, {
485
- specificity: computeMaxSpecificity(selector.data, ancestorsSelectors, scopeStack),
784
+ specificity: computeMaxSpecificity(nestedSelector === null ? selector.data : nestedSelector.data),
486
785
  rule: ruleData,
487
786
  layerStack,
787
+ scopeStack,
488
788
  conditionalStack
489
789
  });
490
790
  }
491
791
 
492
792
  function computeCascadedStylesForElement(element, winningDeclarations, docContext) {
493
- const cascadedStyles = new Map();
494
793
  const allDeclarations = collectDeclarationItemsForElement(element, docContext);
495
794
  const contextGroups = new Map();
496
795
  allDeclarations.forEach(declarationData => {
@@ -502,18 +801,109 @@ function computeCascadedStylesForElement(element, winningDeclarations, docContex
502
801
  }
503
802
  contextGroups.get(contextKey).push(declarationData);
504
803
  });
804
+ let hasWinningRevertLayer = false;
505
805
  contextGroups.forEach(declarations => {
506
806
  declarations.sort((declarationA, declarationB) => compareDeclarations(declarationA, declarationB, docContext));
807
+ const propertyDeclarations = new Map();
507
808
  declarations.forEach(declarationData => {
508
- const { selector, declaration } = declarationData;
509
- const conditionalStack = getConditionalStackForSelector(selector, docContext);
510
- cascadedStyles.set(declaration.data.property + DECLARATION_KEY_SEPARATOR + createContextKey(conditionalStack), {
511
- selector,
512
- declaration
513
- });
809
+ const { property } = declarationData.declaration.data;
810
+ if (!propertyDeclarations.has(property)) {
811
+ propertyDeclarations.set(property, []);
812
+ }
813
+ propertyDeclarations.get(property).push(declarationData);
514
814
  });
815
+ propertyDeclarations.forEach(candidates => {
816
+ if (hasUncertainLayerOrder(candidates, docContext)) {
817
+ candidates.forEach(candidate => winningDeclarations.add(candidate.declaration));
818
+ return;
819
+ }
820
+ for (let indexCandidate = candidates.length - 1; indexCandidate >= 0; indexCandidate--) {
821
+ const { declaration, validity } = candidates[indexCandidate];
822
+ winningDeclarations.add(declaration);
823
+ if (validity === VALIDITY_VALID) {
824
+ hasWinningRevertLayer ||= hasRevertLayerKeyword(declaration, docContext);
825
+ break;
826
+ }
827
+ }
828
+ });
829
+ });
830
+ if (hasWinningRevertLayer || docContext.revertLayerElements.has(element)) {
831
+ allDeclarations.forEach(({ declaration }) => winningDeclarations.add(declaration));
832
+ }
833
+ }
834
+
835
+ function hasUncertainLayerOrder(candidates, docContext) {
836
+ const layerStacks = [new Map(), new Map()];
837
+ candidates.forEach(({ declaration, selector }) => {
838
+ if (selector) {
839
+ const { layerStack } = docContext.selectorData.get(selector);
840
+ layerStacks[declaration.data.important ? CSS_IMPORTANCE_IMPORTANT : CSS_IMPORTANCE_NOT_IMPORTANT].set(getFullLayerName(layerStack), layerStack);
841
+ }
842
+ });
843
+ return layerStacks.some(importanceStacks => {
844
+ const stacks = Array.from(importanceStacks.values());
845
+ for (let indexA = 0; indexA < stacks.length; indexA++) {
846
+ for (let indexB = indexA + 1; indexB < stacks.length; indexB++) {
847
+ if (compareLayers(stacks[indexA], stacks[indexB], docContext) === UNCERTAIN_LAYER_ORDER) {
848
+ return true;
849
+ }
850
+ }
851
+ }
852
+ return false;
515
853
  });
516
- cascadedStyles.forEach(({ declaration }) => winningDeclarations.add(declaration));
854
+ }
855
+
856
+ function registerRevertLayerElements(ruleData, matchedElements, docContext) {
857
+ if (hasRevertLayerDeclaration(ruleData, docContext)) {
858
+ matchedElements.forEach(element => docContext.revertLayerElements.add(element));
859
+ }
860
+ }
861
+
862
+ function registerIndeterminateRevertLayer(ruleData, docContext) {
863
+ if (hasRevertLayerDeclaration(ruleData, docContext)) {
864
+ docContext.hasIndeterminateRevertLayer = true;
865
+ }
866
+ }
867
+
868
+ function hasRevertLayerDeclaration(ruleData, docContext) {
869
+ let found = docContext.revertLayerRules.get(ruleData);
870
+ if (found === undefined) {
871
+ found = findRevertLayerDeclaration(ruleData, docContext);
872
+ docContext.revertLayerRules.set(ruleData, found);
873
+ }
874
+ return found;
875
+ }
876
+
877
+ function findRevertLayerDeclaration(ruleData, docContext) {
878
+ if (!hasChildNodes(ruleData.block)) {
879
+ return false;
880
+ }
881
+ for (let child = ruleData.block.children.head; child; child = child.next) {
882
+ if (child.data.type === DECLARATION_TYPE && hasRevertLayerKeyword(child, docContext)) {
883
+ return true;
884
+ }
885
+ }
886
+ return false;
887
+ }
888
+
889
+ function hasRevertLayerKeyword(declaration, docContext) {
890
+ let found = docContext.revertLayerDeclarations.get(declaration);
891
+ if (found === undefined) {
892
+ found = findRevertLayerKeyword(declaration);
893
+ docContext.revertLayerDeclarations.set(declaration, found);
894
+ }
895
+ return found;
896
+ }
897
+
898
+ function findRevertLayerKeyword(declaration) {
899
+ const { value } = declaration.data;
900
+ if (!value) {
901
+ return false;
902
+ }
903
+ if (value.type === RAW_TYPE) {
904
+ return REVERT_LAYER_TEST.test(decodeIdentifier(value.value));
905
+ }
906
+ return Boolean(cssTree.find(value, node => node.type === IDENTIFIER_TYPE && decodeIdentifier(node.name).toLowerCase() === REVERT_LAYER_KEYWORD));
517
907
  }
518
908
 
519
909
  function createContextKey(conditionalStack) {
@@ -524,13 +914,15 @@ function collectDeclarationItemsForElement(element, docContext) {
524
914
  const matchingSelectors = docContext.matchingSelectors.get(element);
525
915
  const allDeclarations = [];
526
916
  matchingSelectors.forEach(selector => {
527
- const cssRule = docContext.selectorData.get(selector).rule;
917
+ const selectorData = docContext.selectorData.get(selector);
918
+ const cssRule = selectorData.rule;
528
919
  if (hasChildNodes(cssRule.block)) {
920
+ const proximity = getScopeProximity(element, selector, docContext);
529
921
  const declarations = cssRule.block.children;
530
922
  for (let declaration = declarations.head; declaration; declaration = declaration.next) {
531
- const { type, value } = declaration.data;
923
+ const { type, value, order } = declaration.data;
532
924
  if (type === DECLARATION_TYPE && value) {
533
- addDeclaration(declaration, docContext.selectorData.get(selector).specificity, false, selector);
925
+ addDeclaration(declaration, selectorData.specificity, false, selector, order === undefined ? cssRule.order : order, proximity);
534
926
  }
535
927
  }
536
928
  }
@@ -541,36 +933,41 @@ function collectDeclarationItemsForElement(element, docContext) {
541
933
  }
542
934
  return allDeclarations;
543
935
 
544
- function addDeclaration(declaration, specificity, isInline, selector) {
936
+ function addDeclaration(declaration, specificity, isInline, selector, order, proximity) {
545
937
  const { property, value } = declaration.data;
546
- const isRawValue = value.type === RAW_TYPE;
547
- const hasValueChildNodes = hasChildNodes(value) || value.type === RAW_TYPE;
548
- let isInvalidValue;
549
- if (value.type === VALUE_TYPE &&
550
- hasValueChildNodes &&
551
- value.children.size == 1) {
552
- if (value.children.head.data.name) {
553
- const name = value.children.head.data.name;
554
- isInvalidValue = isUnsupportedVendorValue(property, name) || INVALID_CSS_ESCAPE_TEST.test(name);
555
- } if (!property.startsWith(VENDOR_PREFIX) && value.children.head.data.value) {
556
- try {
557
- isInvalidValue = isUnsupportedPropertyValue(property, value);
558
- } catch {
559
- // ignored
560
- }
938
+ if (value.type === VALUE_TYPE && hasChildNodes(value)) {
939
+ const validity = getCachedValueValidity(property, value, docContext);
940
+ if (validity !== VALIDITY_INVALID) {
941
+ allDeclarations.push({
942
+ declaration,
943
+ selector,
944
+ specificity,
945
+ isInline,
946
+ order,
947
+ proximity,
948
+ validity
949
+ });
561
950
  }
562
951
  }
563
- if (hasValueChildNodes && !isRawValue && !isInvalidValue) {
564
- allDeclarations.push({
565
- declaration,
566
- selector,
567
- specificity,
568
- isInline
569
- });
570
- }
571
952
  }
572
953
  }
573
954
 
955
+ function getCachedValueValidity(property, value, docContext) {
956
+ if (property.startsWith(CUSTOM_PROPERTY_PREFIX)) {
957
+ return VALIDITY_VALID;
958
+ }
959
+ if (!docContext.valueValidities.has(value)) {
960
+ docContext.valueValidities.set(value, getValueValidity(property, value));
961
+ }
962
+ return docContext.valueValidities.get(value);
963
+ }
964
+
965
+ function getScopeProximity(element, selector, docContext) {
966
+ const proximities = docContext.scopeProximities.get(docContext.scopeProximityKeys.get(selector));
967
+ const proximity = proximities && proximities.get(element);
968
+ return proximity === undefined ? UNSCOPED_PROXIMITY : proximity;
969
+ }
970
+
574
971
  function getConditionalStackForSelector(selector, docContext) {
575
972
  let conditionalStack = [];
576
973
  if (selector) {
@@ -582,20 +979,27 @@ function getConditionalStackForSelector(selector, docContext) {
582
979
  return conditionalStack;
583
980
  }
584
981
 
585
- function matchElements(selector, ancestorsSelectors, scopeStack, docContext, relativeToScope) {
586
- let selectorText = createSelectorText(selector, ancestorsSelectors, docContext);
982
+ function matchElements(selector, nestedSelector, scopeStack, docContext, relativeToScope) {
983
+ let selectorText = sanitizeSelector(nestedSelector === null ? selector : nestedSelector, docContext);
587
984
  if (relativeToScope) {
588
985
  selectorText = SCOPE_PSEUDO_CLASS + selectorText;
986
+ } else if (scopeStack && scopeStack.length) {
987
+ selectorText = getScopedSelectorText(selectorText, docContext);
589
988
  }
590
989
  const cacheKey = createScopeCacheKey(selectorText, scopeStack);
990
+ if (scopeStack && scopeStack.length) {
991
+ docContext.scopeProximityKeys.set(selector, cacheKey);
992
+ }
591
993
  const cachedNodes = docContext.matchedSelectors.get(cacheKey);
592
994
  if (cachedNodes) {
593
995
  return cachedNodes;
594
996
  }
595
997
  let nodes;
596
998
  if (scopeStack && scopeStack.length) {
597
- nodes = matchElementsInScope(selectorText, scopeStack);
999
+ const proximities = new Map();
1000
+ nodes = matchElementsInScope(selectorText, scopeStack, proximities);
598
1001
  nodes = filterElementsByScopes(nodes, scopeStack);
1002
+ docContext.scopeProximities.set(cacheKey, proximities);
599
1003
  } else {
600
1004
  nodes = querySelectorAll(docContext.doc, selectorText);
601
1005
  }
@@ -603,6 +1007,26 @@ function matchElements(selector, ancestorsSelectors, scopeStack, docContext, rel
603
1007
  return nodes;
604
1008
  }
605
1009
 
1010
+ function getScopedSelectorText(selectorText, docContext) {
1011
+ if (!docContext.scopedSelectorTexts.has(selectorText)) {
1012
+ let scopedSelectorText = selectorText;
1013
+ try {
1014
+ const selectorList = parseCss(selectorText, SELECTOR_LIST_CONTEXT);
1015
+ scopedSelectorText = selectorList.children.toArray().map(selector => {
1016
+ const hasScopePseudoClass = cssTree.find(selector, node => node.type === PSEUDO_CLASS_SELECTOR_TYPE && decodeName(node.name) === SCOPE_PSEUDO_CLASS_NAME);
1017
+ return hasScopePseudoClass ? cssTree.generate(selector) : SCOPE_PSEUDO_CLASS + DESCENDANT_COMBINATOR + cssTree.generate(selector);
1018
+ }).join(PRELUDE_SEPARATOR);
1019
+ } catch {
1020
+ if (DEBUG) {
1021
+ // eslint-disable-next-line no-console
1022
+ console.warn(PARSE_CSS_ERROR_MESSAGE, selectorText);
1023
+ }
1024
+ }
1025
+ docContext.scopedSelectorTexts.set(selectorText, scopedSelectorText);
1026
+ }
1027
+ return docContext.scopedSelectorTexts.get(selectorText);
1028
+ }
1029
+
606
1030
  function createScopeCacheKey(selectorText, scopeStack) {
607
1031
  if (!scopeStack || !scopeStack.length) {
608
1032
  return selectorText;
@@ -611,7 +1035,7 @@ function createScopeCacheKey(selectorText, scopeStack) {
611
1035
  return `${selectorText}${CONTEXT_KEY_SEPARATOR}${signature}`;
612
1036
  }
613
1037
 
614
- function matchElementsInScope(selectorText, scopeStack) {
1038
+ function matchElementsInScope(selectorText, scopeStack, proximities) {
615
1039
  const currentScope = scopeStack[scopeStack.length - 1];
616
1040
  const roots = Array.from(currentScope.rootElements);
617
1041
  if (!roots.length) {
@@ -619,7 +1043,19 @@ function matchElementsInScope(selectorText, scopeStack) {
619
1043
  }
620
1044
  const matchedNodes = new Set();
621
1045
  roots.forEach(root => {
622
- matchSelectorWithinRoot(root, selectorText).forEach(node => matchedNodes.add(node));
1046
+ const stopElements = currentScope.stopElements && currentScope.stopElements.get(root);
1047
+ matchSelectorWithinRoot(root, selectorText).forEach(node => {
1048
+ const proximity = getRootProximity(node, root, stopElements);
1049
+ if (proximity !== UNSCOPED_PROXIMITY) {
1050
+ matchedNodes.add(node);
1051
+ if (proximities) {
1052
+ const knownProximity = proximities.get(node);
1053
+ if (knownProximity === undefined || proximity < knownProximity) {
1054
+ proximities.set(node, proximity);
1055
+ }
1056
+ }
1057
+ }
1058
+ });
623
1059
  });
624
1060
  return Array.from(matchedNodes);
625
1061
  }
@@ -627,7 +1063,7 @@ function matchElementsInScope(selectorText, scopeStack) {
627
1063
  function matchSelectorWithinRoot(root, selectorText) {
628
1064
  const matchedNodes = new Set();
629
1065
  if (!root || root.nodeType !== 1) {
630
- return matchedNodes;
1066
+ return [];
631
1067
  }
632
1068
  if (matches(root, selectorText)) {
633
1069
  matchedNodes.add(root);
@@ -689,17 +1125,10 @@ function getTraversalRoots(root) {
689
1125
  return [];
690
1126
  }
691
1127
  if (root.nodeType === 9 || root.nodeType === 11) {
692
- const roots = [];
693
1128
  if (root.documentElement) {
694
- roots.push(root.documentElement);
695
- }
696
- if (root.body && (!roots.length || root.body !== roots[0])) {
697
- roots.push(root.body);
1129
+ return [root.documentElement];
698
1130
  }
699
- if (!roots.length && root.children) {
700
- roots.push(...Array.from(root.children).filter(node => node.nodeType === 1));
701
- }
702
- return roots;
1131
+ return root.children ? Array.from(root.children).filter(node => node.nodeType === 1) : [];
703
1132
  }
704
1133
  return [root];
705
1134
  }
@@ -724,30 +1153,81 @@ function isElementWithinScopes(element, scopeStack) {
724
1153
  }
725
1154
 
726
1155
  function isElementWithinScope(element, scopeContext) {
727
- let current = element;
728
- while (current && current.nodeType === 1) {
729
- if (scopeContext.stopElements && scopeContext.stopElements.has(current)) {
730
- return false;
731
- }
732
- if (scopeContext.rootElements.has(current)) {
1156
+ const { rootElements, stopElements } = scopeContext;
1157
+ for (let current = element; current && current.nodeType === 1; current = current.parentElement) {
1158
+ if (rootElements.has(current) && getRootProximity(element, current, stopElements && stopElements.get(current)) !== UNSCOPED_PROXIMITY) {
733
1159
  return true;
734
1160
  }
735
- current = current.parentElement;
736
1161
  }
737
1162
  return false;
738
1163
  }
739
1164
 
740
- function createSelectorText(selector, ancestorsSelectors, docContext) {
741
- let selectorText;
742
- if (ancestorsSelectors && ancestorsSelectors.length) {
743
- selectorText = combineSelectorWithAncestors(selector.data, ancestorsSelectors, docContext);
744
- const combinedAst = parseCss(selectorText, SELECTOR_LIST_CONTEXT);
745
- selectorText = sanitizeSelector({ data: combinedAst }, ancestorsSelectors, docContext);
1165
+ function getRootProximity(element, root, stopElements) {
1166
+ const ancestors = [];
1167
+ for (let current = element; current && current.nodeType === 1; current = current.parentElement) {
1168
+ ancestors.push(current);
1169
+ if (current === root) {
1170
+ return isPathBlocked(ancestors, stopElements) ? UNSCOPED_PROXIMITY : ancestors.length - 1;
1171
+ }
746
1172
  }
747
- if (!selectorText) {
748
- selectorText = sanitizeSelector(selector, ancestorsSelectors, docContext);
1173
+ return UNSCOPED_PROXIMITY;
1174
+ }
1175
+
1176
+ function isPathBlocked(ancestors, stopElements) {
1177
+ return Boolean(stopElements) && ancestors.some(ancestor => stopElements.has(ancestor));
1178
+ }
1179
+
1180
+ function getNestedSelector(selector, ancestorsSelectors, docContext) {
1181
+ if (!ancestorsSelectors || !ancestorsSelectors.length) {
1182
+ return null;
749
1183
  }
750
- return selectorText;
1184
+ const nestedSelectorText = nestSelectorText(getSelectorText(selector.data, docContext), getNestingParentText(ancestorsSelectors, docContext), docContext);
1185
+ return { data: parseCss(nestedSelectorText, SELECTOR_LIST_CONTEXT) };
1186
+ }
1187
+
1188
+ function getNestingParentText(ancestorsSelectors, docContext) {
1189
+ const prelude = ancestorsSelectors[ancestorsSelectors.length - 1];
1190
+ if (!docContext.nestingParentTexts.has(prelude)) {
1191
+ const grandParentText = ancestorsSelectors.length > 1 ? getNestingParentText(ancestorsSelectors.slice(0, -1), docContext) : null;
1192
+ const selectorTexts = [];
1193
+ for (let parentSelector = prelude.children.head; parentSelector; parentSelector = parentSelector.next) {
1194
+ if (!hasPseudoElement(parentSelector.data)) {
1195
+ const parentText = getSelectorText(parentSelector.data, docContext);
1196
+ selectorTexts.push(grandParentText === null ? parentText : nestSelectorText(parentText, grandParentText, docContext));
1197
+ }
1198
+ }
1199
+ docContext.nestingParentTexts.set(prelude, selectorTexts.length ? IS_PSEUDO_CLASS_PREFIX + selectorTexts.join(PRELUDE_SEPARATOR) + IS_PSEUDO_CLASS_SUFFIX : NO_ELEMENT_SELECTOR);
1200
+ }
1201
+ return docContext.nestingParentTexts.get(prelude);
1202
+ }
1203
+
1204
+ function hasPseudoElement(selector) {
1205
+ return Boolean(cssTree.find(selector, node => node.type === PSEUDO_ELEMENT_SELECTOR_TYPE || (node.type === PSEUDO_CLASS_SELECTOR_TYPE && PSEUDO_ELEMENT_SYNONYMS.has(decodeName(node.name)))));
1206
+ }
1207
+
1208
+ function nestSelectorText(selectorText, parentText, docContext) {
1209
+ const selector = parseCss(selectorText);
1210
+ let hasNestingSelector = false;
1211
+ cssTree.walk(selector, {
1212
+ visit: NESTING_SELECTOR_TYPE,
1213
+ enter(_node, item, list) {
1214
+ hasNestingSelector = true;
1215
+ list.insertData(getNestingParentNode(parentText, docContext), item);
1216
+ list.remove(item);
1217
+ }
1218
+ });
1219
+ if (hasNestingSelector) {
1220
+ return cssTree.generate(selector);
1221
+ }
1222
+ const startsWithCombinator = selector.children.head.data.type === COMBINATOR_NAME;
1223
+ return parentText + (startsWithCombinator ? EMPTY_STRING : DESCENDANT_COMBINATOR) + selectorText;
1224
+ }
1225
+
1226
+ function getNestingParentNode(parentText, docContext) {
1227
+ if (!docContext.nestingParentNodes.has(parentText)) {
1228
+ docContext.nestingParentNodes.set(parentText, parseCss(parentText).children.head.data);
1229
+ }
1230
+ return cssTree.clone(docContext.nestingParentNodes.get(parentText));
751
1231
  }
752
1232
 
753
1233
  function compareDeclarations(declarationA, declarationB, docContext) {
@@ -762,7 +1242,7 @@ function compareDeclarations(declarationA, declarationB, docContext) {
762
1242
  const selectorDataB = declarationB.selector ? docContext.selectorData.get(declarationB.selector) : null;
763
1243
  if (selectorDataA && selectorDataB) {
764
1244
  const layerComparison = compareLayers(selectorDataA.layerStack, selectorDataB.layerStack, docContext);
765
- if (layerComparison !== 0) {
1245
+ if (layerComparison) {
766
1246
  return importantA ? -layerComparison : layerComparison;
767
1247
  }
768
1248
  const specificityA = declarationA.specificity;
@@ -776,8 +1256,11 @@ function compareDeclarations(declarationA, declarationB, docContext) {
776
1256
  if (specificityA.c !== specificityB.c) {
777
1257
  return specificityA.c - specificityB.c;
778
1258
  }
779
- if (selectorDataA.rule.order !== selectorDataB.rule.order) {
780
- return selectorDataA.rule.order - selectorDataB.rule.order;
1259
+ if (declarationA.proximity !== declarationB.proximity) {
1260
+ return declarationB.proximity - declarationA.proximity;
1261
+ }
1262
+ if (declarationA.order !== declarationB.order) {
1263
+ return declarationA.order - declarationB.order;
781
1264
  }
782
1265
  return 0;
783
1266
  } else {
@@ -813,27 +1296,32 @@ function compareLayers(layersA, layersB, docContext) {
813
1296
  if (fullLayerNameA === fullLayerNameB) {
814
1297
  return 0;
815
1298
  }
1299
+ const comparisonKey = fullLayerNameA + CONTEXT_KEY_SEPARATOR + fullLayerNameB;
1300
+ if (!docContext.layerComparisons.has(comparisonKey)) {
1301
+ docContext.layerComparisons.set(comparisonKey, compareLayerNames(layersA, layersB, docContext));
1302
+ }
1303
+ return docContext.layerComparisons.get(comparisonKey);
1304
+ }
1305
+
1306
+ function compareLayerNames(layersA, layersB, docContext) {
816
1307
  const minLength = Math.min(layersA.length, layersB.length);
817
- const effectiveMap = docContext.layerOrder;
818
1308
  for (let indexLayer = 0; indexLayer < minLength; indexLayer++) {
819
1309
  if (layersA[indexLayer] !== layersB[indexLayer]) {
820
- const partialLayerA = getFullLayerName(layersA.slice(0, indexLayer + 1));
821
- const partialLayerB = getFullLayerName(layersB.slice(0, indexLayer + 1));
822
- const orderA = effectiveMap.get(partialLayerA);
823
- const orderB = effectiveMap.get(partialLayerB);
824
- if (orderA !== undefined && orderB !== undefined) {
825
- return orderA - orderB;
1310
+ const positionA = docContext.layerOrder.get(getFullLayerName(layersA.slice(0, indexLayer + 1)));
1311
+ const positionB = docContext.layerOrder.get(getFullLayerName(layersB.slice(0, indexLayer + 1)));
1312
+ if (!positionA || !positionB) {
1313
+ return UNCERTAIN_LAYER_ORDER;
826
1314
  }
827
- if (orderA !== undefined) {
1315
+ if (positionA.firstCertain < positionB.first) {
828
1316
  return -1;
829
1317
  }
830
- if (orderB !== undefined) {
1318
+ if (positionB.firstCertain < positionA.first) {
831
1319
  return 1;
832
1320
  }
833
- return 0;
1321
+ return UNCERTAIN_LAYER_ORDER;
834
1322
  }
835
1323
  }
836
- return layersA.length - layersB.length;
1324
+ return layersB.length - layersA.length;
837
1325
  }
838
1326
 
839
1327
  function removeStylesheetEmptyRules(cssRules, docContext) {
@@ -843,15 +1331,22 @@ function removeStylesheetEmptyRules(cssRules, docContext) {
843
1331
  if (ruleData.type === RULE_TYPE) {
844
1332
  if (hasChildNodes(ruleData.block)) {
845
1333
  removeStylesheetEmptyRules(ruleData.block.children, docContext);
846
- } else {
1334
+ }
1335
+ if (!hasChildNodes(ruleData.block)) {
847
1336
  docContext.stats.discarded++;
848
1337
  removedRules.add(cssRule);
849
1338
  }
850
- } else if (ruleData.type === AT_RULE_TYPE && ruleData.block && ruleData.name !== FONT_FACE_NAME && ruleData.name !== KEYFRAMES_NAME) {
1339
+ } else if (isImportRule(ruleData)) {
1340
+ removeStylesheetEmptyRules(ruleData.prelude.children.head.data.importedChildren, docContext);
1341
+ } else if (ruleData.type === AT_RULE_TYPE && ruleData.block && decodeName(ruleData.name) !== FONT_FACE_NAME && decodeName(ruleData.name) !== KEYFRAMES_NAME) {
851
1342
  removeStylesheetEmptyRules(ruleData.block.children, docContext);
852
1343
  if (!hasChildNodes(ruleData.block)) {
853
- docContext.stats.discarded++;
854
- removedRules.add(cssRule);
1344
+ if (decodeName(ruleData.name) === LAYER_NAME) {
1345
+ removeEmptyLayerRule(ruleData, cssRule, removedRules, docContext);
1346
+ } else {
1347
+ docContext.stats.discarded++;
1348
+ removedRules.add(cssRule);
1349
+ }
855
1350
  }
856
1351
  }
857
1352
  }
@@ -904,22 +1399,18 @@ function expandRawCssRules(ruleData) {
904
1399
  const ruleChildren = [];
905
1400
  if (hasChildNodes(ruleData.block)) {
906
1401
  for (let cssRuleNode = ruleData.block.children.head; cssRuleNode; cssRuleNode = cssRuleNode.next) {
907
- if (cssRuleNode.data.type === RAW_TYPE) {
908
- if (cssRuleNode.data.value.indexOf(BLOCK_OPEN) !== -1 &&
909
- cssRuleNode.data.value.indexOf(BLOCK_OPEN) < cssRuleNode.data.value.indexOf(BLOCK_CLOSE)) {
910
- try {
911
- const stylesheet = parseCss(cssRuleNode.data.value, STYLESHEET_CONTEXT);
912
- for (let stylesheetChild = stylesheet.children.head; stylesheetChild; stylesheetChild = stylesheetChild.next) {
913
- ruleChildren.push(stylesheetChild);
914
- }
915
- } catch (error) {
916
- if (DEBUG) {
917
- // eslint-disable-next-line no-console
918
- console.warn(PARSE_CSS_ERROR_MESSAGE, cssRuleNode.data.value, error);
919
- }
1402
+ const rawRuleText = getRawRuleText(cssRuleNode.data);
1403
+ if (rawRuleText !== null) {
1404
+ try {
1405
+ const stylesheet = parseCss(rawRuleText, STYLESHEET_CONTEXT);
1406
+ for (let stylesheetChild = stylesheet.children.head; stylesheetChild; stylesheetChild = stylesheetChild.next) {
1407
+ ruleChildren.push(stylesheetChild);
1408
+ }
1409
+ } catch (error) {
1410
+ if (DEBUG) {
1411
+ // eslint-disable-next-line no-console
1412
+ console.warn(PARSE_CSS_ERROR_MESSAGE, rawRuleText, error);
920
1413
  }
921
- } else {
922
- ruleChildren.push(cssRuleNode);
923
1414
  }
924
1415
  } else {
925
1416
  ruleChildren.push(cssRuleNode);
@@ -930,62 +1421,19 @@ function expandRawCssRules(ruleData) {
930
1421
  ruleChildren.forEach(ruleChild => ruleData.block.children.appendData(ruleChild.data));
931
1422
  }
932
1423
 
933
- function combineSelectorWithAncestors(selector, ancestorsSelectors, docContext) {
934
- const selectorText = getSelectorText(selector, docContext);
935
- if (!ancestorsSelectors || !ancestorsSelectors.length) {
936
- return selectorText;
937
- } else {
938
- let contexts = [EMPTY_STRING];
939
- ancestorsSelectors.forEach(selectorList => {
940
- if (hasChildNodes(selectorList)) {
941
- const parentSelectors = selectorList.children.toArray();
942
- const nextContexts = [];
943
- contexts.forEach(context => parentSelectors.forEach(parentSelector => {
944
- const parentText = getSelectorText(parentSelector, docContext);
945
- const combined = context ? combineSelectors(context, parentText) : parentText;
946
- if (!nextContexts.includes(combined)) {
947
- nextContexts.push(combined);
948
- }
949
- }));
950
- if (nextContexts.length) {
951
- contexts = nextContexts;
952
- }
953
- }
954
- });
955
- const expandedSelectors = new Set();
956
- contexts.forEach(context => {
957
- const result = context ? combineSelectors(context, selectorText) : selectorText;
958
- expandedSelectors.add(result);
959
- });
960
- return Array.from(expandedSelectors).join(PRELUDE_SEPARATOR);
1424
+ function getRawRuleText(node) {
1425
+ if (node.type === RAW_TYPE && holdsBlock(node.value)) {
1426
+ return node.value;
961
1427
  }
1428
+ if (node.type === DECLARATION_TYPE && !node.property.startsWith(CUSTOM_PROPERTY_PREFIX) && node.value.type === RAW_TYPE && holdsBlock(node.value.value) && node.value.value.trimStart().indexOf(BLOCK_OPEN) > 0) {
1429
+ return node.property + DECLARATION_SEPARATOR + node.value.value;
1430
+ }
1431
+ return null;
962
1432
  }
963
1433
 
964
- function combineSelectors(parentSelectorText, childSelectorText) {
965
- const childSelector = parseCss(childSelectorText || NESTING_SELECTOR);
966
- const parentSelector = parentSelectorText ? parseCss(parentSelectorText) : null;
967
- let hasNesting = false;
968
- cssTree.walk(childSelector, {
969
- visit: NESTING_SELECTOR_TYPE,
970
- enter(_node, item, list) {
971
- hasNesting = true;
972
- if (!parentSelector) {
973
- list.remove(item);
974
- return;
975
- }
976
- const nodes = parentSelector.children.toArray().map(parent => cssTree.clone(parent));
977
- nodes.forEach(node => list.insertData(node, item));
978
- list.remove(item);
979
- }
980
- });
981
- if (hasNesting) {
982
- return cssTree.generate(childSelector);
983
- }
984
- if (!parentSelector) {
985
- return cssTree.generate(childSelector);
986
- }
987
- const combinedSelector = parseCss(`${parentSelectorText} ${childSelectorText}`);
988
- return cssTree.generate(combinedSelector);
1434
+ function holdsBlock(text) {
1435
+ const blockOpenIndex = text.indexOf(BLOCK_OPEN);
1436
+ return blockOpenIndex !== -1 && blockOpenIndex < text.indexOf(BLOCK_CLOSE);
989
1437
  }
990
1438
 
991
1439
  function hasChildNodes(node) {
@@ -1011,7 +1459,7 @@ function getPreludeText(prelude, docContext) {
1011
1459
  }
1012
1460
 
1013
1461
  function getFullLayerName(layers) {
1014
- return layers.map(layerName => layerName === EMPTY_STRING ? ANONYMOUS_LAYER_PLACEHOLDER : layerName).join(LAYER_NAME_SEPARATOR);
1462
+ return layers.join(LAYER_NAME_KEY_SEPARATOR);
1015
1463
  }
1016
1464
 
1017
1465
  function parseCss(text, context = SELECTOR_CONTEXT) {