@acemir/cssom 0.9.19 → 0.9.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/CSSOM.js +1121 -217
- package/lib/CSSConditionRule.js +8 -3
- package/lib/CSSContainerRule.js +21 -12
- package/lib/CSSDocumentRule.js +2 -1
- package/lib/CSSGroupingRule.js +2 -1
- package/lib/CSSHostRule.js +3 -2
- package/lib/CSSImportRule.js +92 -19
- package/lib/CSSKeyframesRule.js +2 -1
- package/lib/CSSLayerBlockRule.js +1 -1
- package/lib/CSSMediaRule.js +14 -5
- package/lib/CSSNamespaceRule.js +27 -11
- package/lib/CSSPageRule.js +275 -0
- package/lib/CSSRule.js +6 -0
- package/lib/CSSRuleList.js +26 -0
- package/lib/CSSScopeRule.js +53 -0
- package/lib/CSSStartingStyleRule.js +5 -4
- package/lib/CSSStyleRule.js +21 -5
- package/lib/CSSStyleSheet.js +49 -9
- package/lib/CSSSupportsRule.js +1 -0
- package/lib/StyleSheet.js +16 -1
- package/lib/clone.js +1 -0
- package/lib/index.js +3 -0
- package/lib/parse.js +504 -104
- package/package.json +1 -1
package/lib/parse.js
CHANGED
|
@@ -50,7 +50,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
50
50
|
"conditionBlock": true,
|
|
51
51
|
"counterStyleBlock": true,
|
|
52
52
|
'documentRule-begin': true,
|
|
53
|
-
"
|
|
53
|
+
"scopeBlock": true,
|
|
54
|
+
"layerBlock": true,
|
|
55
|
+
"pageBlock": true
|
|
54
56
|
};
|
|
55
57
|
|
|
56
58
|
var styleSheet = new CSSOM.CSSStyleSheet();
|
|
@@ -68,7 +70,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
68
70
|
var ancestorRules = [];
|
|
69
71
|
var prevScope;
|
|
70
72
|
|
|
71
|
-
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
73
|
+
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
72
74
|
|
|
73
75
|
// Track defined namespace prefixes for validation
|
|
74
76
|
var definedNamespacePrefixes = {};
|
|
@@ -78,11 +80,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
78
80
|
// var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
|
|
79
81
|
var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
|
|
80
82
|
var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
|
|
81
|
-
var forwardRuleValidationRegExp = /(?:\(
|
|
83
|
+
var forwardRuleValidationRegExp = /(?:\s|\/\*|\{|\()/; // Match that the rule is followed by any whitespace, a opening comment, a condition opening parenthesis or a opening brace
|
|
82
84
|
var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
|
|
83
85
|
var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
|
|
84
86
|
var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
|
|
85
|
-
var
|
|
87
|
+
var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
|
|
88
|
+
var startsWithCombinatorRegExp = /^\s*[>+~]/; // Checks if a selector starts with a CSS combinator (>, +, ~)
|
|
89
|
+
var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/;
|
|
86
90
|
|
|
87
91
|
/**
|
|
88
92
|
* Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
|
|
@@ -174,24 +178,214 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
174
178
|
return i;
|
|
175
179
|
}
|
|
176
180
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Parses the scope prelude and extracts start and end selectors.
|
|
183
|
+
* @param {string} preludeContent - The scope prelude content (without @scope keyword)
|
|
184
|
+
* @returns {object} Object with startSelector and endSelector properties
|
|
185
|
+
*/
|
|
186
|
+
function parseScopePrelude(preludeContent) {
|
|
187
|
+
var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
|
|
188
|
+
|
|
189
|
+
// Restore the parentheses that were consumed by the split
|
|
190
|
+
if (parts.length === 2) {
|
|
191
|
+
parts[0] = parts[0] + ')';
|
|
192
|
+
parts[1] = '(' + parts[1];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
var hasStart = parts[0] &&
|
|
196
|
+
parts[0].charAt(0) === '(' &&
|
|
197
|
+
parts[0].charAt(parts[0].length - 1) === ')';
|
|
198
|
+
var hasEnd = parts[1] &&
|
|
199
|
+
parts[1].charAt(0) === '(' &&
|
|
200
|
+
parts[1].charAt(parts[1].length - 1) === ')';
|
|
201
|
+
|
|
202
|
+
// Handle case: @scope to (<end>)
|
|
203
|
+
var hasOnlyEnd = !hasStart &&
|
|
204
|
+
!hasEnd &&
|
|
205
|
+
parts[0].indexOf('to (') === 0 &&
|
|
206
|
+
parts[0].charAt(parts[0].length - 1) === ')';
|
|
207
|
+
|
|
208
|
+
var startSelector = '';
|
|
209
|
+
var endSelector = '';
|
|
210
|
+
|
|
211
|
+
if (hasStart) {
|
|
212
|
+
startSelector = parts[0].slice(1, -1).trim();
|
|
213
|
+
}
|
|
214
|
+
if (hasEnd) {
|
|
215
|
+
endSelector = parts[1].slice(1, -1).trim();
|
|
216
|
+
}
|
|
217
|
+
if (hasOnlyEnd) {
|
|
218
|
+
endSelector = parts[0].slice(4, -1).trim();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
startSelector: startSelector,
|
|
223
|
+
endSelector: endSelector,
|
|
224
|
+
hasStart: hasStart,
|
|
225
|
+
hasEnd: hasEnd,
|
|
226
|
+
hasOnlyEnd: hasOnlyEnd
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Checks if a selector contains pseudo-elements.
|
|
232
|
+
* @param {string} selector - The CSS selector to check
|
|
233
|
+
* @returns {boolean} True if the selector contains pseudo-elements
|
|
234
|
+
*/
|
|
235
|
+
function hasPseudoElement(selector) {
|
|
236
|
+
// Match only double-colon (::) pseudo-elements
|
|
237
|
+
// Also match legacy single-colon pseudo-elements: :before, :after, :first-line, :first-letter
|
|
238
|
+
// These must NOT be followed by alphanumeric characters (to avoid matching :before-x or similar)
|
|
239
|
+
var pseudoElementRegex = /::[a-zA-Z][\w-]*|:(before|after|first-line|first-letter)(?![a-zA-Z0-9_-])/;
|
|
240
|
+
return pseudoElementRegex.test(selector);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Validates balanced parentheses, brackets, and quotes in a selector.
|
|
245
|
+
*
|
|
246
|
+
* @param {string} selector - The CSS selector to validate
|
|
247
|
+
* @param {boolean} trackAttributes - Whether to track attribute selector context
|
|
248
|
+
* @param {boolean} useStack - Whether to use a stack for parentheses (needed for nested validation)
|
|
249
|
+
* @returns {boolean} True if the syntax is valid (all brackets, parentheses, and quotes are balanced)
|
|
250
|
+
*/
|
|
251
|
+
function validateBalancedSyntax(selector, trackAttributes, useStack) {
|
|
252
|
+
var parenDepth = 0;
|
|
253
|
+
var bracketDepth = 0;
|
|
254
|
+
var inSingleQuote = false;
|
|
255
|
+
var inDoubleQuote = false;
|
|
256
|
+
var inAttr = false;
|
|
257
|
+
var stack = useStack ? [] : null;
|
|
258
|
+
|
|
259
|
+
for (var i = 0; i < selector.length; i++) {
|
|
260
|
+
var char = selector[i];
|
|
261
|
+
var prevChar = i > 0 ? selector[i - 1] : '';
|
|
262
|
+
|
|
263
|
+
if (inSingleQuote) {
|
|
264
|
+
if (char === "'" && prevChar !== "\\") {
|
|
265
|
+
inSingleQuote = false;
|
|
266
|
+
}
|
|
267
|
+
} else if (inDoubleQuote) {
|
|
268
|
+
if (char === '"' && prevChar !== "\\") {
|
|
269
|
+
inDoubleQuote = false;
|
|
270
|
+
}
|
|
271
|
+
} else if (trackAttributes && inAttr) {
|
|
272
|
+
if (char === "]") {
|
|
273
|
+
inAttr = false;
|
|
274
|
+
} else if (char === "'") {
|
|
275
|
+
inSingleQuote = true;
|
|
276
|
+
} else if (char === '"') {
|
|
277
|
+
inDoubleQuote = true;
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
if (trackAttributes && char === "[") {
|
|
281
|
+
inAttr = true;
|
|
282
|
+
} else if (char === "'") {
|
|
283
|
+
inSingleQuote = true;
|
|
284
|
+
} else if (char === '"') {
|
|
285
|
+
inDoubleQuote = true;
|
|
286
|
+
} else if (char === '(') {
|
|
287
|
+
if (useStack) {
|
|
288
|
+
stack.push("(");
|
|
289
|
+
} else {
|
|
290
|
+
parenDepth++;
|
|
291
|
+
}
|
|
292
|
+
} else if (char === ')') {
|
|
293
|
+
if (useStack) {
|
|
294
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
parenDepth--;
|
|
299
|
+
if (parenDepth < 0) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
} else if (char === '[') {
|
|
304
|
+
bracketDepth++;
|
|
305
|
+
} else if (char === ']') {
|
|
306
|
+
bracketDepth--;
|
|
307
|
+
if (bracketDepth < 0) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
191
312
|
}
|
|
313
|
+
|
|
314
|
+
// Check if everything is balanced
|
|
315
|
+
if (useStack) {
|
|
316
|
+
return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
|
|
317
|
+
} else {
|
|
318
|
+
return parenDepth === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Checks for basic syntax errors in selectors (mismatched parentheses, brackets, quotes).
|
|
324
|
+
* @param {string} selector - The CSS selector to check
|
|
325
|
+
* @returns {boolean} True if there are syntax errors
|
|
326
|
+
*/
|
|
327
|
+
function hasBasicSyntaxError(selector) {
|
|
328
|
+
return !validateBalancedSyntax(selector, false, false);
|
|
192
329
|
};
|
|
193
330
|
|
|
194
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Checks for invalid combinator patterns in selectors.
|
|
333
|
+
* @param {string} selector - The CSS selector to check
|
|
334
|
+
* @returns {boolean} True if the selector contains invalid combinators
|
|
335
|
+
*/
|
|
336
|
+
function hasInvalidCombinators(selector) {
|
|
337
|
+
// Check for invalid combinator patterns:
|
|
338
|
+
// - <> (not a valid combinator)
|
|
339
|
+
// - >> (deep descendant combinator, deprecated and invalid)
|
|
340
|
+
// - Multiple consecutive combinators like >>, >~, etc.
|
|
341
|
+
if (/<>/.test(selector)) return true;
|
|
342
|
+
if (/>>/.test(selector)) return true;
|
|
343
|
+
// Check for other invalid consecutive combinator patterns
|
|
344
|
+
if (/[>+~]\s*[>+~]/.test(selector)) return true;
|
|
345
|
+
return false;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Checks for invalid pseudo-like syntax (function calls without proper pseudo prefix).
|
|
350
|
+
* @param {string} selector - The CSS selector to check
|
|
351
|
+
* @returns {boolean} True if the selector contains invalid pseudo-like syntax
|
|
352
|
+
*/
|
|
353
|
+
function hasInvalidPseudoSyntax(selector) {
|
|
354
|
+
// Check for specific known pseudo-elements used without : or :: prefix
|
|
355
|
+
// Examples: slotted(div), part(name), cue(selector)
|
|
356
|
+
// These are ONLY valid as ::slotted(), ::part(), ::cue()
|
|
357
|
+
var invalidPatterns = [
|
|
358
|
+
/(?:^|[\s>+~,\[])slotted\s*\(/i,
|
|
359
|
+
/(?:^|[\s>+~,\[])part\s*\(/i,
|
|
360
|
+
/(?:^|[\s>+~,\[])cue\s*\(/i,
|
|
361
|
+
/(?:^|[\s>+~,\[])cue-region\s*\(/i
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
for (var i = 0; i < invalidPatterns.length; i++) {
|
|
365
|
+
if (invalidPatterns[i].test(selector)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Checks for invalid nesting selector (&) usage.
|
|
374
|
+
* The & selector cannot be directly followed by a type selector without a delimiter.
|
|
375
|
+
* Valid: &.class, &#id, &[attr], &:hover, &::before, & div, &>div
|
|
376
|
+
* Invalid: &div, &span
|
|
377
|
+
* @param {string} selector - The CSS selector to check
|
|
378
|
+
* @returns {boolean} True if the selector contains invalid & usage
|
|
379
|
+
*/
|
|
380
|
+
function hasInvalidNestingSelector(selector) {
|
|
381
|
+
// Check for & followed directly by a letter (type selector) without any delimiter
|
|
382
|
+
// This regex matches & followed by a letter (start of type selector) that's not preceded by an escape
|
|
383
|
+
// We need to exclude valid cases like &.class, &#id, &[attr], &:pseudo, &::pseudo, & (with space), &>
|
|
384
|
+
var invalidNestingPattern = /&(?![.\#\[:>\+~\s])[a-zA-Z]/;
|
|
385
|
+
return invalidNestingPattern.test(selector);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
function validateAtRule(atRuleKey, validCallback, cannotBeNested) {
|
|
195
389
|
var isValid = false;
|
|
196
390
|
var sourceRuleRegExp = atRuleKey === "@import" ? forwardImportRuleValidationRegExp : forwardRuleValidationRegExp;
|
|
197
391
|
var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
|
|
@@ -212,6 +406,114 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
212
406
|
isValid = true;
|
|
213
407
|
}
|
|
214
408
|
}
|
|
409
|
+
|
|
410
|
+
// Additional validation for @scope rule
|
|
411
|
+
if (isValid && atRuleKey === "@scope") {
|
|
412
|
+
var openBraceIndex = ruleSlice.indexOf('{');
|
|
413
|
+
if (openBraceIndex !== -1) {
|
|
414
|
+
// Extract the rule prelude (everything between the at-rule and {)
|
|
415
|
+
var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
|
|
416
|
+
|
|
417
|
+
// Skip past at-rule keyword and whitespace
|
|
418
|
+
var preludeContent = rulePrelude.slice("@scope".length).trim();
|
|
419
|
+
|
|
420
|
+
if (preludeContent.length > 0) {
|
|
421
|
+
// Parse the scope prelude
|
|
422
|
+
var parsedScopePrelude = parseScopePrelude(preludeContent);
|
|
423
|
+
var startSelector = parsedScopePrelude.startSelector;
|
|
424
|
+
var endSelector = parsedScopePrelude.endSelector;
|
|
425
|
+
var hasStart = parsedScopePrelude.hasStart;
|
|
426
|
+
var hasEnd = parsedScopePrelude.hasEnd;
|
|
427
|
+
var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
|
|
428
|
+
|
|
429
|
+
// Validation rules for @scope:
|
|
430
|
+
// 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
|
|
431
|
+
if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
|
|
432
|
+
isValid = false;
|
|
433
|
+
}
|
|
434
|
+
// 2. Pseudo-elements are invalid in scope selectors
|
|
435
|
+
else if ((startSelector && hasPseudoElement(startSelector)) || (endSelector && hasPseudoElement(endSelector))) {
|
|
436
|
+
isValid = false;
|
|
437
|
+
}
|
|
438
|
+
// 3. Basic syntax errors (mismatched parens, brackets, quotes)
|
|
439
|
+
else if ((startSelector && hasBasicSyntaxError(startSelector)) || (endSelector && hasBasicSyntaxError(endSelector))) {
|
|
440
|
+
isValid = false;
|
|
441
|
+
}
|
|
442
|
+
// 4. Invalid combinator patterns
|
|
443
|
+
else if ((startSelector && hasInvalidCombinators(startSelector)) || (endSelector && hasInvalidCombinators(endSelector))) {
|
|
444
|
+
isValid = false;
|
|
445
|
+
}
|
|
446
|
+
// 5. Invalid pseudo-like syntax (function without : or :: prefix)
|
|
447
|
+
else if ((startSelector && hasInvalidPseudoSyntax(startSelector)) || (endSelector && hasInvalidPseudoSyntax(endSelector))) {
|
|
448
|
+
isValid = false;
|
|
449
|
+
}
|
|
450
|
+
// 6. Invalid structure (no proper parentheses found when prelude is not empty)
|
|
451
|
+
else if (!hasStart && !hasOnlyEnd) {
|
|
452
|
+
isValid = false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// Empty prelude (@scope {}) is valid
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (isValid && atRuleKey === "@page") {
|
|
460
|
+
var openBraceIndex = ruleSlice.indexOf('{');
|
|
461
|
+
if (openBraceIndex !== -1) {
|
|
462
|
+
// Extract the rule prelude (everything between the at-rule and {)
|
|
463
|
+
var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
|
|
464
|
+
|
|
465
|
+
// Skip past at-rule keyword and whitespace
|
|
466
|
+
var preludeContent = rulePrelude.slice("@page".length).trim();
|
|
467
|
+
|
|
468
|
+
if (preludeContent.length > 0) {
|
|
469
|
+
var trimmedValue = preludeContent.trim();
|
|
470
|
+
|
|
471
|
+
// Empty selector is valid for @page
|
|
472
|
+
if (trimmedValue !== '') {
|
|
473
|
+
// Parse @page selectorText for page name and pseudo-pages
|
|
474
|
+
// Valid formats:
|
|
475
|
+
// - (empty - no name, no pseudo-page)
|
|
476
|
+
// - :left, :right, :first, :blank (pseudo-page only)
|
|
477
|
+
// - named (named page only)
|
|
478
|
+
// - named:first (named page with single pseudo-page)
|
|
479
|
+
// - named:first:left (named page with multiple pseudo-pages)
|
|
480
|
+
var match = trimmedValue.match(atPageRuleSelectorRegExp);
|
|
481
|
+
if (match) {
|
|
482
|
+
var pageName = match[1] || '';
|
|
483
|
+
var pseudoPages = match[2] || '';
|
|
484
|
+
|
|
485
|
+
// Validate page name if present
|
|
486
|
+
if (pageName) {
|
|
487
|
+
if (!cssCustomIdentifierRegExp.test(pageName)) {
|
|
488
|
+
isValid = false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Validate pseudo-pages if present
|
|
493
|
+
if (pseudoPages) {
|
|
494
|
+
var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
|
|
495
|
+
var validPseudos = ['left', 'right', 'first', 'blank'];
|
|
496
|
+
var allValid = true;
|
|
497
|
+
for (var j = 0; j < pseudos.length; j++) {
|
|
498
|
+
if (validPseudos.indexOf(pseudos[j].toLowerCase()) === -1) {
|
|
499
|
+
allValid = false;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!allValid) {
|
|
505
|
+
isValid = false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
isValid = false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
215
517
|
if (!isValid) {
|
|
216
518
|
// If it's invalid the browser will simply ignore the entire invalid block
|
|
217
519
|
// Use regex to find the closing brace of the invalid rule
|
|
@@ -270,55 +572,47 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
270
572
|
* @returns {boolean}
|
|
271
573
|
*/
|
|
272
574
|
function basicSelectorValidator(selector) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
var inSingleQuote = false;
|
|
278
|
-
var inDoubleQuote = false;
|
|
575
|
+
// Validate balanced syntax with attribute tracking and stack-based parentheses matching
|
|
576
|
+
if (!validateBalancedSyntax(selector, true, true)) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
279
579
|
|
|
280
|
-
|
|
281
|
-
|
|
580
|
+
// Check for invalid combinator patterns
|
|
581
|
+
if (hasInvalidCombinators(selector)) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
282
584
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
} else if (inDoubleQuote) {
|
|
288
|
-
if (char === '"' && selector[i - 1] !== "\\") {
|
|
289
|
-
inDoubleQuote = false;
|
|
290
|
-
}
|
|
291
|
-
} else if (inAttr) {
|
|
292
|
-
if (char === "]") {
|
|
293
|
-
inAttr = false;
|
|
294
|
-
} else if (char === "'") {
|
|
295
|
-
inSingleQuote = true;
|
|
296
|
-
} else if (char === '"') {
|
|
297
|
-
inDoubleQuote = true;
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
if (char === "[") {
|
|
301
|
-
inAttr = true;
|
|
302
|
-
} else if (char === "'") {
|
|
303
|
-
inSingleQuote = true;
|
|
304
|
-
} else if (char === '"') {
|
|
305
|
-
inDoubleQuote = true;
|
|
306
|
-
} else if (char === "(") {
|
|
307
|
-
stack.push("(");
|
|
308
|
-
} else if (char === ")") {
|
|
309
|
-
if (!stack.length || stack.pop() !== "(") {
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
i++;
|
|
585
|
+
// Check for invalid pseudo-like syntax
|
|
586
|
+
if (hasInvalidPseudoSyntax(selector)) {
|
|
587
|
+
return false;
|
|
315
588
|
}
|
|
316
589
|
|
|
317
|
-
//
|
|
318
|
-
if (
|
|
590
|
+
// Check for invalid nesting selector (&) usage
|
|
591
|
+
if (hasInvalidNestingSelector(selector)) {
|
|
319
592
|
return false;
|
|
320
593
|
}
|
|
321
594
|
|
|
595
|
+
// Check for invalid pseudo-class usage with quoted strings
|
|
596
|
+
// Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
|
|
597
|
+
var pseudoPattern = /::?([a-zA-Z][\w-]*)\(([^)]+)\)/g;
|
|
598
|
+
var pseudoMatch;
|
|
599
|
+
while ((pseudoMatch = pseudoPattern.exec(selector)) !== null) {
|
|
600
|
+
var pseudoName = pseudoMatch[1];
|
|
601
|
+
var pseudoContent = pseudoMatch[2];
|
|
602
|
+
|
|
603
|
+
// List of pseudo-classes that should not accept quoted strings
|
|
604
|
+
// :lang() - accepts language codes: en, fr-CA
|
|
605
|
+
// :dir() - accepts direction: ltr, rtl
|
|
606
|
+
// :nth-*() - accepts An+B notation: 2n+1, odd, even
|
|
607
|
+
var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
|
|
608
|
+
|
|
609
|
+
for (var i = 0; i < noQuotesPseudos.length; i++) {
|
|
610
|
+
if (pseudoName === noQuotesPseudos[i] && /['"]/.test(pseudoContent)) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
322
616
|
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
323
617
|
// This is similar to the original, but without nested paren limitations
|
|
324
618
|
// Modified to support namespace selectors: *|element, prefix|element, |element
|
|
@@ -326,7 +620,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
326
620
|
var looseSelectorRegExp = /^((?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|(?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?\*|#[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\.[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\[(?:[^\[\]'"]|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")*(?:\s+[iI])?\]|::?[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+(?:\((.*)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
327
621
|
return looseSelectorRegExp.test(selector);
|
|
328
622
|
}
|
|
329
|
-
|
|
623
|
+
|
|
330
624
|
/**
|
|
331
625
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
332
626
|
*
|
|
@@ -357,48 +651,56 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
357
651
|
* @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
|
|
358
652
|
*/
|
|
359
653
|
function parseAndSplitNestedSelectors(selector) {
|
|
360
|
-
var depth = 0;
|
|
361
|
-
var buffer = "";
|
|
362
|
-
var parts = [];
|
|
363
|
-
var inSingleQuote = false;
|
|
364
|
-
var inDoubleQuote = false;
|
|
654
|
+
var depth = 0; // Track parenthesis nesting depth
|
|
655
|
+
var buffer = ""; // Accumulate characters for current selector part
|
|
656
|
+
var parts = []; // Array of split selector parts
|
|
657
|
+
var inSingleQuote = false; // Track if we're inside single quotes
|
|
658
|
+
var inDoubleQuote = false; // Track if we're inside double quotes
|
|
365
659
|
var i, char;
|
|
366
660
|
|
|
367
661
|
for (i = 0; i < selector.length; i++) {
|
|
368
662
|
char = selector.charAt(i);
|
|
369
663
|
|
|
664
|
+
// Handle single quote strings
|
|
370
665
|
if (char === "'" && !inDoubleQuote) {
|
|
371
666
|
inSingleQuote = !inSingleQuote;
|
|
372
667
|
buffer += char;
|
|
373
|
-
}
|
|
668
|
+
}
|
|
669
|
+
// Handle double quote strings
|
|
670
|
+
else if (char === '"' && !inSingleQuote) {
|
|
374
671
|
inDoubleQuote = !inDoubleQuote;
|
|
375
672
|
buffer += char;
|
|
376
|
-
}
|
|
673
|
+
}
|
|
674
|
+
// Process characters outside of quoted strings
|
|
675
|
+
else if (!inSingleQuote && !inDoubleQuote) {
|
|
377
676
|
if (char === '(') {
|
|
677
|
+
// Entering a nested level (e.g., :is(...))
|
|
378
678
|
depth++;
|
|
379
679
|
buffer += char;
|
|
380
680
|
} else if (char === ')') {
|
|
681
|
+
// Exiting a nested level
|
|
381
682
|
depth--;
|
|
382
683
|
buffer += char;
|
|
383
|
-
if (depth === 0) {
|
|
384
|
-
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
385
|
-
buffer = "";
|
|
386
|
-
}
|
|
387
684
|
} else if (char === ',' && depth === 0) {
|
|
388
|
-
|
|
389
|
-
|
|
685
|
+
// Found a top-level comma separator - split here
|
|
686
|
+
if (buffer.trim()) {
|
|
687
|
+
parts.push(buffer.trim());
|
|
390
688
|
}
|
|
391
689
|
buffer = "";
|
|
392
690
|
} else {
|
|
691
|
+
// Regular character - add to buffer
|
|
393
692
|
buffer += char;
|
|
394
693
|
}
|
|
395
|
-
}
|
|
694
|
+
}
|
|
695
|
+
// Characters inside quoted strings - add to buffer
|
|
696
|
+
else {
|
|
396
697
|
buffer += char;
|
|
397
698
|
}
|
|
398
699
|
}
|
|
399
700
|
|
|
400
|
-
|
|
401
|
-
|
|
701
|
+
// Add any remaining content in buffer as the last part
|
|
702
|
+
if (buffer.trim()) {
|
|
703
|
+
parts.push(buffer.trim());
|
|
402
704
|
}
|
|
403
705
|
|
|
404
706
|
return parts;
|
|
@@ -415,8 +717,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
415
717
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
416
718
|
*/
|
|
417
719
|
|
|
418
|
-
// Cache to store validated selectors
|
|
419
|
-
var validatedSelectorsCache =
|
|
720
|
+
// Cache to store validated selectors (previously a ES6 Map, now an ES5-compliant object)
|
|
721
|
+
var validatedSelectorsCache = {};
|
|
420
722
|
|
|
421
723
|
// Only pseudo-classes that accept selector lists should recurse
|
|
422
724
|
var selectorListPseudoClasses = {
|
|
@@ -427,8 +729,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
427
729
|
};
|
|
428
730
|
|
|
429
731
|
function validateSelector(selector) {
|
|
430
|
-
if (validatedSelectorsCache.
|
|
431
|
-
return validatedSelectorsCache
|
|
732
|
+
if (validatedSelectorsCache.hasOwnProperty(selector)) {
|
|
733
|
+
return validatedSelectorsCache[selector];
|
|
432
734
|
}
|
|
433
735
|
|
|
434
736
|
// Use a non-global regex to find all pseudo-classes with arguments
|
|
@@ -445,15 +747,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
445
747
|
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
446
748
|
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
447
749
|
var nestedSelector = nestedSelectors[i];
|
|
448
|
-
if (!validatedSelectorsCache.
|
|
750
|
+
if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
|
|
449
751
|
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
450
|
-
validatedSelectorsCache
|
|
752
|
+
validatedSelectorsCache[nestedSelector] = nestedSelectorValidation;
|
|
451
753
|
if (!nestedSelectorValidation) {
|
|
452
|
-
validatedSelectorsCache
|
|
754
|
+
validatedSelectorsCache[selector] = false;
|
|
453
755
|
return false;
|
|
454
756
|
}
|
|
455
|
-
} else if (!validatedSelectorsCache
|
|
456
|
-
validatedSelectorsCache
|
|
757
|
+
} else if (!validatedSelectorsCache[nestedSelector]) {
|
|
758
|
+
validatedSelectorsCache[selector] = false;
|
|
457
759
|
return false;
|
|
458
760
|
}
|
|
459
761
|
}
|
|
@@ -461,7 +763,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
461
763
|
}
|
|
462
764
|
|
|
463
765
|
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
464
|
-
validatedSelectorsCache
|
|
766
|
+
validatedSelectorsCache[selector] = basicSelectorValidation;
|
|
465
767
|
|
|
466
768
|
return basicSelectorValidation;
|
|
467
769
|
}
|
|
@@ -525,6 +827,28 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
525
827
|
return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
|
|
526
828
|
}
|
|
527
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Processes a CSS selector text
|
|
832
|
+
*
|
|
833
|
+
* @param {string} selectorText - The CSS selector text to process
|
|
834
|
+
* @returns {string} The processed selector text with normalized whitespace
|
|
835
|
+
*/
|
|
836
|
+
function processSelectorText(selectorText) {
|
|
837
|
+
// TODO: Remove invalid selectors that appears inside pseudo classes
|
|
838
|
+
// TODO: The same processing here needs to be reused in CSSStyleRule.selectorText setter
|
|
839
|
+
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Normalizes whitespace and preserving quoted strings.
|
|
843
|
+
* Replaces all newline characters (CRLF, CR, or LF) with spaces while keeping quoted
|
|
844
|
+
* strings (single or double quotes) intact, including any escaped characters within them.
|
|
845
|
+
*/
|
|
846
|
+
return selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
|
|
847
|
+
if (newline) return " ";
|
|
848
|
+
return match;
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
|
|
528
852
|
/**
|
|
529
853
|
* Checks if a given CSS selector text is valid by splitting it by commas
|
|
530
854
|
* and validating each individual selector using the `validateSelector` function.
|
|
@@ -533,6 +857,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
533
857
|
* @returns {boolean} Returns true if all selectors are valid, otherwise false.
|
|
534
858
|
*/
|
|
535
859
|
function isValidSelectorText(selectorText) {
|
|
860
|
+
// TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
|
|
861
|
+
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
862
|
+
|
|
536
863
|
// Check for newlines inside single or double quotes using regex
|
|
537
864
|
// This matches any quoted string (single or double) containing a newline
|
|
538
865
|
var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
|
|
@@ -553,6 +880,23 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
553
880
|
return true;
|
|
554
881
|
}
|
|
555
882
|
|
|
883
|
+
function parseError(message) {
|
|
884
|
+
var lines = token.substring(0, i).split('\n');
|
|
885
|
+
var lineCount = lines.length;
|
|
886
|
+
var charCount = lines.pop().length + 1;
|
|
887
|
+
var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
|
|
888
|
+
error.line = lineCount;
|
|
889
|
+
/* jshint sub : true */
|
|
890
|
+
error['char'] = charCount;
|
|
891
|
+
error.styleSheet = styleSheet;
|
|
892
|
+
// Print the error but continue parsing the sheet
|
|
893
|
+
try {
|
|
894
|
+
throw error;
|
|
895
|
+
} catch(e) {
|
|
896
|
+
errorHandler && errorHandler(e);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
|
|
556
900
|
var endingIndex = token.length - 1;
|
|
557
901
|
|
|
558
902
|
for (var character; (character = token.charAt(i)); i++) {
|
|
@@ -642,7 +986,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
642
986
|
i += 2;
|
|
643
987
|
index = token.indexOf("*/", i);
|
|
644
988
|
if (index === -1) {
|
|
645
|
-
|
|
989
|
+
i = token.length - 1;
|
|
990
|
+
buffer = "";
|
|
646
991
|
} else {
|
|
647
992
|
i = index + 1;
|
|
648
993
|
}
|
|
@@ -697,6 +1042,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
697
1042
|
}, true);
|
|
698
1043
|
buffer = "";
|
|
699
1044
|
break;
|
|
1045
|
+
} else if (token.indexOf("@scope", i) === i) {
|
|
1046
|
+
validateAtRule("@scope", function(){
|
|
1047
|
+
state = "scopeBlock";
|
|
1048
|
+
scopeRule = new CSSOM.CSSScopeRule();
|
|
1049
|
+
scopeRule.__starts = i;
|
|
1050
|
+
i += "scope".length;
|
|
1051
|
+
});
|
|
1052
|
+
buffer = "";
|
|
1053
|
+
break;
|
|
700
1054
|
} else if (token.indexOf("@layer", i) === i) {
|
|
701
1055
|
validateAtRule("@layer", function(){
|
|
702
1056
|
state = "layerBlock"
|
|
@@ -706,6 +1060,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
706
1060
|
});
|
|
707
1061
|
buffer = "";
|
|
708
1062
|
break;
|
|
1063
|
+
} else if (token.indexOf("@page", i) === i) {
|
|
1064
|
+
validateAtRule("@page", function(){
|
|
1065
|
+
state = "pageBlock"
|
|
1066
|
+
pageRule = new CSSOM.CSSPageRule();
|
|
1067
|
+
pageRule.__starts = i;
|
|
1068
|
+
i += "page".length;
|
|
1069
|
+
});
|
|
1070
|
+
buffer = "";
|
|
1071
|
+
break;
|
|
709
1072
|
} else if (token.indexOf("@supports", i) === i) {
|
|
710
1073
|
validateAtRule("@supports", function(){
|
|
711
1074
|
state = "conditionBlock";
|
|
@@ -803,10 +1166,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
803
1166
|
}
|
|
804
1167
|
|
|
805
1168
|
currentScope = parentRule = styleRule;
|
|
806
|
-
styleRule.selectorText = buffer.trim()
|
|
807
|
-
if (newline) return " ";
|
|
808
|
-
return match;
|
|
809
|
-
});
|
|
1169
|
+
styleRule.selectorText = processSelectorText(buffer.trim());
|
|
810
1170
|
styleRule.style.__starts = i;
|
|
811
1171
|
styleRule.__parentStyleSheet = styleSheet;
|
|
812
1172
|
buffer = "";
|
|
@@ -824,7 +1184,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
824
1184
|
buffer = "";
|
|
825
1185
|
state = "before-selector";
|
|
826
1186
|
} else if (state === "containerBlock") {
|
|
827
|
-
containerRule.
|
|
1187
|
+
containerRule.__conditionText = buffer.trim();
|
|
828
1188
|
|
|
829
1189
|
if (parentRule) {
|
|
830
1190
|
containerRule.__parentRule = parentRule;
|
|
@@ -841,7 +1201,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
841
1201
|
counterStyleRule.__parentStyleSheet = styleSheet;
|
|
842
1202
|
buffer = "";
|
|
843
1203
|
} else if (state === "conditionBlock") {
|
|
844
|
-
supportsRule.
|
|
1204
|
+
supportsRule.__conditionText = buffer.trim();
|
|
845
1205
|
|
|
846
1206
|
if (parentRule) {
|
|
847
1207
|
supportsRule.__parentRule = parentRule;
|
|
@@ -852,10 +1212,31 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
852
1212
|
supportsRule.__parentStyleSheet = styleSheet;
|
|
853
1213
|
buffer = "";
|
|
854
1214
|
state = "before-selector";
|
|
1215
|
+
} else if (state === "scopeBlock") {
|
|
1216
|
+
var parsedScopePrelude = parseScopePrelude(buffer.trim());
|
|
1217
|
+
|
|
1218
|
+
if (parsedScopePrelude.hasStart) {
|
|
1219
|
+
scopeRule.__start = parsedScopePrelude.startSelector;
|
|
1220
|
+
}
|
|
1221
|
+
if (parsedScopePrelude.hasEnd) {
|
|
1222
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1223
|
+
}
|
|
1224
|
+
if (parsedScopePrelude.hasOnlyEnd) {
|
|
1225
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (parentRule) {
|
|
1229
|
+
scopeRule.__parentRule = parentRule;
|
|
1230
|
+
ancestorRules.push(parentRule);
|
|
1231
|
+
}
|
|
1232
|
+
currentScope = parentRule = scopeRule;
|
|
1233
|
+
scopeRule.__parentStyleSheet = styleSheet;
|
|
1234
|
+
buffer = "";
|
|
1235
|
+
state = "before-selector";
|
|
855
1236
|
} else if (state === "layerBlock") {
|
|
856
1237
|
layerBlockRule.name = buffer.trim();
|
|
857
1238
|
|
|
858
|
-
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(
|
|
1239
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
|
|
859
1240
|
|
|
860
1241
|
if (isValidName) {
|
|
861
1242
|
if (parentRule) {
|
|
@@ -868,6 +1249,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
868
1249
|
}
|
|
869
1250
|
buffer = "";
|
|
870
1251
|
state = "before-selector";
|
|
1252
|
+
} else if (state === "pageBlock") {
|
|
1253
|
+
pageRule.selectorText = buffer.trim();
|
|
1254
|
+
|
|
1255
|
+
if (parentRule) {
|
|
1256
|
+
pageRule.__parentRule = parentRule;
|
|
1257
|
+
ancestorRules.push(parentRule);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
currentScope = parentRule = pageRule;
|
|
1261
|
+
pageRule.__parentStyleSheet = styleSheet;
|
|
1262
|
+
styleRule = pageRule;
|
|
1263
|
+
buffer = "";
|
|
1264
|
+
state = "before-name";
|
|
871
1265
|
} else if (state === "hostRule-begin") {
|
|
872
1266
|
if (parentRule) {
|
|
873
1267
|
ancestorRules.push(parentRule);
|
|
@@ -941,17 +1335,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
941
1335
|
}
|
|
942
1336
|
|
|
943
1337
|
styleRule = new CSSOM.CSSStyleRule();
|
|
944
|
-
var processedSelectorText = buffer.trim()
|
|
945
|
-
if (newline) return " ";
|
|
946
|
-
return match;
|
|
947
|
-
});
|
|
1338
|
+
var processedSelectorText = processSelectorText(buffer.trim());
|
|
948
1339
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
949
1340
|
if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
|
|
950
1341
|
styleRule.selectorText = processedSelectorText;
|
|
951
1342
|
} else {
|
|
952
1343
|
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
|
|
953
|
-
|
|
954
|
-
|
|
1344
|
+
// Add & at the beginning if there's no & in the selector, or if it starts with a combinator
|
|
1345
|
+
return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
|
|
1346
|
+
}).join(', ');
|
|
955
1347
|
}
|
|
956
1348
|
styleRule.style.__starts = i - buffer.length;
|
|
957
1349
|
styleRule.__parentRule = parentRule;
|
|
@@ -1067,7 +1459,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1067
1459
|
testNamespaceRule.cssText = buffer + character;
|
|
1068
1460
|
|
|
1069
1461
|
namespaceRule = testNamespaceRule;
|
|
1070
|
-
namespaceRule.__parentStyleSheet =
|
|
1462
|
+
namespaceRule.__parentStyleSheet = styleSheet;
|
|
1071
1463
|
styleSheet.cssRules.push(namespaceRule);
|
|
1072
1464
|
|
|
1073
1465
|
// Track the namespace prefix for validation
|
|
@@ -1086,7 +1478,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1086
1478
|
return name.trim();
|
|
1087
1479
|
});
|
|
1088
1480
|
var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
|
|
1089
|
-
return name.trim().match(
|
|
1481
|
+
return name.trim().match(cssCustomIdentifierRegExp) === null;
|
|
1090
1482
|
});
|
|
1091
1483
|
|
|
1092
1484
|
if (!isInvalid) {
|
|
@@ -1187,6 +1579,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1187
1579
|
|| parentRule.constructor.name === "CSSMediaRule"
|
|
1188
1580
|
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
1189
1581
|
|| parentRule.constructor.name === "CSSContainerRule"
|
|
1582
|
+
|| parentRule.constructor.name === "CSSScopeRule"
|
|
1190
1583
|
|| parentRule.constructor.name === "CSSLayerBlockRule"
|
|
1191
1584
|
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
|
1192
1585
|
) {
|
|
@@ -1256,6 +1649,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1256
1649
|
|| parentRule.constructor.name === "CSSMediaRule"
|
|
1257
1650
|
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
1258
1651
|
|| parentRule.constructor.name === "CSSContainerRule"
|
|
1652
|
+
|| parentRule.constructor.name === "CSSScopeRule"
|
|
1259
1653
|
|| parentRule.constructor.name === "CSSLayerBlockRule"
|
|
1260
1654
|
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
|
1261
1655
|
) {
|
|
@@ -1311,6 +1705,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1311
1705
|
}
|
|
1312
1706
|
}
|
|
1313
1707
|
|
|
1708
|
+
if (buffer.trim() !== "") {
|
|
1709
|
+
parseError("Unexpected end of input");
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1314
1712
|
return styleSheet;
|
|
1315
1713
|
};
|
|
1316
1714
|
|
|
@@ -1337,8 +1735,10 @@ CSSOM.CSSKeyframeRule = require('./CSSKeyframeRule').CSSKeyframeRule;
|
|
|
1337
1735
|
CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
|
|
1338
1736
|
CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
|
|
1339
1737
|
CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
|
|
1738
|
+
CSSOM.CSSScopeRule = require('./CSSScopeRule').CSSScopeRule;
|
|
1340
1739
|
CSSOM.CSSLayerBlockRule = require("./CSSLayerBlockRule").CSSLayerBlockRule;
|
|
1341
1740
|
CSSOM.CSSLayerStatementRule = require("./CSSLayerStatementRule").CSSLayerStatementRule;
|
|
1741
|
+
CSSOM.CSSPageRule = require("./CSSPageRule").CSSPageRule;
|
|
1342
1742
|
// Use cssstyle if available
|
|
1343
1743
|
try {
|
|
1344
1744
|
CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
|