@acemir/cssom 0.9.24 → 0.9.26
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 +1505 -1236
- package/lib/CSSConditionRule.js +3 -1
- package/lib/CSSContainerRule.js +9 -5
- package/lib/CSSCounterStyleRule.js +36 -2
- package/lib/CSSDocumentRule.js +9 -2
- package/lib/CSSFontFaceRule.js +9 -2
- package/lib/CSSGroupingRule.js +65 -9
- package/lib/CSSHostRule.js +9 -2
- package/lib/CSSImportRule.js +49 -39
- package/lib/CSSKeyframeRule.js +9 -2
- package/lib/CSSKeyframesRule.js +8 -2
- package/lib/CSSLayerBlockRule.js +9 -5
- package/lib/CSSLayerStatementRule.js +9 -5
- package/lib/CSSMediaRule.js +9 -5
- package/lib/CSSNamespaceRule.js +27 -17
- package/lib/CSSNestedDeclarations.js +9 -5
- package/lib/CSSOM.js +16 -1
- package/lib/CSSPageRule.js +3 -152
- package/lib/CSSRule.js +10 -0
- package/lib/CSSScopeRule.js +2 -1
- package/lib/CSSStartingStyleRule.js +9 -2
- package/lib/CSSStyleRule.js +3 -154
- package/lib/CSSStyleSheet.js +50 -9
- package/lib/CSSSupportsRule.js +8 -2
- package/lib/CSSValueExpression.js +3 -1
- package/lib/MediaList.js +22 -7
- package/lib/StyleSheet.js +24 -1
- package/lib/errorUtils.js +8 -13
- package/lib/index.js +2 -0
- package/lib/parse.js +1089 -791
- package/package.json +1 -1
package/lib/parse.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
//.CommonJS
|
|
2
|
-
var CSSOM = {
|
|
2
|
+
var CSSOM = {
|
|
3
|
+
setup: require('./CSSOM').setup
|
|
4
|
+
};
|
|
3
5
|
///CommonJS
|
|
4
6
|
|
|
5
|
-
|
|
6
7
|
/**
|
|
7
8
|
* Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
|
|
8
9
|
*
|
|
9
10
|
* @param {string} token - The CSS string to parse.
|
|
10
11
|
* @param {object} [opts] - Optional parsing options.
|
|
11
|
-
* @param {object} [opts.globalObject] - An optional global object to
|
|
12
|
+
* @param {object} [opts.globalObject] - @deprecated This property will be removed in the next release. Use CSSOM.setup({ globalObject }) instead. - An optional global object to override globals and window. Useful on jsdom webplatform tests.
|
|
13
|
+
* @param {Element | ProcessingInstruction} [opts.ownerNode] - The owner node of the stylesheet.
|
|
14
|
+
* @param {CSSRule} [opts.ownerRule] - The owner rule of the stylesheet.
|
|
12
15
|
* @param {CSSOM.CSSStyleSheet} [opts.styleSheet] - Reuse a style sheet instead of creating a new one (e.g. as `parentStyleSheet`)
|
|
13
16
|
* @param {CSSOM.CSSRuleList} [opts.cssRules] - Prepare all rules in this list instead of mutating the style sheet continually
|
|
14
17
|
* @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
|
|
@@ -62,6 +65,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
62
65
|
styleSheet = opts.styleSheet;
|
|
63
66
|
} else {
|
|
64
67
|
styleSheet = new CSSOM.CSSStyleSheet()
|
|
68
|
+
styleSheet.__constructed = false;
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
var topScope;
|
|
@@ -72,7 +76,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
if (opts && opts.globalObject) {
|
|
75
|
-
|
|
79
|
+
CSSOM.setup({ globalObject: opts.globalObject });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (opts && opts.ownerNode) {
|
|
83
|
+
styleSheet.__ownerNode = opts.ownerNode;
|
|
84
|
+
var ownerNodeMedia = opts.ownerNode.media || (opts.ownerNode.getAttribute && opts.ownerNode.getAttribute("media"));
|
|
85
|
+
if (ownerNodeMedia) {
|
|
86
|
+
styleSheet.media.mediaText = ownerNodeMedia;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (opts && opts.ownerRule) {
|
|
91
|
+
styleSheet.__ownerRule = opts.ownerRule;
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
// @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
|
@@ -84,7 +100,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
84
100
|
var ancestorRules = [];
|
|
85
101
|
var prevScope;
|
|
86
102
|
|
|
87
|
-
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
103
|
+
var name, priority = "", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
88
104
|
|
|
89
105
|
// Track defined namespace prefixes for validation
|
|
90
106
|
var definedNamespacePrefixes = {};
|
|
@@ -182,14 +198,14 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
182
198
|
var ruleClosingMatch = matchBalancedBlock(str, fromIndex);
|
|
183
199
|
if (ruleClosingMatch) {
|
|
184
200
|
var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
|
|
185
|
-
i+= ignoreRange;
|
|
201
|
+
i += ignoreRange;
|
|
186
202
|
if (token.charAt(i) === '}') {
|
|
187
203
|
i -= 1;
|
|
188
204
|
}
|
|
189
205
|
} else {
|
|
190
206
|
i += str.length;
|
|
191
207
|
}
|
|
192
|
-
return i;
|
|
208
|
+
return i;
|
|
193
209
|
}
|
|
194
210
|
|
|
195
211
|
/**
|
|
@@ -199,29 +215,29 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
199
215
|
*/
|
|
200
216
|
function parseScopePrelude(preludeContent) {
|
|
201
217
|
var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
|
|
202
|
-
|
|
218
|
+
|
|
203
219
|
// Restore the parentheses that were consumed by the split
|
|
204
220
|
if (parts.length === 2) {
|
|
205
221
|
parts[0] = parts[0] + ')';
|
|
206
222
|
parts[1] = '(' + parts[1];
|
|
207
223
|
}
|
|
208
|
-
|
|
224
|
+
|
|
209
225
|
var hasStart = parts[0] &&
|
|
210
226
|
parts[0].charAt(0) === '(' &&
|
|
211
227
|
parts[0].charAt(parts[0].length - 1) === ')';
|
|
212
228
|
var hasEnd = parts[1] &&
|
|
213
229
|
parts[1].charAt(0) === '(' &&
|
|
214
230
|
parts[1].charAt(parts[1].length - 1) === ')';
|
|
215
|
-
|
|
231
|
+
|
|
216
232
|
// Handle case: @scope to (<end>)
|
|
217
233
|
var hasOnlyEnd = !hasStart &&
|
|
218
234
|
!hasEnd &&
|
|
219
235
|
parts[0].indexOf('to (') === 0 &&
|
|
220
236
|
parts[0].charAt(parts[0].length - 1) === ')';
|
|
221
|
-
|
|
237
|
+
|
|
222
238
|
var startSelector = '';
|
|
223
239
|
var endSelector = '';
|
|
224
|
-
|
|
240
|
+
|
|
225
241
|
if (hasStart) {
|
|
226
242
|
startSelector = parts[0].slice(1, -1).trim();
|
|
227
243
|
}
|
|
@@ -231,7 +247,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
231
247
|
if (hasOnlyEnd) {
|
|
232
248
|
endSelector = parts[0].slice(4, -1).trim();
|
|
233
249
|
}
|
|
234
|
-
|
|
250
|
+
|
|
235
251
|
return {
|
|
236
252
|
startSelector: startSelector,
|
|
237
253
|
endSelector: endSelector,
|
|
@@ -269,11 +285,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
269
285
|
var inDoubleQuote = false;
|
|
270
286
|
var inAttr = false;
|
|
271
287
|
var stack = useStack ? [] : null;
|
|
272
|
-
|
|
288
|
+
|
|
273
289
|
for (var i = 0; i < selector.length; i++) {
|
|
274
290
|
var char = selector[i];
|
|
275
291
|
var prevChar = i > 0 ? selector[i - 1] : '';
|
|
276
|
-
|
|
292
|
+
|
|
277
293
|
if (inSingleQuote) {
|
|
278
294
|
if (char === "'" && prevChar !== "\\") {
|
|
279
295
|
inSingleQuote = false;
|
|
@@ -324,7 +340,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
324
340
|
}
|
|
325
341
|
}
|
|
326
342
|
}
|
|
327
|
-
|
|
343
|
+
|
|
328
344
|
// Check if everything is balanced
|
|
329
345
|
if (useStack) {
|
|
330
346
|
return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
|
|
@@ -374,7 +390,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
374
390
|
/(?:^|[\s>+~,\[])cue\s*\(/i,
|
|
375
391
|
/(?:^|[\s>+~,\[])cue-region\s*\(/i
|
|
376
392
|
];
|
|
377
|
-
|
|
393
|
+
|
|
378
394
|
for (var i = 0; i < invalidPatterns.length; i++) {
|
|
379
395
|
if (invalidPatterns[i].test(selector)) {
|
|
380
396
|
return true;
|
|
@@ -420,7 +436,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
420
436
|
isValid = true;
|
|
421
437
|
}
|
|
422
438
|
}
|
|
423
|
-
|
|
439
|
+
|
|
424
440
|
// Additional validation for @scope rule
|
|
425
441
|
if (isValid && atRuleKey === "@scope") {
|
|
426
442
|
var openBraceIndex = ruleSlice.indexOf('{');
|
|
@@ -439,7 +455,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
439
455
|
var hasStart = parsedScopePrelude.hasStart;
|
|
440
456
|
var hasEnd = parsedScopePrelude.hasEnd;
|
|
441
457
|
var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
|
|
442
|
-
|
|
458
|
+
|
|
443
459
|
// Validation rules for @scope:
|
|
444
460
|
// 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
|
|
445
461
|
if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
|
|
@@ -475,13 +491,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
475
491
|
if (openBraceIndex !== -1) {
|
|
476
492
|
// Extract the rule prelude (everything between the at-rule and {)
|
|
477
493
|
var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
|
|
478
|
-
|
|
494
|
+
|
|
479
495
|
// Skip past at-rule keyword and whitespace
|
|
480
496
|
var preludeContent = rulePrelude.slice("@page".length).trim();
|
|
481
497
|
|
|
482
498
|
if (preludeContent.length > 0) {
|
|
483
499
|
var trimmedValue = preludeContent.trim();
|
|
484
|
-
|
|
500
|
+
|
|
485
501
|
// Empty selector is valid for @page
|
|
486
502
|
if (trimmedValue !== '') {
|
|
487
503
|
// Parse @page selectorText for page name and pseudo-pages
|
|
@@ -505,7 +521,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
505
521
|
|
|
506
522
|
// Validate pseudo-pages if present
|
|
507
523
|
if (pseudoPages) {
|
|
508
|
-
var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
|
|
524
|
+
var pseudos = pseudoPages.split(':').filter(function (p) { return p; });
|
|
509
525
|
var validPseudos = ['left', 'right', 'first', 'blank'];
|
|
510
526
|
var allValid = true;
|
|
511
527
|
for (var j = 0; j < pseudos.length; j++) {
|
|
@@ -514,7 +530,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
514
530
|
break;
|
|
515
531
|
}
|
|
516
532
|
}
|
|
517
|
-
|
|
533
|
+
|
|
518
534
|
if (!allValid) {
|
|
519
535
|
isValid = false;
|
|
520
536
|
}
|
|
@@ -523,15 +539,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
523
539
|
isValid = false;
|
|
524
540
|
}
|
|
525
541
|
}
|
|
526
|
-
|
|
542
|
+
|
|
527
543
|
}
|
|
528
544
|
}
|
|
529
545
|
}
|
|
530
|
-
|
|
546
|
+
|
|
531
547
|
if (!isValid) {
|
|
532
548
|
// If it's invalid the browser will simply ignore the entire invalid block
|
|
533
549
|
// Use regex to find the closing brace of the invalid rule
|
|
534
|
-
|
|
550
|
+
|
|
535
551
|
// Regex used above is not ES5 compliant. Using alternative.
|
|
536
552
|
// var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
|
|
537
553
|
var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
|
|
@@ -546,7 +562,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
546
562
|
// Check if there's a semicolon before the invalid at-rule and the first opening brace
|
|
547
563
|
if (atRuleKey === "@layer") {
|
|
548
564
|
var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
|
|
549
|
-
if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";"
|
|
565
|
+
if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";") {
|
|
550
566
|
// Ignore the rule block until the semicolon
|
|
551
567
|
i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
|
|
552
568
|
state = "before-selector";
|
|
@@ -564,7 +580,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
564
580
|
|
|
565
581
|
// Helper functions for looseSelectorValidator
|
|
566
582
|
// Defined outside to avoid recreation on every validation call
|
|
567
|
-
|
|
583
|
+
|
|
568
584
|
/**
|
|
569
585
|
* Check if character is a valid identifier start
|
|
570
586
|
* @param {string} c - Character to check
|
|
@@ -636,19 +652,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
636
652
|
// Helper: Parse namespace prefix (optional)
|
|
637
653
|
function parseNamespace() {
|
|
638
654
|
var start = i;
|
|
639
|
-
|
|
655
|
+
|
|
640
656
|
// Match: *| or identifier| or |
|
|
641
657
|
if (i < len && selector[i] === '*') {
|
|
642
658
|
i++;
|
|
643
659
|
} else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
|
|
644
660
|
parseIdentifier();
|
|
645
661
|
}
|
|
646
|
-
|
|
662
|
+
|
|
647
663
|
if (i < len && selector[i] === '|') {
|
|
648
664
|
i++;
|
|
649
665
|
return true;
|
|
650
666
|
}
|
|
651
|
-
|
|
667
|
+
|
|
652
668
|
// Rollback if no pipe found
|
|
653
669
|
i = start;
|
|
654
670
|
return false;
|
|
@@ -659,15 +675,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
659
675
|
if (i >= len || selector[i] !== '(') {
|
|
660
676
|
return false;
|
|
661
677
|
}
|
|
662
|
-
|
|
678
|
+
|
|
663
679
|
i++; // Skip opening paren
|
|
664
680
|
var depth = 1;
|
|
665
681
|
var inString = false;
|
|
666
682
|
var stringChar = '';
|
|
667
|
-
|
|
683
|
+
|
|
668
684
|
while (i < len && depth > 0) {
|
|
669
685
|
var c = selector[i];
|
|
670
|
-
|
|
686
|
+
|
|
671
687
|
if (c === '\\' && i + 1 < len) {
|
|
672
688
|
i += 2; // Skip escaped character
|
|
673
689
|
} else if (!inString && (c === '"' || c === '\'')) {
|
|
@@ -687,7 +703,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
687
703
|
i++;
|
|
688
704
|
}
|
|
689
705
|
}
|
|
690
|
-
|
|
706
|
+
|
|
691
707
|
return depth === 0;
|
|
692
708
|
}
|
|
693
709
|
|
|
@@ -789,7 +805,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
789
805
|
// Match type selector with optional namespace: [namespace|]identifier
|
|
790
806
|
else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|')) {
|
|
791
807
|
parseNamespace(); // Optional namespace prefix
|
|
792
|
-
|
|
808
|
+
|
|
793
809
|
if (i < len && selector[i] === '*') {
|
|
794
810
|
i++; // Universal selector
|
|
795
811
|
hasMatchedComponent = true;
|
|
@@ -865,36 +881,36 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
865
881
|
// Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
|
|
866
882
|
// Using iterative parsing instead of regex to avoid exponential backtracking
|
|
867
883
|
var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
|
|
868
|
-
|
|
884
|
+
|
|
869
885
|
for (var idx = 0; idx < selector.length; idx++) {
|
|
870
886
|
// Look for pseudo-class/element start
|
|
871
887
|
if (selector[idx] === ':') {
|
|
872
888
|
var pseudoStart = idx;
|
|
873
889
|
idx++;
|
|
874
|
-
|
|
890
|
+
|
|
875
891
|
// Skip second colon for pseudo-elements
|
|
876
892
|
if (idx < selector.length && selector[idx] === ':') {
|
|
877
893
|
idx++;
|
|
878
894
|
}
|
|
879
|
-
|
|
895
|
+
|
|
880
896
|
// Extract pseudo name
|
|
881
897
|
var nameStart = idx;
|
|
882
898
|
while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
|
|
883
899
|
idx++;
|
|
884
900
|
}
|
|
885
|
-
|
|
901
|
+
|
|
886
902
|
if (idx === nameStart) {
|
|
887
903
|
continue; // No name found
|
|
888
904
|
}
|
|
889
|
-
|
|
905
|
+
|
|
890
906
|
var pseudoName = selector.substring(nameStart, idx).toLowerCase();
|
|
891
|
-
|
|
907
|
+
|
|
892
908
|
// Check if this pseudo has arguments
|
|
893
909
|
if (idx < selector.length && selector[idx] === '(') {
|
|
894
910
|
idx++;
|
|
895
911
|
var contentStart = idx;
|
|
896
912
|
var depth = 1;
|
|
897
|
-
|
|
913
|
+
|
|
898
914
|
// Find matching closing paren (handle nesting)
|
|
899
915
|
while (idx < selector.length && depth > 0) {
|
|
900
916
|
if (selector[idx] === '\\') {
|
|
@@ -909,10 +925,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
909
925
|
idx++;
|
|
910
926
|
}
|
|
911
927
|
}
|
|
912
|
-
|
|
928
|
+
|
|
913
929
|
if (depth === 0) {
|
|
914
930
|
var pseudoContent = selector.substring(contentStart, idx - 1);
|
|
915
|
-
|
|
931
|
+
|
|
916
932
|
// Check if this pseudo should not have quoted strings
|
|
917
933
|
for (var j = 0; j < noQuotesPseudos.length; j++) {
|
|
918
934
|
if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
|
|
@@ -927,7 +943,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
927
943
|
// Use the iterative validator to avoid regex backtracking issues
|
|
928
944
|
return looseSelectorValidator(selector);
|
|
929
945
|
}
|
|
930
|
-
|
|
946
|
+
|
|
931
947
|
/**
|
|
932
948
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
933
949
|
*
|
|
@@ -946,7 +962,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
946
962
|
*
|
|
947
963
|
* REPLACED WITH FUNCTION to avoid exponential backtracking.
|
|
948
964
|
*/
|
|
949
|
-
|
|
965
|
+
|
|
950
966
|
/**
|
|
951
967
|
* Extract pseudo-classes with arguments from a selector using iterative parsing.
|
|
952
968
|
* Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
|
|
@@ -962,30 +978,30 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
962
978
|
*/
|
|
963
979
|
function extractPseudoClasses(selector) {
|
|
964
980
|
var matches = [];
|
|
965
|
-
|
|
981
|
+
|
|
966
982
|
for (var i = 0; i < selector.length; i++) {
|
|
967
983
|
// Look for pseudo-class start (single or double colon)
|
|
968
984
|
if (selector[i] === ':') {
|
|
969
985
|
var pseudoStart = i;
|
|
970
986
|
i++;
|
|
971
|
-
|
|
987
|
+
|
|
972
988
|
// Skip second colon for pseudo-elements (::)
|
|
973
989
|
if (i < selector.length && selector[i] === ':') {
|
|
974
990
|
i++;
|
|
975
991
|
}
|
|
976
|
-
|
|
992
|
+
|
|
977
993
|
// Extract pseudo name
|
|
978
994
|
var nameStart = i;
|
|
979
995
|
while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
|
|
980
996
|
i++;
|
|
981
997
|
}
|
|
982
|
-
|
|
998
|
+
|
|
983
999
|
if (i === nameStart) {
|
|
984
1000
|
continue; // No name found
|
|
985
1001
|
}
|
|
986
|
-
|
|
1002
|
+
|
|
987
1003
|
var pseudoName = selector.substring(nameStart, i);
|
|
988
|
-
|
|
1004
|
+
|
|
989
1005
|
// Check if this pseudo has arguments
|
|
990
1006
|
if (i < selector.length && selector[i] === '(') {
|
|
991
1007
|
i++;
|
|
@@ -993,11 +1009,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
993
1009
|
var depth = 1;
|
|
994
1010
|
var inSingleQuote = false;
|
|
995
1011
|
var inDoubleQuote = false;
|
|
996
|
-
|
|
1012
|
+
|
|
997
1013
|
// Find matching closing paren (handle nesting and strings)
|
|
998
1014
|
while (i < selector.length && depth > 0) {
|
|
999
1015
|
var ch = selector[i];
|
|
1000
|
-
|
|
1016
|
+
|
|
1001
1017
|
if (ch === '\\') {
|
|
1002
1018
|
i += 2; // Skip escaped character
|
|
1003
1019
|
} else if (ch === "'" && !inDoubleQuote) {
|
|
@@ -1016,21 +1032,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1016
1032
|
i++;
|
|
1017
1033
|
}
|
|
1018
1034
|
}
|
|
1019
|
-
|
|
1035
|
+
|
|
1020
1036
|
if (depth === 0) {
|
|
1021
1037
|
var pseudoArgs = selector.substring(argsStart, i - 1);
|
|
1022
1038
|
var fullMatch = selector.substring(pseudoStart, i);
|
|
1023
|
-
|
|
1039
|
+
|
|
1024
1040
|
// Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
|
|
1025
1041
|
matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
|
|
1026
1042
|
}
|
|
1027
|
-
|
|
1043
|
+
|
|
1028
1044
|
// Move back one since loop will increment
|
|
1029
1045
|
i--;
|
|
1030
1046
|
}
|
|
1031
1047
|
}
|
|
1032
1048
|
}
|
|
1033
|
-
|
|
1049
|
+
|
|
1034
1050
|
return matches;
|
|
1035
1051
|
}
|
|
1036
1052
|
|
|
@@ -1134,6 +1150,23 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1134
1150
|
var pseudoClass = pseudoClassMatches[j][1];
|
|
1135
1151
|
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1136
1152
|
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
1153
|
+
|
|
1154
|
+
// Check if ANY selector in the list contains & (nesting selector)
|
|
1155
|
+
// If so, skip validation for the entire selector list since & will be replaced at runtime
|
|
1156
|
+
var hasAmpersand = false;
|
|
1157
|
+
for (var k = 0; k < nestedSelectors.length; k++) {
|
|
1158
|
+
if (/&/.test(nestedSelectors[k])) {
|
|
1159
|
+
hasAmpersand = true;
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// If any selector has &, skip validation for this entire pseudo-class
|
|
1165
|
+
if (hasAmpersand) {
|
|
1166
|
+
continue;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Otherwise, validate each selector normally
|
|
1137
1170
|
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
1138
1171
|
var nestedSelector = nestedSelectors[i];
|
|
1139
1172
|
if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
|
|
@@ -1170,10 +1203,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1170
1203
|
var inAttr = false;
|
|
1171
1204
|
var inSingleQuote = false;
|
|
1172
1205
|
var inDoubleQuote = false;
|
|
1173
|
-
|
|
1206
|
+
|
|
1174
1207
|
for (var i = 0; i < selector.length; i++) {
|
|
1175
1208
|
var char = selector[i];
|
|
1176
|
-
|
|
1209
|
+
|
|
1177
1210
|
if (inSingleQuote) {
|
|
1178
1211
|
if (char === "'" && selector[i - 1] !== "\\") {
|
|
1179
1212
|
inSingleQuote = false;
|
|
@@ -1200,18 +1233,18 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1200
1233
|
}
|
|
1201
1234
|
}
|
|
1202
1235
|
}
|
|
1203
|
-
|
|
1236
|
+
|
|
1204
1237
|
if (pipeIndex === -1) {
|
|
1205
1238
|
return true; // No namespace, always valid
|
|
1206
1239
|
}
|
|
1207
|
-
|
|
1240
|
+
|
|
1208
1241
|
var namespacePrefix = selector.substring(0, pipeIndex);
|
|
1209
|
-
|
|
1242
|
+
|
|
1210
1243
|
// Universal namespace (*|) and default namespace (|) are always valid
|
|
1211
1244
|
if (namespacePrefix === '*' || namespacePrefix === '') {
|
|
1212
1245
|
return true;
|
|
1213
1246
|
}
|
|
1214
|
-
|
|
1247
|
+
|
|
1215
1248
|
// Check if the custom namespace prefix is defined
|
|
1216
1249
|
return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
|
|
1217
1250
|
}
|
|
@@ -1220,22 +1253,92 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1220
1253
|
* Processes a CSS selector text
|
|
1221
1254
|
*
|
|
1222
1255
|
* @param {string} selectorText - The CSS selector text to process
|
|
1223
|
-
* @returns {string} The processed selector text with normalized whitespace
|
|
1256
|
+
* @returns {string} The processed selector text with normalized whitespace and invalid selectors removed
|
|
1224
1257
|
*/
|
|
1225
1258
|
function processSelectorText(selectorText) {
|
|
1226
|
-
//
|
|
1227
|
-
|
|
1228
|
-
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Normalizes whitespace and preserving quoted strings.
|
|
1232
|
-
* Replaces all newline characters (CRLF, CR, or LF) with spaces while keeping quoted
|
|
1233
|
-
* strings (single or double quotes) intact, including any escaped characters within them.
|
|
1234
|
-
*/
|
|
1235
|
-
return selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
|
|
1259
|
+
// Normalize whitespace first
|
|
1260
|
+
var normalized = selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function (match, _, newline) {
|
|
1236
1261
|
if (newline) return " ";
|
|
1237
1262
|
return match;
|
|
1238
1263
|
});
|
|
1264
|
+
|
|
1265
|
+
// Recursively process pseudo-classes to handle nesting
|
|
1266
|
+
return processNestedPseudoClasses(normalized);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Recursively processes pseudo-classes to filter invalid selectors
|
|
1271
|
+
*
|
|
1272
|
+
* @param {string} selectorText - The CSS selector text to process
|
|
1273
|
+
* @param {number} depth - Current recursion depth (to prevent infinite loops)
|
|
1274
|
+
* @returns {string} The processed selector text with invalid selectors removed
|
|
1275
|
+
*/
|
|
1276
|
+
function processNestedPseudoClasses(selectorText, depth) {
|
|
1277
|
+
// Prevent infinite recursion
|
|
1278
|
+
if (typeof depth === 'undefined') {
|
|
1279
|
+
depth = 0;
|
|
1280
|
+
}
|
|
1281
|
+
if (depth > 10) {
|
|
1282
|
+
return selectorText;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
var pseudoClassMatches = extractPseudoClasses(selectorText);
|
|
1286
|
+
|
|
1287
|
+
// If no pseudo-classes found, return as-is
|
|
1288
|
+
if (pseudoClassMatches.length === 0) {
|
|
1289
|
+
return selectorText;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Build result by processing matches from right to left (to preserve positions)
|
|
1293
|
+
var result = selectorText;
|
|
1294
|
+
|
|
1295
|
+
for (var j = pseudoClassMatches.length - 1; j >= 0; j--) {
|
|
1296
|
+
var pseudoClass = pseudoClassMatches[j][1];
|
|
1297
|
+
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1298
|
+
var fullMatch = pseudoClassMatches[j][0];
|
|
1299
|
+
var pseudoArgs = pseudoClassMatches[j][2];
|
|
1300
|
+
var matchStart = pseudoClassMatches[j][3];
|
|
1301
|
+
|
|
1302
|
+
// Check if ANY selector contains & BEFORE processing
|
|
1303
|
+
var nestedSelectorsRaw = parseAndSplitNestedSelectors(pseudoArgs);
|
|
1304
|
+
var hasAmpersand = false;
|
|
1305
|
+
for (var k = 0; k < nestedSelectorsRaw.length; k++) {
|
|
1306
|
+
if (/&/.test(nestedSelectorsRaw[k])) {
|
|
1307
|
+
hasAmpersand = true;
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// If & is present, skip all processing (keep everything unchanged)
|
|
1313
|
+
if (hasAmpersand) {
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// Recursively process the arguments
|
|
1318
|
+
var processedArgs = processNestedPseudoClasses(pseudoArgs, depth + 1);
|
|
1319
|
+
var nestedSelectors = parseAndSplitNestedSelectors(processedArgs);
|
|
1320
|
+
|
|
1321
|
+
// Filter out invalid selectors
|
|
1322
|
+
var validSelectors = [];
|
|
1323
|
+
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
1324
|
+
var nestedSelector = nestedSelectors[i];
|
|
1325
|
+
if (basicSelectorValidator(nestedSelector)) {
|
|
1326
|
+
validSelectors.push(nestedSelector);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Reconstruct the pseudo-class with only valid selectors
|
|
1331
|
+
var newArgs = validSelectors.join(', ');
|
|
1332
|
+
var newPseudoClass = ':' + pseudoClass + '(' + newArgs + ')';
|
|
1333
|
+
|
|
1334
|
+
// Replace in the result string using position (processing right to left preserves positions)
|
|
1335
|
+
result = result.substring(0, matchStart) + newPseudoClass + result.substring(matchStart + fullMatch.length);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return result;
|
|
1340
|
+
|
|
1341
|
+
return normalized;
|
|
1239
1342
|
}
|
|
1240
1343
|
|
|
1241
1344
|
/**
|
|
@@ -1249,6 +1352,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1249
1352
|
// TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
|
|
1250
1353
|
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
1251
1354
|
|
|
1355
|
+
// Check for empty selector lists in pseudo-classes (e.g., :is(), :not(), :where(), :has())
|
|
1356
|
+
// These are invalid after filtering out invalid selectors
|
|
1357
|
+
if (/:(?:is|not|where|has)\(\s*\)/.test(selectorText)) {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1252
1361
|
// Check for newlines inside single or double quotes using regex
|
|
1253
1362
|
// This matches any quoted string (single or double) containing a newline
|
|
1254
1363
|
var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
|
|
@@ -1288,12 +1397,22 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1288
1397
|
// Print the error but continue parsing the sheet
|
|
1289
1398
|
try {
|
|
1290
1399
|
throw error;
|
|
1291
|
-
} catch(e) {
|
|
1400
|
+
} catch (e) {
|
|
1292
1401
|
errorHandler && errorHandler(e);
|
|
1293
1402
|
}
|
|
1294
1403
|
};
|
|
1295
1404
|
|
|
1405
|
+
// Helper functions to check character types
|
|
1406
|
+
function isSelectorStartChar(char) {
|
|
1407
|
+
return '.:#&*['.indexOf(char) !== -1;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
function isWhitespaceChar(char) {
|
|
1411
|
+
return ' \t\n\r'.indexOf(char) !== -1;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1296
1414
|
var endingIndex = token.length - 1;
|
|
1415
|
+
var initialEndingIndex = endingIndex;
|
|
1297
1416
|
|
|
1298
1417
|
for (var character; (character = token.charAt(i)); i++) {
|
|
1299
1418
|
if (i === endingIndex) {
|
|
@@ -1303,8 +1422,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1303
1422
|
case "layerBlock":
|
|
1304
1423
|
if (character !== ";") {
|
|
1305
1424
|
token += ";";
|
|
1306
|
-
|
|
1425
|
+
endingIndex += 1;
|
|
1307
1426
|
}
|
|
1427
|
+
break;
|
|
1308
1428
|
case "value":
|
|
1309
1429
|
if (character !== "}") {
|
|
1310
1430
|
if (character === ";") {
|
|
@@ -1332,7 +1452,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1332
1452
|
}
|
|
1333
1453
|
}
|
|
1334
1454
|
}
|
|
1335
|
-
|
|
1455
|
+
|
|
1336
1456
|
// Handle escape sequences before processing special characters
|
|
1337
1457
|
// If we encounter a backslash, add both the backslash and the next character to buffer
|
|
1338
1458
|
// and skip the next iteration to prevent the escaped character from being interpreted
|
|
@@ -1341,813 +1461,991 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
1341
1461
|
i++; // Skip the next character
|
|
1342
1462
|
continue;
|
|
1343
1463
|
}
|
|
1344
|
-
|
|
1464
|
+
|
|
1345
1465
|
switch (character) {
|
|
1346
1466
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
break;
|
|
1356
|
-
|
|
1357
|
-
// String
|
|
1358
|
-
case '"':
|
|
1359
|
-
index = i + 1;
|
|
1360
|
-
do {
|
|
1361
|
-
index = token.indexOf('"', index) + 1;
|
|
1362
|
-
if (!index) {
|
|
1363
|
-
parseError('Unmatched "');
|
|
1467
|
+
case " ":
|
|
1468
|
+
case "\t":
|
|
1469
|
+
case "\r":
|
|
1470
|
+
case "\n":
|
|
1471
|
+
case "\f":
|
|
1472
|
+
if (SIGNIFICANT_WHITESPACE[state]) {
|
|
1473
|
+
buffer += character;
|
|
1364
1474
|
}
|
|
1365
|
-
} while (token[index - 2] === '\\');
|
|
1366
|
-
if (index === 0) {
|
|
1367
1475
|
break;
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
state = 'importRule';
|
|
1377
|
-
if (i === endingIndex) {
|
|
1378
|
-
token += ';'
|
|
1476
|
+
|
|
1477
|
+
// String
|
|
1478
|
+
case '"':
|
|
1479
|
+
index = i + 1;
|
|
1480
|
+
do {
|
|
1481
|
+
index = token.indexOf('"', index) + 1;
|
|
1482
|
+
if (!index) {
|
|
1483
|
+
parseError('Unmatched "');
|
|
1379
1484
|
}
|
|
1485
|
+
} while (token[index - 2] === '\\');
|
|
1486
|
+
if (index === 0) {
|
|
1380
1487
|
break;
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1488
|
+
}
|
|
1489
|
+
buffer += token.slice(i, index);
|
|
1490
|
+
i = index - 1;
|
|
1491
|
+
switch (state) {
|
|
1492
|
+
case 'before-value':
|
|
1493
|
+
state = 'value';
|
|
1494
|
+
break;
|
|
1495
|
+
case 'importRule-begin':
|
|
1496
|
+
state = 'importRule';
|
|
1497
|
+
if (i === endingIndex) {
|
|
1498
|
+
token += ';'
|
|
1499
|
+
}
|
|
1500
|
+
break;
|
|
1501
|
+
case 'namespaceRule-begin':
|
|
1502
|
+
state = 'namespaceRule';
|
|
1503
|
+
if (i === endingIndex) {
|
|
1504
|
+
token += ';'
|
|
1505
|
+
}
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
break;
|
|
1509
|
+
|
|
1510
|
+
case "'":
|
|
1511
|
+
index = i + 1;
|
|
1512
|
+
do {
|
|
1513
|
+
index = token.indexOf("'", index) + 1;
|
|
1514
|
+
if (!index) {
|
|
1515
|
+
parseError("Unmatched '");
|
|
1385
1516
|
}
|
|
1517
|
+
} while (token[index - 2] === '\\');
|
|
1518
|
+
if (index === 0) {
|
|
1386
1519
|
break;
|
|
1387
|
-
}
|
|
1388
|
-
break;
|
|
1389
|
-
|
|
1390
|
-
case "'":
|
|
1391
|
-
index = i + 1;
|
|
1392
|
-
do {
|
|
1393
|
-
index = token.indexOf("'", index) + 1;
|
|
1394
|
-
if (!index) {
|
|
1395
|
-
parseError("Unmatched '");
|
|
1396
1520
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1521
|
+
buffer += token.slice(i, index);
|
|
1522
|
+
i = index - 1;
|
|
1523
|
+
switch (state) {
|
|
1524
|
+
case 'before-value':
|
|
1525
|
+
state = 'value';
|
|
1526
|
+
break;
|
|
1527
|
+
case 'importRule-begin':
|
|
1528
|
+
state = 'importRule';
|
|
1529
|
+
break;
|
|
1530
|
+
case 'namespaceRule-begin':
|
|
1531
|
+
state = 'namespaceRule';
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1399
1534
|
break;
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
break;
|
|
1413
|
-
}
|
|
1414
|
-
break;
|
|
1415
|
-
|
|
1416
|
-
// Comment
|
|
1417
|
-
case "/":
|
|
1418
|
-
if (token.charAt(i + 1) === "*") {
|
|
1419
|
-
i += 2;
|
|
1420
|
-
index = token.indexOf("*/", i);
|
|
1421
|
-
if (index === -1) {
|
|
1422
|
-
i = token.length - 1;
|
|
1423
|
-
buffer = "";
|
|
1535
|
+
|
|
1536
|
+
// Comment
|
|
1537
|
+
case "/":
|
|
1538
|
+
if (token.charAt(i + 1) === "*") {
|
|
1539
|
+
i += 2;
|
|
1540
|
+
index = token.indexOf("*/", i);
|
|
1541
|
+
if (index === -1) {
|
|
1542
|
+
i = token.length - 1;
|
|
1543
|
+
buffer = "";
|
|
1544
|
+
} else {
|
|
1545
|
+
i = index + 1;
|
|
1546
|
+
}
|
|
1424
1547
|
} else {
|
|
1425
|
-
|
|
1548
|
+
buffer += character;
|
|
1426
1549
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
if (state === "importRule-begin") {
|
|
1431
|
-
buffer += " ";
|
|
1432
|
-
state = "importRule";
|
|
1433
|
-
}
|
|
1434
|
-
if (state === "namespaceRule-begin") {
|
|
1435
|
-
buffer += " ";
|
|
1436
|
-
state = "namespaceRule";
|
|
1437
|
-
}
|
|
1438
|
-
break;
|
|
1439
|
-
|
|
1440
|
-
// At-rule
|
|
1441
|
-
case "@":
|
|
1442
|
-
if (nestedSelectorRule) {
|
|
1443
|
-
if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
1444
|
-
currentScope.cssRules.push(styleRule);
|
|
1550
|
+
if (state === "importRule-begin") {
|
|
1551
|
+
buffer += " ";
|
|
1552
|
+
state = "importRule";
|
|
1445
1553
|
}
|
|
1446
|
-
if (
|
|
1447
|
-
|
|
1554
|
+
if (state === "namespaceRule-begin") {
|
|
1555
|
+
buffer += " ";
|
|
1556
|
+
state = "namespaceRule";
|
|
1448
1557
|
}
|
|
1449
|
-
nestedSelectorRule = null;
|
|
1450
|
-
}
|
|
1451
|
-
if (token.indexOf("@-moz-document", i) === i) {
|
|
1452
|
-
validateAtRule("@-moz-document", function(){
|
|
1453
|
-
state = "documentRule-begin";
|
|
1454
|
-
documentRule = new CSSOM.CSSDocumentRule();
|
|
1455
|
-
documentRule.__starts = i;
|
|
1456
|
-
i += "-moz-document".length;
|
|
1457
|
-
});
|
|
1458
|
-
buffer = "";
|
|
1459
|
-
break;
|
|
1460
|
-
} else if (token.indexOf("@media", i) === i) {
|
|
1461
|
-
validateAtRule("@media", function(){
|
|
1462
|
-
state = "atBlock";
|
|
1463
|
-
mediaRule = new CSSOM.CSSMediaRule();
|
|
1464
|
-
mediaRule.__starts = i;
|
|
1465
|
-
i += "media".length;
|
|
1466
|
-
});
|
|
1467
|
-
buffer = "";
|
|
1468
|
-
break;
|
|
1469
|
-
} else if (token.indexOf("@container", i) === i) {
|
|
1470
|
-
validateAtRule("@container", function(){
|
|
1471
|
-
state = "containerBlock";
|
|
1472
|
-
containerRule = new CSSOM.CSSContainerRule();
|
|
1473
|
-
containerRule.__starts = i;
|
|
1474
|
-
i += "container".length;
|
|
1475
|
-
});
|
|
1476
|
-
buffer = "";
|
|
1477
|
-
break;
|
|
1478
|
-
} else if (token.indexOf("@counter-style", i) === i) {
|
|
1479
|
-
validateAtRule("@counter-style", function(){
|
|
1480
|
-
state = "counterStyleBlock"
|
|
1481
|
-
counterStyleRule = new CSSOM.CSSCounterStyleRule();
|
|
1482
|
-
counterStyleRule.__starts = i;
|
|
1483
|
-
i += "counter-style".length;
|
|
1484
|
-
}, true);
|
|
1485
|
-
buffer = "";
|
|
1486
|
-
break;
|
|
1487
|
-
} else if (token.indexOf("@scope", i) === i) {
|
|
1488
|
-
validateAtRule("@scope", function(){
|
|
1489
|
-
state = "scopeBlock";
|
|
1490
|
-
scopeRule = new CSSOM.CSSScopeRule();
|
|
1491
|
-
scopeRule.__starts = i;
|
|
1492
|
-
i += "scope".length;
|
|
1493
|
-
});
|
|
1494
|
-
buffer = "";
|
|
1495
|
-
break;
|
|
1496
|
-
} else if (token.indexOf("@layer", i) === i) {
|
|
1497
|
-
validateAtRule("@layer", function(){
|
|
1498
|
-
state = "layerBlock"
|
|
1499
|
-
layerBlockRule = new CSSOM.CSSLayerBlockRule();
|
|
1500
|
-
layerBlockRule.__starts = i;
|
|
1501
|
-
i += "layer".length;
|
|
1502
|
-
});
|
|
1503
|
-
buffer = "";
|
|
1504
|
-
break;
|
|
1505
|
-
} else if (token.indexOf("@page", i) === i) {
|
|
1506
|
-
validateAtRule("@page", function(){
|
|
1507
|
-
state = "pageBlock"
|
|
1508
|
-
pageRule = new CSSOM.CSSPageRule();
|
|
1509
|
-
pageRule.__starts = i;
|
|
1510
|
-
i += "page".length;
|
|
1511
|
-
});
|
|
1512
|
-
buffer = "";
|
|
1513
|
-
break;
|
|
1514
|
-
} else if (token.indexOf("@supports", i) === i) {
|
|
1515
|
-
validateAtRule("@supports", function(){
|
|
1516
|
-
state = "conditionBlock";
|
|
1517
|
-
supportsRule = new CSSOM.CSSSupportsRule();
|
|
1518
|
-
supportsRule.__starts = i;
|
|
1519
|
-
i += "supports".length;
|
|
1520
|
-
});
|
|
1521
|
-
buffer = "";
|
|
1522
1558
|
break;
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
buffer = "";
|
|
1543
|
-
validateAtRule("@import", function(){
|
|
1544
|
-
state = "importRule-begin";
|
|
1545
|
-
i += "import".length;
|
|
1546
|
-
buffer += "@import";
|
|
1547
|
-
}, true);
|
|
1548
|
-
break;
|
|
1549
|
-
} else if (token.indexOf("@namespace", i) === i) {
|
|
1550
|
-
buffer = "";
|
|
1551
|
-
validateAtRule("@namespace", function(){
|
|
1552
|
-
state = "namespaceRule-begin";
|
|
1553
|
-
i += "namespace".length;
|
|
1554
|
-
buffer += "@namespace";
|
|
1555
|
-
}, true);
|
|
1556
|
-
break;
|
|
1557
|
-
} else if (token.indexOf("@font-face", i) === i) {
|
|
1558
|
-
buffer = "";
|
|
1559
|
-
validateAtRule("@font-face", function(){
|
|
1560
|
-
state = "fontFaceRule-begin";
|
|
1561
|
-
i += "font-face".length;
|
|
1562
|
-
fontFaceRule = new CSSOM.CSSFontFaceRule();
|
|
1563
|
-
fontFaceRule.__starts = i;
|
|
1564
|
-
}, true);
|
|
1565
|
-
break;
|
|
1566
|
-
} else {
|
|
1567
|
-
atKeyframesRegExp.lastIndex = i;
|
|
1568
|
-
var matchKeyframes = atKeyframesRegExp.exec(token);
|
|
1569
|
-
if (matchKeyframes && matchKeyframes.index === i) {
|
|
1570
|
-
state = "keyframesRule-begin";
|
|
1571
|
-
keyframesRule = new CSSOM.CSSKeyframesRule();
|
|
1572
|
-
keyframesRule.__starts = i;
|
|
1573
|
-
keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
|
|
1574
|
-
i += matchKeyframes[0].length - 1;
|
|
1559
|
+
|
|
1560
|
+
// At-rule
|
|
1561
|
+
case "@":
|
|
1562
|
+
if (nestedSelectorRule) {
|
|
1563
|
+
if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
1564
|
+
currentScope.cssRules.push(styleRule);
|
|
1565
|
+
}
|
|
1566
|
+
if (nestedSelectorRule.parentRule && nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
|
|
1567
|
+
styleRule = nestedSelectorRule.parentRule;
|
|
1568
|
+
}
|
|
1569
|
+
// Don't reset nestedSelectorRule here - preserve it through @-rules
|
|
1570
|
+
}
|
|
1571
|
+
if (token.indexOf("@-moz-document", i) === i) {
|
|
1572
|
+
validateAtRule("@-moz-document", function () {
|
|
1573
|
+
state = "documentRule-begin";
|
|
1574
|
+
documentRule = new CSSOM.CSSDocumentRule();
|
|
1575
|
+
documentRule.__starts = i;
|
|
1576
|
+
i += "-moz-document".length;
|
|
1577
|
+
});
|
|
1575
1578
|
buffer = "";
|
|
1576
1579
|
break;
|
|
1577
|
-
} else if (
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1580
|
+
} else if (token.indexOf("@media", i) === i) {
|
|
1581
|
+
validateAtRule("@media", function () {
|
|
1582
|
+
state = "atBlock";
|
|
1583
|
+
mediaRule = new CSSOM.CSSMediaRule();
|
|
1584
|
+
mediaRule.__starts = i;
|
|
1585
|
+
i += "media".length;
|
|
1586
|
+
});
|
|
1587
|
+
buffer = "";
|
|
1588
|
+
break;
|
|
1589
|
+
} else if (token.indexOf("@container", i) === i) {
|
|
1590
|
+
validateAtRule("@container", function () {
|
|
1591
|
+
state = "containerBlock";
|
|
1592
|
+
containerRule = new CSSOM.CSSContainerRule();
|
|
1593
|
+
containerRule.__starts = i;
|
|
1594
|
+
i += "container".length;
|
|
1595
|
+
});
|
|
1596
|
+
buffer = "";
|
|
1597
|
+
break;
|
|
1598
|
+
} else if (token.indexOf("@counter-style", i) === i) {
|
|
1599
|
+
validateAtRule("@counter-style", function () {
|
|
1600
|
+
state = "counterStyleBlock"
|
|
1601
|
+
counterStyleRule = new CSSOM.CSSCounterStyleRule();
|
|
1602
|
+
counterStyleRule.__starts = i;
|
|
1603
|
+
i += "counter-style".length;
|
|
1604
|
+
}, true);
|
|
1605
|
+
buffer = "";
|
|
1606
|
+
break;
|
|
1607
|
+
} else if (token.indexOf("@scope", i) === i) {
|
|
1608
|
+
validateAtRule("@scope", function () {
|
|
1609
|
+
state = "scopeBlock";
|
|
1610
|
+
scopeRule = new CSSOM.CSSScopeRule();
|
|
1611
|
+
scopeRule.__starts = i;
|
|
1612
|
+
i += "scope".length;
|
|
1613
|
+
});
|
|
1614
|
+
buffer = "";
|
|
1615
|
+
break;
|
|
1616
|
+
} else if (token.indexOf("@layer", i) === i) {
|
|
1617
|
+
validateAtRule("@layer", function () {
|
|
1618
|
+
state = "layerBlock"
|
|
1619
|
+
layerBlockRule = new CSSOM.CSSLayerBlockRule();
|
|
1620
|
+
layerBlockRule.__starts = i;
|
|
1621
|
+
i += "layer".length;
|
|
1622
|
+
});
|
|
1623
|
+
buffer = "";
|
|
1624
|
+
break;
|
|
1625
|
+
} else if (token.indexOf("@page", i) === i) {
|
|
1626
|
+
validateAtRule("@page", function () {
|
|
1627
|
+
state = "pageBlock"
|
|
1628
|
+
pageRule = new CSSOM.CSSPageRule();
|
|
1629
|
+
pageRule.__starts = i;
|
|
1630
|
+
i += "page".length;
|
|
1631
|
+
});
|
|
1632
|
+
buffer = "";
|
|
1633
|
+
break;
|
|
1634
|
+
} else if (token.indexOf("@supports", i) === i) {
|
|
1635
|
+
validateAtRule("@supports", function () {
|
|
1636
|
+
state = "conditionBlock";
|
|
1637
|
+
supportsRule = new CSSOM.CSSSupportsRule();
|
|
1638
|
+
supportsRule.__starts = i;
|
|
1639
|
+
i += "supports".length;
|
|
1640
|
+
});
|
|
1641
|
+
buffer = "";
|
|
1642
|
+
break;
|
|
1643
|
+
} else if (token.indexOf("@host", i) === i) {
|
|
1644
|
+
validateAtRule("@host", function () {
|
|
1645
|
+
state = "hostRule-begin";
|
|
1646
|
+
i += "host".length;
|
|
1647
|
+
hostRule = new CSSOM.CSSHostRule();
|
|
1648
|
+
hostRule.__starts = i;
|
|
1649
|
+
});
|
|
1650
|
+
buffer = "";
|
|
1651
|
+
break;
|
|
1652
|
+
} else if (token.indexOf("@starting-style", i) === i) {
|
|
1653
|
+
validateAtRule("@starting-style", function () {
|
|
1654
|
+
state = "startingStyleRule-begin";
|
|
1655
|
+
i += "starting-style".length;
|
|
1656
|
+
startingStyleRule = new CSSOM.CSSStartingStyleRule();
|
|
1657
|
+
startingStyleRule.__starts = i;
|
|
1658
|
+
});
|
|
1659
|
+
buffer = "";
|
|
1660
|
+
break;
|
|
1661
|
+
} else if (token.indexOf("@import", i) === i) {
|
|
1662
|
+
buffer = "";
|
|
1663
|
+
validateAtRule("@import", function () {
|
|
1664
|
+
state = "importRule-begin";
|
|
1665
|
+
i += "import".length;
|
|
1666
|
+
buffer += "@import";
|
|
1667
|
+
}, true);
|
|
1668
|
+
break;
|
|
1669
|
+
} else if (token.indexOf("@namespace", i) === i) {
|
|
1670
|
+
buffer = "";
|
|
1671
|
+
validateAtRule("@namespace", function () {
|
|
1672
|
+
state = "namespaceRule-begin";
|
|
1673
|
+
i += "namespace".length;
|
|
1674
|
+
buffer += "@namespace";
|
|
1675
|
+
}, true);
|
|
1676
|
+
break;
|
|
1677
|
+
} else if (token.indexOf("@font-face", i) === i) {
|
|
1678
|
+
buffer = "";
|
|
1679
|
+
// @font-face can be nested only inside CSSScopeRule or CSSConditionRule
|
|
1680
|
+
// and only if there's no CSSStyleRule in the parent chain
|
|
1681
|
+
var cannotBeNested = true;
|
|
1682
|
+
if (currentScope !== topScope) {
|
|
1683
|
+
var hasStyleRuleInChain = false;
|
|
1684
|
+
var hasValidParent = false;
|
|
1685
|
+
|
|
1686
|
+
// Check currentScope
|
|
1687
|
+
if (currentScope.constructor.name === 'CSSStyleRule') {
|
|
1688
|
+
hasStyleRuleInChain = true;
|
|
1689
|
+
} else if (currentScope instanceof CSSOM.CSSScopeRule || currentScope instanceof CSSOM.CSSConditionRule) {
|
|
1690
|
+
hasValidParent = true;
|
|
1691
|
+
}
|
|
1583
1692
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1693
|
+
// Check ancestorRules for CSSStyleRule
|
|
1694
|
+
if (!hasStyleRuleInChain) {
|
|
1695
|
+
for (var j = 0; j < ancestorRules.length; j++) {
|
|
1696
|
+
if (ancestorRules[j].constructor.name === 'CSSStyleRule') {
|
|
1697
|
+
hasStyleRuleInChain = true;
|
|
1698
|
+
break;
|
|
1699
|
+
}
|
|
1700
|
+
if (ancestorRules[j] instanceof CSSOM.CSSScopeRule || ancestorRules[j] instanceof CSSOM.CSSConditionRule) {
|
|
1701
|
+
hasValidParent = true;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// Allow nesting if we have a valid parent and no style rule in the chain
|
|
1707
|
+
if (hasValidParent && !hasStyleRuleInChain) {
|
|
1708
|
+
cannotBeNested = false;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
validateAtRule("@font-face", function () {
|
|
1712
|
+
state = "fontFaceRule-begin";
|
|
1713
|
+
i += "font-face".length;
|
|
1714
|
+
fontFaceRule = new CSSOM.CSSFontFaceRule();
|
|
1715
|
+
fontFaceRule.__starts = i;
|
|
1716
|
+
}, cannotBeNested);
|
|
1717
|
+
break;
|
|
1718
|
+
} else {
|
|
1719
|
+
atKeyframesRegExp.lastIndex = i;
|
|
1720
|
+
var matchKeyframes = atKeyframesRegExp.exec(token);
|
|
1721
|
+
if (matchKeyframes && matchKeyframes.index === i) {
|
|
1722
|
+
state = "keyframesRule-begin";
|
|
1723
|
+
keyframesRule = new CSSOM.CSSKeyframesRule();
|
|
1724
|
+
keyframesRule.__starts = i;
|
|
1725
|
+
keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
|
|
1726
|
+
i += matchKeyframes[0].length - 1;
|
|
1598
1727
|
buffer = "";
|
|
1599
|
-
state = "before-selector";
|
|
1600
|
-
i += ruleClosingMatch.index + ruleClosingMatch[0].length;
|
|
1601
1728
|
break;
|
|
1729
|
+
} else if (state === "selector") {
|
|
1730
|
+
state = "atRule";
|
|
1602
1731
|
}
|
|
1603
1732
|
}
|
|
1733
|
+
buffer += character;
|
|
1734
|
+
break;
|
|
1604
1735
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1736
|
+
case "{":
|
|
1737
|
+
if (currentScope === topScope) {
|
|
1738
|
+
nestedSelectorRule = null;
|
|
1608
1739
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
styleRule.__parentStyleSheet = styleSheet;
|
|
1614
|
-
buffer = "";
|
|
1615
|
-
state = "before-name";
|
|
1616
|
-
} else if (state === "atBlock") {
|
|
1617
|
-
mediaRule.media.mediaText = buffer.trim();
|
|
1618
|
-
|
|
1619
|
-
if (parentRule) {
|
|
1620
|
-
mediaRule.__parentRule = parentRule;
|
|
1621
|
-
pushToAncestorRules(parentRule);
|
|
1740
|
+
if (state === 'before-selector') {
|
|
1741
|
+
parseError("Unexpected {");
|
|
1742
|
+
i = ignoreBalancedBlock(i, token.slice(i));
|
|
1743
|
+
break;
|
|
1622
1744
|
}
|
|
1745
|
+
if (state === "selector" || state === "atRule") {
|
|
1746
|
+
if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
|
|
1747
|
+
var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
|
|
1748
|
+
if (ruleClosingMatch) {
|
|
1749
|
+
styleRule = null;
|
|
1750
|
+
buffer = "";
|
|
1751
|
+
state = "before-selector";
|
|
1752
|
+
i += ruleClosingMatch.index + ruleClosingMatch[0].length;
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1623
1756
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
containerRule.__conditionText = buffer.trim();
|
|
1630
|
-
|
|
1631
|
-
if (parentRule) {
|
|
1632
|
-
containerRule.__parentRule = parentRule;
|
|
1633
|
-
pushToAncestorRules(parentRule);
|
|
1634
|
-
}
|
|
1635
|
-
currentScope = parentRule = containerRule;
|
|
1636
|
-
containerRule.__parentStyleSheet = styleSheet;
|
|
1637
|
-
buffer = "";
|
|
1638
|
-
state = "before-selector";
|
|
1639
|
-
} else if (state === "counterStyleBlock") {
|
|
1640
|
-
// TODO: Validate counter-style name. At least that it cannot be empty nor multiple
|
|
1641
|
-
counterStyleRule.name = buffer.trim().replace(/\n/g, "");
|
|
1642
|
-
currentScope = parentRule = counterStyleRule;
|
|
1643
|
-
counterStyleRule.__parentStyleSheet = styleSheet;
|
|
1644
|
-
buffer = "";
|
|
1645
|
-
} else if (state === "conditionBlock") {
|
|
1646
|
-
supportsRule.__conditionText = buffer.trim();
|
|
1647
|
-
|
|
1648
|
-
if (parentRule) {
|
|
1649
|
-
supportsRule.__parentRule = parentRule;
|
|
1650
|
-
pushToAncestorRules(parentRule);
|
|
1651
|
-
}
|
|
1757
|
+
// Ensure styleRule exists before trying to set properties on it
|
|
1758
|
+
if (!styleRule) {
|
|
1759
|
+
styleRule = new CSSOM.CSSStyleRule();
|
|
1760
|
+
styleRule.__starts = i;
|
|
1761
|
+
}
|
|
1652
1762
|
|
|
1653
|
-
|
|
1654
|
-
supportsRule.__parentStyleSheet = styleSheet;
|
|
1655
|
-
buffer = "";
|
|
1656
|
-
state = "before-selector";
|
|
1657
|
-
} else if (state === "scopeBlock") {
|
|
1658
|
-
var parsedScopePrelude = parseScopePrelude(buffer.trim());
|
|
1659
|
-
|
|
1660
|
-
if (parsedScopePrelude.hasStart) {
|
|
1661
|
-
scopeRule.__start = parsedScopePrelude.startSelector;
|
|
1662
|
-
}
|
|
1663
|
-
if (parsedScopePrelude.hasEnd) {
|
|
1664
|
-
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1665
|
-
}
|
|
1666
|
-
if (parsedScopePrelude.hasOnlyEnd) {
|
|
1667
|
-
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1668
|
-
}
|
|
1763
|
+
var originalParentRule = parentRule;
|
|
1669
1764
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
currentScope = parentRule = scopeRule;
|
|
1675
|
-
scopeRule.__parentStyleSheet = styleSheet;
|
|
1676
|
-
buffer = "";
|
|
1677
|
-
state = "before-selector";
|
|
1678
|
-
} else if (state === "layerBlock") {
|
|
1679
|
-
layerBlockRule.name = buffer.trim();
|
|
1765
|
+
if (parentRule) {
|
|
1766
|
+
styleRule.__parentRule = parentRule;
|
|
1767
|
+
pushToAncestorRules(parentRule);
|
|
1768
|
+
}
|
|
1680
1769
|
|
|
1681
|
-
|
|
1770
|
+
currentScope = parentRule = styleRule;
|
|
1771
|
+
var processedSelectorText = processSelectorText(buffer.trim());
|
|
1772
|
+
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
1773
|
+
if (originalParentRule && originalParentRule.constructor.name === "CSSStyleRule") {
|
|
1774
|
+
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
|
|
1775
|
+
// Add & at the beginning if there's no & in the selector, or if it starts with a combinator
|
|
1776
|
+
return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
|
|
1777
|
+
}).join(', ');
|
|
1778
|
+
} else {
|
|
1779
|
+
styleRule.selectorText = processedSelectorText;
|
|
1780
|
+
}
|
|
1781
|
+
styleRule.style.__starts = i;
|
|
1782
|
+
styleRule.__parentStyleSheet = styleSheet;
|
|
1783
|
+
buffer = "";
|
|
1784
|
+
state = "before-name";
|
|
1785
|
+
} else if (state === "atBlock") {
|
|
1786
|
+
mediaRule.media.mediaText = buffer.trim();
|
|
1682
1787
|
|
|
1683
|
-
if (isValidName) {
|
|
1684
1788
|
if (parentRule) {
|
|
1685
|
-
|
|
1789
|
+
mediaRule.__parentRule = parentRule;
|
|
1686
1790
|
pushToAncestorRules(parentRule);
|
|
1791
|
+
// If entering @media from within a CSSStyleRule, set nestedSelectorRule
|
|
1792
|
+
// so that & selectors and declarations work correctly inside
|
|
1793
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1794
|
+
nestedSelectorRule = parentRule;
|
|
1795
|
+
}
|
|
1687
1796
|
}
|
|
1688
|
-
|
|
1689
|
-
currentScope = parentRule = layerBlockRule;
|
|
1690
|
-
layerBlockRule.__parentStyleSheet = styleSheet;
|
|
1691
|
-
}
|
|
1692
|
-
buffer = "";
|
|
1693
|
-
state = "before-selector";
|
|
1694
|
-
} else if (state === "pageBlock") {
|
|
1695
|
-
pageRule.selectorText = buffer.trim();
|
|
1696
|
-
|
|
1697
|
-
if (parentRule) {
|
|
1698
|
-
pageRule.__parentRule = parentRule;
|
|
1699
|
-
pushToAncestorRules(parentRule);
|
|
1700
|
-
}
|
|
1701
1797
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
currentScope = parentRule = hostRule;
|
|
1713
|
-
hostRule.__parentStyleSheet = styleSheet;
|
|
1714
|
-
buffer = "";
|
|
1715
|
-
state = "before-selector";
|
|
1716
|
-
} else if (state === "startingStyleRule-begin") {
|
|
1717
|
-
if (parentRule) {
|
|
1718
|
-
startingStyleRule.__parentRule = parentRule;
|
|
1719
|
-
pushToAncestorRules(parentRule);
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
currentScope = parentRule = startingStyleRule;
|
|
1723
|
-
startingStyleRule.__parentStyleSheet = styleSheet;
|
|
1724
|
-
buffer = "";
|
|
1725
|
-
state = "before-selector";
|
|
1798
|
+
currentScope = parentRule = mediaRule;
|
|
1799
|
+
pushToAncestorRules(mediaRule);
|
|
1800
|
+
mediaRule.__parentStyleSheet = styleSheet;
|
|
1801
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1802
|
+
buffer = "";
|
|
1803
|
+
state = "before-selector";
|
|
1804
|
+
} else if (state === "containerBlock") {
|
|
1805
|
+
containerRule.__conditionText = buffer.trim();
|
|
1726
1806
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
fontFaceRule.__parentRule = parentRule;
|
|
1730
|
-
}
|
|
1731
|
-
fontFaceRule.__parentStyleSheet = styleSheet;
|
|
1732
|
-
styleRule = fontFaceRule;
|
|
1733
|
-
buffer = "";
|
|
1734
|
-
state = "before-name";
|
|
1735
|
-
} else if (state === "keyframesRule-begin") {
|
|
1736
|
-
keyframesRule.name = buffer.trim();
|
|
1737
|
-
if (parentRule) {
|
|
1738
|
-
pushToAncestorRules(parentRule);
|
|
1739
|
-
keyframesRule.__parentRule = parentRule;
|
|
1740
|
-
}
|
|
1741
|
-
keyframesRule.__parentStyleSheet = styleSheet;
|
|
1742
|
-
currentScope = parentRule = keyframesRule;
|
|
1743
|
-
buffer = "";
|
|
1744
|
-
state = "keyframeRule-begin";
|
|
1745
|
-
} else if (state === "keyframeRule-begin") {
|
|
1746
|
-
styleRule = new CSSOM.CSSKeyframeRule();
|
|
1747
|
-
styleRule.keyText = buffer.trim();
|
|
1748
|
-
styleRule.__starts = i;
|
|
1749
|
-
buffer = "";
|
|
1750
|
-
state = "before-name";
|
|
1751
|
-
} else if (state === "documentRule-begin") {
|
|
1752
|
-
// FIXME: what if this '{' is in the url text of the match function?
|
|
1753
|
-
documentRule.matcher.matcherText = buffer.trim();
|
|
1754
|
-
if (parentRule) {
|
|
1755
|
-
pushToAncestorRules(parentRule);
|
|
1756
|
-
documentRule.__parentRule = parentRule;
|
|
1757
|
-
}
|
|
1758
|
-
currentScope = parentRule = documentRule;
|
|
1759
|
-
documentRule.__parentStyleSheet = styleSheet;
|
|
1760
|
-
buffer = "";
|
|
1761
|
-
state = "before-selector";
|
|
1762
|
-
} else if (state === "before-name" || state === "name") {
|
|
1763
|
-
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
1764
|
-
if (styleRule.style.length) {
|
|
1765
|
-
parentRule.cssRules.push(styleRule);
|
|
1766
|
-
styleRule.__parentRule = parentRule;
|
|
1767
|
-
styleRule.__parentStyleSheet = styleSheet;
|
|
1807
|
+
if (parentRule) {
|
|
1808
|
+
containerRule.__parentRule = parentRule;
|
|
1768
1809
|
pushToAncestorRules(parentRule);
|
|
1769
|
-
|
|
1770
|
-
|
|
1810
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1811
|
+
nestedSelectorRule = parentRule;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
currentScope = parentRule = containerRule;
|
|
1815
|
+
pushToAncestorRules(containerRule);
|
|
1816
|
+
containerRule.__parentStyleSheet = styleSheet;
|
|
1817
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1818
|
+
buffer = "";
|
|
1819
|
+
state = "before-selector";
|
|
1820
|
+
} else if (state === "counterStyleBlock") {
|
|
1821
|
+
var counterStyleName = buffer.trim().replace(/\n/g, "");
|
|
1822
|
+
// Validate: name cannot be empty, contain whitespace, or contain dots
|
|
1823
|
+
var isValidCounterStyleName = counterStyleName.length > 0 && !/[\s.]/.test(counterStyleName);
|
|
1824
|
+
|
|
1825
|
+
if (isValidCounterStyleName) {
|
|
1826
|
+
counterStyleRule.name = counterStyleName;
|
|
1827
|
+
currentScope = parentRule = counterStyleRule;
|
|
1828
|
+
counterStyleRule.__parentStyleSheet = styleSheet;
|
|
1829
|
+
}
|
|
1830
|
+
buffer = "";
|
|
1831
|
+
} else if (state === "conditionBlock") {
|
|
1832
|
+
supportsRule.__conditionText = buffer.trim();
|
|
1833
|
+
|
|
1834
|
+
if (parentRule) {
|
|
1835
|
+
supportsRule.__parentRule = parentRule;
|
|
1771
1836
|
pushToAncestorRules(parentRule);
|
|
1837
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1838
|
+
nestedSelectorRule = parentRule;
|
|
1839
|
+
}
|
|
1772
1840
|
}
|
|
1773
|
-
} else {
|
|
1774
|
-
currentScope = parentRule = styleRule;
|
|
1775
|
-
pushToAncestorRules(parentRule);
|
|
1776
|
-
styleRule.__parentStyleSheet = styleSheet;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
styleRule = new CSSOM.CSSStyleRule();
|
|
1780
|
-
var processedSelectorText = processSelectorText(buffer.trim());
|
|
1781
|
-
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
1782
|
-
if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
|
|
1783
|
-
styleRule.selectorText = processedSelectorText;
|
|
1784
|
-
} else {
|
|
1785
|
-
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
|
|
1786
|
-
// Add & at the beginning if there's no & in the selector, or if it starts with a combinator
|
|
1787
|
-
return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
|
|
1788
|
-
}).join(', ');
|
|
1789
|
-
}
|
|
1790
|
-
styleRule.style.__starts = i - buffer.length;
|
|
1791
|
-
styleRule.__parentRule = parentRule;
|
|
1792
|
-
nestedSelectorRule = styleRule;
|
|
1793
1841
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
case ":":
|
|
1800
|
-
if (state === "name") {
|
|
1801
|
-
// It can be a nested selector, let's check
|
|
1802
|
-
var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
|
|
1803
|
-
var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
|
|
1804
|
-
if (hasOpenBraceBefore) {
|
|
1805
|
-
// Is a selector
|
|
1806
|
-
buffer += character;
|
|
1807
|
-
} else {
|
|
1808
|
-
// Is a declaration
|
|
1809
|
-
name = buffer.trim();
|
|
1842
|
+
currentScope = parentRule = supportsRule;
|
|
1843
|
+
pushToAncestorRules(supportsRule);
|
|
1844
|
+
supportsRule.__parentStyleSheet = styleSheet;
|
|
1845
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1810
1846
|
buffer = "";
|
|
1811
|
-
state = "before-
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
buffer += character;
|
|
1815
|
-
}
|
|
1816
|
-
break;
|
|
1847
|
+
state = "before-selector";
|
|
1848
|
+
} else if (state === "scopeBlock") {
|
|
1849
|
+
var parsedScopePrelude = parseScopePrelude(buffer.trim());
|
|
1817
1850
|
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1851
|
+
if (parsedScopePrelude.hasStart) {
|
|
1852
|
+
scopeRule.__start = parsedScopePrelude.startSelector;
|
|
1853
|
+
}
|
|
1854
|
+
if (parsedScopePrelude.hasEnd) {
|
|
1855
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1856
|
+
}
|
|
1857
|
+
if (parsedScopePrelude.hasOnlyEnd) {
|
|
1858
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
1859
|
+
}
|
|
1823
1860
|
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1861
|
+
if (parentRule) {
|
|
1862
|
+
scopeRule.__parentRule = parentRule;
|
|
1863
|
+
pushToAncestorRules(parentRule);
|
|
1864
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1865
|
+
nestedSelectorRule = parentRule;
|
|
1866
|
+
}
|
|
1829
1867
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
//
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
valueParenthesisDepth++;
|
|
1839
|
-
buffer += character;
|
|
1840
|
-
} else {
|
|
1841
|
-
buffer += character;
|
|
1842
|
-
}
|
|
1843
|
-
break;
|
|
1868
|
+
currentScope = parentRule = scopeRule;
|
|
1869
|
+
pushToAncestorRules(scopeRule);
|
|
1870
|
+
scopeRule.__parentStyleSheet = styleSheet;
|
|
1871
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1872
|
+
buffer = "";
|
|
1873
|
+
state = "before-selector";
|
|
1874
|
+
} else if (state === "layerBlock") {
|
|
1875
|
+
layerBlockRule.name = buffer.trim();
|
|
1844
1876
|
|
|
1845
|
-
|
|
1846
|
-
if (state === 'value-parenthesis') {
|
|
1847
|
-
valueParenthesisDepth--;
|
|
1848
|
-
if (valueParenthesisDepth === 0) state = 'value';
|
|
1849
|
-
}
|
|
1850
|
-
buffer += character;
|
|
1851
|
-
break;
|
|
1877
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
|
|
1852
1878
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1879
|
+
if (isValidName) {
|
|
1880
|
+
if (parentRule) {
|
|
1881
|
+
layerBlockRule.__parentRule = parentRule;
|
|
1882
|
+
pushToAncestorRules(parentRule);
|
|
1883
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1884
|
+
nestedSelectorRule = parentRule;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1861
1887
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1888
|
+
currentScope = parentRule = layerBlockRule;
|
|
1889
|
+
pushToAncestorRules(layerBlockRule);
|
|
1890
|
+
layerBlockRule.__parentStyleSheet = styleSheet;
|
|
1891
|
+
}
|
|
1892
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1867
1893
|
buffer = "";
|
|
1868
|
-
state = "before-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1894
|
+
state = "before-selector";
|
|
1895
|
+
} else if (state === "pageBlock") {
|
|
1896
|
+
pageRule.selectorText = buffer.trim();
|
|
1897
|
+
|
|
1898
|
+
if (parentRule) {
|
|
1899
|
+
pageRule.__parentRule = parentRule;
|
|
1900
|
+
pushToAncestorRules(parentRule);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
currentScope = parentRule = pageRule;
|
|
1904
|
+
pageRule.__parentStyleSheet = styleSheet;
|
|
1905
|
+
styleRule = pageRule;
|
|
1873
1906
|
buffer = "";
|
|
1874
1907
|
state = "before-name";
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
state = "before-selector";
|
|
1879
|
-
break;
|
|
1880
|
-
case "importRule":
|
|
1881
|
-
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
|
|
1882
|
-
return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
|
|
1883
|
-
});
|
|
1884
|
-
if (isValid) {
|
|
1885
|
-
importRule = new CSSOM.CSSImportRule();
|
|
1886
|
-
importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
|
|
1887
|
-
importRule.cssText = buffer + character;
|
|
1888
|
-
topScope.cssRules.push(importRule);
|
|
1908
|
+
} else if (state === "hostRule-begin") {
|
|
1909
|
+
if (parentRule) {
|
|
1910
|
+
pushToAncestorRules(parentRule);
|
|
1889
1911
|
}
|
|
1912
|
+
|
|
1913
|
+
currentScope = parentRule = hostRule;
|
|
1914
|
+
pushToAncestorRules(hostRule);
|
|
1915
|
+
hostRule.__parentStyleSheet = styleSheet;
|
|
1890
1916
|
buffer = "";
|
|
1891
1917
|
state = "before-selector";
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
try {
|
|
1899
|
-
// Validate namespace syntax before creating the rule
|
|
1900
|
-
var testNamespaceRule = new CSSOM.CSSNamespaceRule();
|
|
1901
|
-
testNamespaceRule.cssText = buffer + character;
|
|
1902
|
-
|
|
1903
|
-
namespaceRule = testNamespaceRule;
|
|
1904
|
-
namespaceRule.__parentStyleSheet = styleSheet;
|
|
1905
|
-
topScope.cssRules.push(namespaceRule);
|
|
1906
|
-
|
|
1907
|
-
// Track the namespace prefix for validation
|
|
1908
|
-
if (namespaceRule.prefix) {
|
|
1909
|
-
definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
|
|
1910
|
-
}
|
|
1911
|
-
} catch(e) {
|
|
1912
|
-
parseError(e.message);
|
|
1918
|
+
} else if (state === "startingStyleRule-begin") {
|
|
1919
|
+
if (parentRule) {
|
|
1920
|
+
startingStyleRule.__parentRule = parentRule;
|
|
1921
|
+
pushToAncestorRules(parentRule);
|
|
1922
|
+
if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
|
|
1923
|
+
nestedSelectorRule = parentRule;
|
|
1913
1924
|
}
|
|
1914
1925
|
}
|
|
1926
|
+
|
|
1927
|
+
currentScope = parentRule = startingStyleRule;
|
|
1928
|
+
pushToAncestorRules(startingStyleRule);
|
|
1929
|
+
startingStyleRule.__parentStyleSheet = styleSheet;
|
|
1930
|
+
styleRule = null; // Reset styleRule when entering @-rule
|
|
1915
1931
|
buffer = "";
|
|
1916
1932
|
state = "before-selector";
|
|
1917
|
-
break;
|
|
1918
|
-
case "layerBlock":
|
|
1919
|
-
var nameListStr = buffer.trim().split(",").map(function (name) {
|
|
1920
|
-
return name.trim();
|
|
1921
|
-
});
|
|
1922
|
-
var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
|
|
1923
|
-
return name.trim().match(cssCustomIdentifierRegExp) === null;
|
|
1924
|
-
});
|
|
1925
1933
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
layerStatementRule.__starts = layerBlockRule.__starts;
|
|
1930
|
-
layerStatementRule.__ends = i;
|
|
1931
|
-
layerStatementRule.nameList = nameListStr;
|
|
1932
|
-
topScope.cssRules.push(layerStatementRule);
|
|
1934
|
+
} else if (state === "fontFaceRule-begin") {
|
|
1935
|
+
if (parentRule) {
|
|
1936
|
+
fontFaceRule.__parentRule = parentRule;
|
|
1933
1937
|
}
|
|
1938
|
+
fontFaceRule.__parentStyleSheet = styleSheet;
|
|
1939
|
+
styleRule = fontFaceRule;
|
|
1934
1940
|
buffer = "";
|
|
1935
|
-
state = "before-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
break;
|
|
1942
|
-
|
|
1943
|
-
case "}":
|
|
1944
|
-
if (state === "counterStyleBlock") {
|
|
1945
|
-
// FIXME : Implement cssText get setter that parses the real implementation
|
|
1946
|
-
counterStyleRule.cssText = "@counter-style " + counterStyleRule.name + " { " + buffer.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function(match, quote) {
|
|
1947
|
-
return quote ? match : ' ';
|
|
1948
|
-
}) + " }";
|
|
1949
|
-
buffer = "";
|
|
1950
|
-
state = "before-selector";
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
switch (state) {
|
|
1954
|
-
case "value":
|
|
1955
|
-
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
1956
|
-
priority = "";
|
|
1957
|
-
/* falls through */
|
|
1958
|
-
case "before-value":
|
|
1959
|
-
case "before-name":
|
|
1960
|
-
case "name":
|
|
1961
|
-
styleRule.__ends = i + 1;
|
|
1962
|
-
|
|
1963
|
-
if (parentRule === styleRule) {
|
|
1964
|
-
parentRule = ancestorRules.pop()
|
|
1941
|
+
state = "before-name";
|
|
1942
|
+
} else if (state === "keyframesRule-begin") {
|
|
1943
|
+
keyframesRule.name = buffer.trim();
|
|
1944
|
+
if (parentRule) {
|
|
1945
|
+
pushToAncestorRules(parentRule);
|
|
1946
|
+
keyframesRule.__parentRule = parentRule;
|
|
1965
1947
|
}
|
|
1966
|
-
|
|
1948
|
+
keyframesRule.__parentStyleSheet = styleSheet;
|
|
1949
|
+
currentScope = parentRule = keyframesRule;
|
|
1950
|
+
buffer = "";
|
|
1951
|
+
state = "keyframeRule-begin";
|
|
1952
|
+
} else if (state === "keyframeRule-begin") {
|
|
1953
|
+
styleRule = new CSSOM.CSSKeyframeRule();
|
|
1954
|
+
styleRule.keyText = buffer.trim();
|
|
1955
|
+
styleRule.__starts = i;
|
|
1956
|
+
buffer = "";
|
|
1957
|
+
state = "before-name";
|
|
1958
|
+
} else if (state === "documentRule-begin") {
|
|
1959
|
+
// FIXME: what if this '{' is in the url text of the match function?
|
|
1960
|
+
documentRule.matcher.matcherText = buffer.trim();
|
|
1967
1961
|
if (parentRule) {
|
|
1968
|
-
|
|
1962
|
+
pushToAncestorRules(parentRule);
|
|
1963
|
+
documentRule.__parentRule = parentRule;
|
|
1969
1964
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1965
|
+
currentScope = parentRule = documentRule;
|
|
1966
|
+
pushToAncestorRules(documentRule);
|
|
1967
|
+
documentRule.__parentStyleSheet = styleSheet;
|
|
1968
|
+
buffer = "";
|
|
1969
|
+
state = "before-selector";
|
|
1970
|
+
} else if (state === "before-name" || state === "name") {
|
|
1971
|
+
// @font-face and similar rules don't support nested selectors
|
|
1972
|
+
// If we encounter a nested selector block inside them, skip it
|
|
1973
|
+
if (styleRule.constructor.name === "CSSFontFaceRule" ||
|
|
1974
|
+
styleRule.constructor.name === "CSSKeyframeRule" ||
|
|
1975
|
+
(styleRule.constructor.name === "CSSPageRule" && parentRule === styleRule)) {
|
|
1976
|
+
// Skip the nested block
|
|
1977
|
+
var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
|
|
1978
|
+
if (ruleClosingMatch) {
|
|
1979
|
+
i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
|
|
1980
|
+
buffer = "";
|
|
1981
|
+
state = "before-name";
|
|
1982
|
+
break;
|
|
1983
|
+
}
|
|
1974
1984
|
}
|
|
1975
1985
|
|
|
1976
|
-
if (styleRule.constructor.name === "
|
|
1977
|
-
if (styleRule
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
if (styleRule.parentRule) {
|
|
1983
|
-
styleRule.parentRule.cssRules.push(styleRule);
|
|
1986
|
+
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
1987
|
+
if (styleRule.style.length) {
|
|
1988
|
+
parentRule.cssRules.push(styleRule);
|
|
1989
|
+
styleRule.__parentRule = parentRule;
|
|
1990
|
+
styleRule.__parentStyleSheet = styleSheet;
|
|
1991
|
+
pushToAncestorRules(parentRule);
|
|
1984
1992
|
} else {
|
|
1985
|
-
|
|
1993
|
+
// If the styleRule is empty, we can assume that it's a nested selector
|
|
1994
|
+
pushToAncestorRules(parentRule);
|
|
1986
1995
|
}
|
|
1996
|
+
} else {
|
|
1997
|
+
currentScope = parentRule = styleRule;
|
|
1998
|
+
pushToAncestorRules(parentRule);
|
|
1999
|
+
styleRule.__parentStyleSheet = styleSheet;
|
|
1987
2000
|
}
|
|
2001
|
+
|
|
2002
|
+
styleRule = new CSSOM.CSSStyleRule();
|
|
2003
|
+
var processedSelectorText = processSelectorText(buffer.trim());
|
|
2004
|
+
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
2005
|
+
if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
|
|
2006
|
+
styleRule.selectorText = processedSelectorText;
|
|
2007
|
+
} else {
|
|
2008
|
+
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
|
|
2009
|
+
// Add & at the beginning if there's no & in the selector, or if it starts with a combinator
|
|
2010
|
+
return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
|
|
2011
|
+
}).join(', ');
|
|
2012
|
+
}
|
|
2013
|
+
styleRule.style.__starts = i - buffer.length;
|
|
2014
|
+
styleRule.__parentRule = parentRule;
|
|
2015
|
+
// Only set nestedSelectorRule if we're directly inside a CSSStyleRule or CSSScopeRule,
|
|
2016
|
+
// not inside other grouping rules like @media/@supports
|
|
2017
|
+
if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
|
|
2018
|
+
nestedSelectorRule = styleRule;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1988
2021
|
buffer = "";
|
|
1989
|
-
|
|
1990
|
-
|
|
2022
|
+
state = "before-name";
|
|
2023
|
+
}
|
|
2024
|
+
break;
|
|
2025
|
+
|
|
2026
|
+
case ":":
|
|
2027
|
+
if (state === "name") {
|
|
2028
|
+
// It can be a nested selector, let's check
|
|
2029
|
+
var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
|
|
2030
|
+
var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
|
|
2031
|
+
if (hasOpenBraceBefore) {
|
|
2032
|
+
// Is a selector
|
|
2033
|
+
buffer += character;
|
|
1991
2034
|
} else {
|
|
1992
|
-
|
|
2035
|
+
// Is a declaration
|
|
2036
|
+
name = buffer.trim();
|
|
2037
|
+
buffer = "";
|
|
2038
|
+
state = "before-value";
|
|
1993
2039
|
}
|
|
2040
|
+
} else {
|
|
2041
|
+
buffer += character;
|
|
2042
|
+
}
|
|
2043
|
+
break;
|
|
1994
2044
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2045
|
+
case "(":
|
|
2046
|
+
if (state === 'value') {
|
|
2047
|
+
// ie css expression mode
|
|
2048
|
+
if (buffer.trim() === 'expression') {
|
|
2049
|
+
var info = (new CSSOM.CSSValueExpression(token, i)).parse();
|
|
2050
|
+
|
|
2051
|
+
if (info.error) {
|
|
2052
|
+
parseError(info.error);
|
|
2053
|
+
} else {
|
|
2054
|
+
buffer += info.expression;
|
|
2055
|
+
i = info.idx;
|
|
1998
2056
|
}
|
|
1999
|
-
styleRule = null;
|
|
2000
2057
|
} else {
|
|
2001
|
-
|
|
2002
|
-
|
|
2058
|
+
state = 'value-parenthesis';
|
|
2059
|
+
//always ensure this is reset to 1 on transition
|
|
2060
|
+
//from value to value-parenthesis
|
|
2061
|
+
valueParenthesisDepth = 1;
|
|
2062
|
+
buffer += character;
|
|
2003
2063
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2064
|
+
} else if (state === 'value-parenthesis') {
|
|
2065
|
+
valueParenthesisDepth++;
|
|
2066
|
+
buffer += character;
|
|
2067
|
+
} else {
|
|
2068
|
+
buffer += character;
|
|
2069
|
+
}
|
|
2070
|
+
break;
|
|
2071
|
+
|
|
2072
|
+
case ")":
|
|
2073
|
+
if (state === 'value-parenthesis') {
|
|
2074
|
+
valueParenthesisDepth--;
|
|
2075
|
+
if (valueParenthesisDepth === 0) state = 'value';
|
|
2076
|
+
}
|
|
2077
|
+
buffer += character;
|
|
2078
|
+
break;
|
|
2079
|
+
|
|
2080
|
+
case "!":
|
|
2081
|
+
if (state === "value" && token.indexOf("!important", i) === i) {
|
|
2082
|
+
priority = "important";
|
|
2083
|
+
i += "important".length;
|
|
2084
|
+
} else {
|
|
2085
|
+
buffer += character;
|
|
2086
|
+
}
|
|
2087
|
+
break;
|
|
2088
|
+
|
|
2089
|
+
case ";":
|
|
2090
|
+
switch (state) {
|
|
2091
|
+
case "before-value":
|
|
2092
|
+
case "before-name":
|
|
2093
|
+
parseError("Unexpected ;");
|
|
2094
|
+
buffer = "";
|
|
2095
|
+
state = "before-name";
|
|
2096
|
+
break;
|
|
2097
|
+
case "value":
|
|
2098
|
+
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
2099
|
+
priority = "";
|
|
2100
|
+
buffer = "";
|
|
2101
|
+
state = "before-name";
|
|
2102
|
+
break;
|
|
2103
|
+
case "atRule":
|
|
2104
|
+
buffer = "";
|
|
2105
|
+
state = "before-selector";
|
|
2106
|
+
break;
|
|
2107
|
+
case "importRule":
|
|
2108
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
|
|
2109
|
+
return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
|
|
2110
|
+
});
|
|
2111
|
+
if (isValid) {
|
|
2112
|
+
importRule = new CSSOM.CSSImportRule();
|
|
2113
|
+
importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
|
|
2114
|
+
importRule.parse(buffer + character);
|
|
2115
|
+
topScope.cssRules.push(importRule);
|
|
2014
2116
|
}
|
|
2015
|
-
|
|
2117
|
+
buffer = "";
|
|
2118
|
+
state = "before-selector";
|
|
2016
2119
|
break;
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
if (
|
|
2033
|
-
|
|
2034
|
-
currentScope = nestedSelectorRule.parentRule;
|
|
2035
|
-
if (currentScope.cssRules.findIndex(function (rule) {
|
|
2036
|
-
return rule === prevScope
|
|
2037
|
-
}) === -1) {
|
|
2038
|
-
currentScope.cssRules.push(prevScope);
|
|
2039
|
-
}
|
|
2040
|
-
nestedSelectorRule = currentScope;
|
|
2120
|
+
case "namespaceRule":
|
|
2121
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
|
|
2122
|
+
return ['CSSImportRule', 'CSSLayerStatementRule', 'CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
|
|
2123
|
+
});
|
|
2124
|
+
if (isValid) {
|
|
2125
|
+
try {
|
|
2126
|
+
// Validate namespace syntax before creating the rule
|
|
2127
|
+
var testNamespaceRule = new CSSOM.CSSNamespaceRule();
|
|
2128
|
+
testNamespaceRule.parse(buffer + character);
|
|
2129
|
+
|
|
2130
|
+
namespaceRule = testNamespaceRule;
|
|
2131
|
+
namespaceRule.__parentStyleSheet = styleSheet;
|
|
2132
|
+
topScope.cssRules.push(namespaceRule);
|
|
2133
|
+
|
|
2134
|
+
// Track the namespace prefix for validation
|
|
2135
|
+
if (namespaceRule.prefix) {
|
|
2136
|
+
definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
|
|
2041
2137
|
}
|
|
2138
|
+
} catch (e) {
|
|
2139
|
+
parseError(e.message);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
buffer = "";
|
|
2143
|
+
state = "before-selector";
|
|
2144
|
+
break;
|
|
2145
|
+
case "layerBlock":
|
|
2146
|
+
var nameListStr = buffer.trim().split(",").map(function (name) {
|
|
2147
|
+
return name.trim();
|
|
2148
|
+
});
|
|
2149
|
+
var isInvalid = nameListStr.some(function (name) {
|
|
2150
|
+
return name.trim().match(cssCustomIdentifierRegExp) === null;
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
// Check if there's a CSSStyleRule in the parent chain
|
|
2154
|
+
var hasStyleRuleParent = false;
|
|
2155
|
+
if (parentRule) {
|
|
2156
|
+
var checkParent = parentRule;
|
|
2157
|
+
while (checkParent) {
|
|
2158
|
+
if (checkParent.constructor.name === "CSSStyleRule") {
|
|
2159
|
+
hasStyleRuleParent = true;
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2162
|
+
checkParent = checkParent.__parentRule;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
if (!isInvalid && !hasStyleRuleParent) {
|
|
2167
|
+
layerStatementRule = new CSSOM.CSSLayerStatementRule();
|
|
2168
|
+
layerStatementRule.__parentStyleSheet = styleSheet;
|
|
2169
|
+
layerStatementRule.__starts = layerBlockRule.__starts;
|
|
2170
|
+
layerStatementRule.__ends = i;
|
|
2171
|
+
layerStatementRule.nameList = nameListStr;
|
|
2172
|
+
|
|
2173
|
+
// Add to parent rule if nested, otherwise to top scope
|
|
2174
|
+
if (parentRule) {
|
|
2175
|
+
layerStatementRule.__parentRule = parentRule;
|
|
2176
|
+
parentRule.cssRules.push(layerStatementRule);
|
|
2042
2177
|
} else {
|
|
2043
|
-
|
|
2044
|
-
parentRule !== prevScope && parentRule.cssRules.push(prevScope);
|
|
2045
|
-
break;
|
|
2178
|
+
topScope.cssRules.push(layerStatementRule);
|
|
2046
2179
|
}
|
|
2047
2180
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2181
|
+
buffer = "";
|
|
2182
|
+
state = "before-selector";
|
|
2183
|
+
break;
|
|
2184
|
+
default:
|
|
2185
|
+
buffer += character;
|
|
2186
|
+
break;
|
|
2187
|
+
}
|
|
2188
|
+
break;
|
|
2189
|
+
|
|
2190
|
+
case "}":
|
|
2191
|
+
if (state === "counterStyleBlock") {
|
|
2192
|
+
// FIXME : Implement missing properties on CSSCounterStyleRule interface and update parse method
|
|
2193
|
+
// For now it's just assigning entire rule text
|
|
2194
|
+
counterStyleRule.parse("@counter-style " + counterStyleRule.name + " { " + buffer + " }");
|
|
2195
|
+
buffer = "";
|
|
2196
|
+
state = "before-selector";
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
switch (state) {
|
|
2200
|
+
case "value":
|
|
2201
|
+
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
2202
|
+
priority = "";
|
|
2203
|
+
/* falls through */
|
|
2204
|
+
case "before-value":
|
|
2205
|
+
case "before-name":
|
|
2206
|
+
case "name":
|
|
2207
|
+
styleRule.__ends = i + 1;
|
|
2208
|
+
|
|
2209
|
+
if (parentRule === styleRule) {
|
|
2210
|
+
parentRule = ancestorRules.pop()
|
|
2056
2211
|
}
|
|
2057
|
-
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2212
|
+
|
|
2213
|
+
if (parentRule) {
|
|
2214
|
+
styleRule.__parentRule = parentRule;
|
|
2215
|
+
}
|
|
2216
|
+
styleRule.__parentStyleSheet = styleSheet;
|
|
2217
|
+
|
|
2218
|
+
if (currentScope === styleRule) {
|
|
2219
|
+
currentScope = parentRule || topScope;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
|
|
2223
|
+
if (styleRule === nestedSelectorRule) {
|
|
2069
2224
|
nestedSelectorRule = null;
|
|
2070
|
-
parentRule = null;
|
|
2071
2225
|
}
|
|
2226
|
+
parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
|
|
2227
|
+
} else {
|
|
2228
|
+
if (styleRule.parentRule) {
|
|
2229
|
+
styleRule.parentRule.cssRules.push(styleRule);
|
|
2230
|
+
} else {
|
|
2231
|
+
currentScope.cssRules.push(styleRule);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
buffer = "";
|
|
2235
|
+
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
2236
|
+
state = "keyframeRule-begin";
|
|
2237
|
+
} else {
|
|
2238
|
+
state = "before-selector";
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
2242
|
+
if (currentScope !== topScope) {
|
|
2243
|
+
// Only set nestedSelectorRule if currentScope is CSSStyleRule or CSSScopeRule
|
|
2244
|
+
// Not for other grouping rules like @media/@supports
|
|
2245
|
+
if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
|
|
2246
|
+
nestedSelectorRule = currentScope;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
styleRule = null;
|
|
2072
2250
|
} else {
|
|
2073
|
-
|
|
2251
|
+
// Update nestedSelectorRule when closing a CSSStyleRule
|
|
2252
|
+
if (styleRule === nestedSelectorRule) {
|
|
2253
|
+
var selector = styleRule.selectorText && styleRule.selectorText.trim();
|
|
2254
|
+
// Check if this is proper nesting (&.class, &:pseudo) vs prepended & (& :is, & .class with space)
|
|
2255
|
+
// Prepended & has pattern "& X" where X starts with : or .
|
|
2256
|
+
var isPrependedAmpersand = selector && selector.match(/^&\s+[:\.]/);
|
|
2257
|
+
|
|
2258
|
+
// Check if parent is a grouping rule that can contain nested selectors
|
|
2259
|
+
var isGroupingRule = currentScope && currentScope instanceof CSSOM.CSSGroupingRule;
|
|
2260
|
+
|
|
2261
|
+
if (!isPrependedAmpersand && isGroupingRule) {
|
|
2262
|
+
// Proper nesting - set nestedSelectorRule to parent for more nested selectors
|
|
2263
|
+
// But only if it's a CSSStyleRule or CSSScopeRule, not other grouping rules like @media
|
|
2264
|
+
if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
|
|
2265
|
+
nestedSelectorRule = currentScope;
|
|
2266
|
+
}
|
|
2267
|
+
// If currentScope is another type of grouping rule (like @media), keep nestedSelectorRule unchanged
|
|
2268
|
+
} else {
|
|
2269
|
+
// Prepended & or not nested in grouping rule - reset to prevent CSSNestedDeclarations
|
|
2270
|
+
nestedSelectorRule = null;
|
|
2271
|
+
}
|
|
2272
|
+
} else if (nestedSelectorRule && currentScope instanceof CSSOM.CSSGroupingRule) {
|
|
2273
|
+
// When closing a nested rule that's not the nestedSelectorRule itself,
|
|
2274
|
+
// maintain nestedSelectorRule if we're still inside a grouping rule
|
|
2275
|
+
// This ensures declarations after nested selectors inside @media/@supports etc. work correctly
|
|
2276
|
+
}
|
|
2277
|
+
styleRule = null;
|
|
2278
|
+
break;
|
|
2279
|
+
}
|
|
2280
|
+
case "keyframeRule-begin":
|
|
2281
|
+
case "before-selector":
|
|
2282
|
+
case "selector":
|
|
2283
|
+
// End of media/supports/document rule.
|
|
2284
|
+
if (!parentRule) {
|
|
2285
|
+
parseError("Unexpected }");
|
|
2286
|
+
|
|
2287
|
+
var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
|
|
2288
|
+
if (hasPreviousStyleRule) {
|
|
2289
|
+
i = ignoreBalancedBlock(i, token.slice(i), 1);
|
|
2290
|
+
}
|
|
2074
2291
|
|
|
2292
|
+
break;
|
|
2075
2293
|
}
|
|
2076
|
-
} else {
|
|
2077
|
-
currentScope = parentRule;
|
|
2078
|
-
}
|
|
2079
2294
|
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2295
|
+
while (ancestorRules.length > 0) {
|
|
2296
|
+
parentRule = ancestorRules.pop();
|
|
2297
|
+
|
|
2298
|
+
if (parentRule instanceof CSSOM.CSSGroupingRule && (parentRule.constructor.name !== 'CSSStyleRule' || parentRule.__parentRule)) {
|
|
2299
|
+
if (nestedSelectorRule) {
|
|
2300
|
+
if (nestedSelectorRule.parentRule) {
|
|
2301
|
+
prevScope = nestedSelectorRule;
|
|
2302
|
+
currentScope = nestedSelectorRule.parentRule;
|
|
2303
|
+
if (currentScope.cssRules.findIndex(function (rule) {
|
|
2304
|
+
return rule === prevScope
|
|
2305
|
+
}) === -1) {
|
|
2306
|
+
currentScope.cssRules.push(prevScope);
|
|
2307
|
+
}
|
|
2308
|
+
nestedSelectorRule = currentScope;
|
|
2309
|
+
} else {
|
|
2310
|
+
// If nestedSelectorRule doesn't have a parentRule, we're closing a grouping rule
|
|
2311
|
+
// inside a top-level CSSStyleRule. We need to push currentScope to the parentRule.
|
|
2312
|
+
prevScope = currentScope;
|
|
2313
|
+
// Push to actual parent from ancestorRules if available
|
|
2314
|
+
var actualParent = ancestorRules.length > 0 ? ancestorRules[ancestorRules.length - 1] : nestedSelectorRule;
|
|
2315
|
+
actualParent !== prevScope && actualParent.cssRules.push(prevScope);
|
|
2316
|
+
// Update currentScope to the nestedSelectorRule before breaking
|
|
2317
|
+
currentScope = actualParent;
|
|
2318
|
+
parentRule = actualParent;
|
|
2319
|
+
break;
|
|
2320
|
+
}
|
|
2321
|
+
} else {
|
|
2322
|
+
prevScope = currentScope;
|
|
2323
|
+
parentRule !== prevScope && parentRule.cssRules.push(prevScope);
|
|
2324
|
+
break;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2085
2328
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
// If Declaration inside Nested Selector let's keep the same styleRule
|
|
2093
|
-
if (
|
|
2094
|
-
parentRule.constructor.name === "CSSStyleRule"
|
|
2095
|
-
|| parentRule.constructor.name === "CSSMediaRule"
|
|
2096
|
-
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
2097
|
-
|| parentRule.constructor.name === "CSSContainerRule"
|
|
2098
|
-
|| parentRule.constructor.name === "CSSScopeRule"
|
|
2099
|
-
|| parentRule.constructor.name === "CSSLayerBlockRule"
|
|
2100
|
-
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
|
2101
|
-
) {
|
|
2102
|
-
// parentRule.__parentRule = styleRule;
|
|
2103
|
-
state = "before-name";
|
|
2104
|
-
if (styleRule !== parentRule) {
|
|
2105
|
-
styleRule = new CSSOM.CSSNestedDeclarations();
|
|
2106
|
-
styleRule.__starts = i;
|
|
2329
|
+
// If currentScope has a __parentRule and wasn't added yet, add it
|
|
2330
|
+
if (ancestorRules.length === 0 && currentScope.__parentRule && currentScope.__parentRule.cssRules) {
|
|
2331
|
+
if (currentScope.__parentRule.cssRules.findIndex(function (rule) {
|
|
2332
|
+
return rule === currentScope
|
|
2333
|
+
}) === -1) {
|
|
2334
|
+
currentScope.__parentRule.cssRules.push(currentScope);
|
|
2107
2335
|
}
|
|
2108
2336
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2337
|
+
|
|
2338
|
+
// Only handle top-level rule closing if we processed all ancestors
|
|
2339
|
+
if (ancestorRules.length === 0 && currentScope.parentRule == null) {
|
|
2340
|
+
currentScope.__ends = i + 1;
|
|
2341
|
+
if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
|
|
2342
|
+
return rule === currentScope
|
|
2343
|
+
}) === -1) {
|
|
2344
|
+
topScope.cssRules.push(currentScope);
|
|
2345
|
+
}
|
|
2346
|
+
currentScope = topScope;
|
|
2347
|
+
if (nestedSelectorRule === parentRule) {
|
|
2348
|
+
// Check if this selector is really starting inside another selector
|
|
2349
|
+
var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
|
|
2350
|
+
var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
|
|
2351
|
+
var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
|
|
2352
|
+
var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
|
|
2353
|
+
var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
|
|
2354
|
+
|
|
2355
|
+
if (openingBraceLen === closingBraceLen) {
|
|
2356
|
+
// If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
|
|
2357
|
+
nestedSelectorRule.__ends = i + 1;
|
|
2358
|
+
nestedSelectorRule = null;
|
|
2359
|
+
parentRule = null;
|
|
2360
|
+
}
|
|
2126
2361
|
} else {
|
|
2362
|
+
parentRule = null;
|
|
2363
|
+
}
|
|
2364
|
+
} else {
|
|
2365
|
+
currentScope = parentRule;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
buffer = "";
|
|
2369
|
+
state = "before-selector";
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
break;
|
|
2373
|
+
|
|
2374
|
+
default:
|
|
2375
|
+
switch (state) {
|
|
2376
|
+
case "before-selector":
|
|
2377
|
+
state = "selector";
|
|
2378
|
+
if ((styleRule || scopeRule) && parentRule) {
|
|
2379
|
+
// Assuming it's a declaration inside Nested Selector OR a Nested Declaration
|
|
2380
|
+
// If Declaration inside Nested Selector let's keep the same styleRule
|
|
2381
|
+
if (!isSelectorStartChar(character) && !isWhitespaceChar(character) && parentRule instanceof CSSOM.CSSGroupingRule) {
|
|
2382
|
+
// parentRule.__parentRule = styleRule;
|
|
2383
|
+
state = "before-name";
|
|
2384
|
+
if (styleRule !== parentRule) {
|
|
2385
|
+
styleRule = new CSSOM.CSSNestedDeclarations();
|
|
2386
|
+
styleRule.__starts = i;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
} else if (nestedSelectorRule && parentRule && parentRule instanceof CSSOM.CSSGroupingRule) {
|
|
2391
|
+
if (isSelectorStartChar(character)) {
|
|
2392
|
+
// If starting with a selector character, create CSSStyleRule instead of CSSNestedDeclarations
|
|
2127
2393
|
styleRule = new CSSOM.CSSStyleRule();
|
|
2128
|
-
styleRule.__starts = i;
|
|
2394
|
+
styleRule.__starts = i;
|
|
2395
|
+
} else if (!isWhitespaceChar(character)) {
|
|
2396
|
+
// Starting a declaration (not whitespace, not a selector)
|
|
2397
|
+
state = "before-name";
|
|
2398
|
+
// Check if we should create CSSNestedDeclarations
|
|
2399
|
+
// This happens if: parent has cssRules OR nestedSelectorRule exists (indicating CSSStyleRule in hierarchy)
|
|
2400
|
+
if (parentRule.cssRules.length || nestedSelectorRule) {
|
|
2401
|
+
currentScope = parentRule;
|
|
2402
|
+
// Only set nestedSelectorRule if parentRule is CSSStyleRule or CSSScopeRule
|
|
2403
|
+
if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
|
|
2404
|
+
nestedSelectorRule = parentRule;
|
|
2405
|
+
}
|
|
2406
|
+
styleRule = new CSSOM.CSSNestedDeclarations();
|
|
2407
|
+
styleRule.__starts = i;
|
|
2408
|
+
} else {
|
|
2409
|
+
if (parentRule.constructor.name === "CSSStyleRule") {
|
|
2410
|
+
styleRule = parentRule;
|
|
2411
|
+
} else {
|
|
2412
|
+
styleRule = new CSSOM.CSSStyleRule();
|
|
2413
|
+
styleRule.__starts = i;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2129
2416
|
}
|
|
2130
2417
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2418
|
+
break;
|
|
2419
|
+
case "before-name":
|
|
2420
|
+
state = "name";
|
|
2421
|
+
break;
|
|
2422
|
+
case "before-value":
|
|
2423
|
+
state = "value";
|
|
2424
|
+
break;
|
|
2425
|
+
case "importRule-begin":
|
|
2426
|
+
state = "importRule";
|
|
2427
|
+
break;
|
|
2428
|
+
case "namespaceRule-begin":
|
|
2429
|
+
state = "namespaceRule";
|
|
2430
|
+
break;
|
|
2431
|
+
}
|
|
2432
|
+
buffer += character;
|
|
2433
|
+
break;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Auto-close all unclosed nested structures
|
|
2437
|
+
// Check AFTER processing the character, at the ORIGINAL ending index
|
|
2438
|
+
// Only add closing braces if CSS is incomplete (not at top scope)
|
|
2439
|
+
if (i === initialEndingIndex && (currentScope !== topScope || ancestorRules.length > 0)) {
|
|
2440
|
+
var needsClosing = ancestorRules.length;
|
|
2441
|
+
if (currentScope !== topScope && ancestorRules.indexOf(currentScope) === -1) {
|
|
2442
|
+
needsClosing += 1;
|
|
2443
|
+
}
|
|
2444
|
+
// Add closing braces for all unclosed structures
|
|
2445
|
+
for (var closeIdx = 0; closeIdx < needsClosing; closeIdx++) {
|
|
2446
|
+
token += "}";
|
|
2447
|
+
endingIndex += 1;
|
|
2148
2448
|
}
|
|
2149
|
-
buffer += character;
|
|
2150
|
-
break;
|
|
2151
2449
|
}
|
|
2152
2450
|
}
|
|
2153
2451
|
|