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