@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 CHANGED
@@ -1,4 +1,18 @@
1
- var CSSOM = {};
1
+ var __globalObject = null;
2
+
3
+ var CSSOM = {
4
+ setup: function(opts) {
5
+ if (opts.globalObject) {
6
+ __globalObject = opts.globalObject;
7
+ }
8
+ },
9
+ getGlobalObject: function() {
10
+ return __globalObject;
11
+ }
12
+ };
13
+
14
+
15
+
2
16
 
3
17
 
4
18
  // Utility functions for CSSOM error handling
@@ -13,19 +27,8 @@ var CSSOM = {};
13
27
  * @return {Function} The error constructor
14
28
  */
15
29
  function getErrorConstructor(context, errorType) {
16
- // Try parentStyleSheet.__globalObject first
17
- if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
18
- return context.parentStyleSheet.__globalObject[errorType];
19
- }
20
-
21
- // Try __parentStyleSheet (alternative naming)
22
- if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
23
- return context.__parentStyleSheet.__globalObject[errorType];
24
- }
25
-
26
- // Try __globalObject on the context itself
27
- if (context.__globalObject && context.__globalObject[errorType]) {
28
- return context.__globalObject[errorType];
30
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()[errorType]) {
31
+ return CSSOM.getGlobalObject()[errorType];
29
32
  }
30
33
 
31
34
  // Fall back to native constructor
@@ -344,6 +347,16 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
344
347
  enumerable: true
345
348
  },
346
349
 
350
+ cssText: {
351
+ get: function() {
352
+ // Default getter: subclasses should override this
353
+ return "";
354
+ },
355
+ set: function(cssText) {
356
+ return cssText;
357
+ }
358
+ },
359
+
347
360
  parentRule: {
348
361
  get: function() {
349
362
  return this.__parentRule
@@ -414,9 +427,15 @@ CSSOM.CSSNestedDeclarations = function CSSNestedDeclarations() {
414
427
  this.__style.parentRule = this;
415
428
  };
416
429
 
417
- CSSOM.CSSNestedDeclarations.prototype = new CSSOM.CSSRule();
430
+ CSSOM.CSSNestedDeclarations.prototype = Object.create(CSSOM.CSSRule.prototype);
418
431
  CSSOM.CSSNestedDeclarations.prototype.constructor = CSSOM.CSSNestedDeclarations;
419
- CSSOM.CSSNestedDeclarations.prototype.type = 0;
432
+
433
+ Object.setPrototypeOf(CSSOM.CSSNestedDeclarations, CSSOM.CSSRule);
434
+
435
+ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "type", {
436
+ value: 0,
437
+ writable: false
438
+ });
420
439
 
421
440
  Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", {
422
441
  get: function() {
@@ -434,9 +453,7 @@ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", {
434
453
  Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
435
454
  get: function () {
436
455
  return this.style.cssText;
437
- },
438
- configurable: true,
439
- enumerable: true,
456
+ }
440
457
  });
441
458
 
442
459
 
@@ -448,12 +465,19 @@ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
448
465
  */
449
466
  CSSOM.CSSGroupingRule = function CSSGroupingRule() {
450
467
  CSSOM.CSSRule.call(this);
451
- this.cssRules = new CSSOM.CSSRuleList();
468
+ this.__cssRules = new CSSOM.CSSRuleList();
452
469
  };
453
470
 
454
- CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
471
+ CSSOM.CSSGroupingRule.prototype = Object.create(CSSOM.CSSRule.prototype);
455
472
  CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
456
473
 
474
+ Object.setPrototypeOf(CSSOM.CSSGroupingRule, CSSOM.CSSRule);
475
+
476
+ Object.defineProperty(CSSOM.CSSGroupingRule.prototype, "cssRules", {
477
+ get: function() {
478
+ return this.__cssRules;
479
+ }
480
+ });
457
481
 
458
482
  /**
459
483
  * Used to insert a new CSS rule to a list of CSS rules.
@@ -485,13 +509,60 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
485
509
  if (index > this.cssRules.length) {
486
510
  errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
487
511
  }
488
-
489
- var ruleToParse = String(rule);
490
- var parsedSheet = CSSOM.parse(ruleToParse);
491
- if (parsedSheet.cssRules.length !== 1) {
492
- errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
512
+ var ruleToParse = processedRuleToParse = String(rule);
513
+ ruleToParse = ruleToParse.trim().replace(/^\/\*[\s\S]*?\*\/\s*/, "");
514
+ var isNestedSelector = this.constructor.name === "CSSStyleRule";
515
+ if (isNestedSelector === false) {
516
+ var currentRule = this;
517
+ while (currentRule.parentRule) {
518
+ currentRule = currentRule.parentRule;
519
+ if (currentRule.constructor.name === "CSSStyleRule") {
520
+ isNestedSelector = true;
521
+ break;
522
+ }
523
+ }
524
+ }
525
+ if (isNestedSelector) {
526
+ processedRuleToParse = 's { n { } ' + ruleToParse + '}';
527
+ }
528
+ var isScopeRule = this.constructor.name === "CSSScopeRule";
529
+ if (isScopeRule) {
530
+ if (isNestedSelector) {
531
+ processedRuleToParse = 's { ' + '@scope {' + ruleToParse + '}}';
532
+ } else {
533
+ processedRuleToParse = '@scope {' + ruleToParse + '}';
534
+ }
535
+ }
536
+ var parsedRules = new CSSOM.CSSRuleList();
537
+ CSSOM.parse(processedRuleToParse, {
538
+ styleSheet: this.parentStyleSheet,
539
+ cssRules: parsedRules
540
+ });
541
+ if (isScopeRule) {
542
+ if (isNestedSelector) {
543
+ parsedRules = parsedRules[0].cssRules[0].cssRules;
544
+ } else {
545
+ parsedRules = parsedRules[0].cssRules
546
+ }
547
+ }
548
+ if (isNestedSelector) {
549
+ parsedRules = parsedRules[0].cssRules.slice(1);
550
+ }
551
+ if (parsedRules.length !== 1) {
552
+ if (isNestedSelector && parsedRules.length === 0 && ruleToParse.indexOf('@font-face') === 0) {
553
+ errorUtils.throwError(this, 'DOMException',
554
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
555
+ "Only conditional nested group rules, style rules, @scope rules, @apply rules, and nested declaration rules may be nested.",
556
+ 'HierarchyRequestError');
557
+ } else {
558
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
559
+ }
560
+ }
561
+ var cssRule = parsedRules[0];
562
+
563
+ if (cssRule.constructor.name === 'CSSNestedDeclarations' && cssRule.style.length === 0) {
564
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
493
565
  }
494
- var cssRule = parsedSheet.cssRules[0];
495
566
 
496
567
  // Check for rules that cannot be inserted inside a CSSGroupingRule
497
568
  if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
@@ -535,7 +606,9 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
535
606
  if (index >= this.cssRules.length) {
536
607
  errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
537
608
  }
538
- this.cssRules.splice(index, 1)[0].__parentRule = null;
609
+ this.cssRules[index].__parentRule = null;
610
+ this.cssRules[index].__parentStyleSheet = null;
611
+ this.cssRules.splice(index, 1);
539
612
  };
540
613
 
541
614
 
@@ -549,11 +622,45 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
549
622
  CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() {
550
623
  CSSOM.CSSRule.call(this);
551
624
  this.name = "";
625
+ this.__props = "";
552
626
  };
553
627
 
554
- CSSOM.CSSCounterStyleRule.prototype = new CSSOM.CSSRule();
628
+ CSSOM.CSSCounterStyleRule.prototype = Object.create(CSSOM.CSSRule.prototype);
555
629
  CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule;
556
- CSSOM.CSSCounterStyleRule.prototype.type = 11;
630
+
631
+ Object.setPrototypeOf(CSSOM.CSSCounterStyleRule, CSSOM.CSSRule);
632
+
633
+ Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "type", {
634
+ value: 11,
635
+ writable: false
636
+ });
637
+
638
+ Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "cssText", {
639
+ get: function() {
640
+ // FIXME : Implement real cssText generation based on properties
641
+ return "@counter-style " + this.name + " { " + this.__props + " }";
642
+ }
643
+ });
644
+
645
+ /**
646
+ * NON-STANDARD
647
+ * Rule text parser.
648
+ * @param {string} cssText
649
+ */
650
+ Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "parse", {
651
+ value: function(cssText) {
652
+ // Extract the name from "@counter-style <name> { ... }"
653
+ var match = cssText.match(/@counter-style\s+([^\s{]+)\s*\{([^]*)\}/);
654
+ if (match) {
655
+ this.name = match[1];
656
+ // Get the text inside the brackets and clean it up
657
+ var propsText = match[2];
658
+ this.__props = propsText.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function (match, quote) {
659
+ return quote ? match : ' ';
660
+ });
661
+ }
662
+ }
663
+ });
557
664
 
558
665
 
559
666
 
@@ -568,9 +675,11 @@ CSSOM.CSSConditionRule = function CSSConditionRule() {
568
675
  this.__conditionText = '';
569
676
  };
570
677
 
571
- CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
678
+ CSSOM.CSSConditionRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
572
679
  CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
573
680
 
681
+ Object.setPrototypeOf(CSSOM.CSSConditionRule, CSSOM.CSSGroupingRule);
682
+
574
683
  Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
575
684
  get: function () {
576
685
  return this.__conditionText;
@@ -593,9 +702,11 @@ CSSOM.CSSStyleRule = function CSSStyleRule() {
593
702
  this.__style.parentRule = this;
594
703
  };
595
704
 
596
- CSSOM.CSSStyleRule.prototype = new CSSOM.CSSGroupingRule();
705
+ CSSOM.CSSStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
597
706
  CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
598
707
 
708
+ Object.setPrototypeOf(CSSOM.CSSStyleRule, CSSOM.CSSGroupingRule);
709
+
599
710
  Object.defineProperty(CSSOM.CSSStyleRule.prototype, "type", {
600
711
  value: 1,
601
712
  writable: false
@@ -659,163 +770,10 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
659
770
  text = "";
660
771
  }
661
772
  return text;
662
- },
663
- set: function(cssText) {
664
- if (typeof cssText === "string") {
665
- var rule = CSSOM.CSSStyleRule.parse(cssText);
666
- this.__style = rule.style;
667
- this.selectorText = rule.selectorText;
668
- }
669
773
  }
670
774
  });
671
775
 
672
776
 
673
- /**
674
- * NON-STANDARD
675
- * lightweight version of parse.js.
676
- * @param {string} ruleText
677
- * @return CSSStyleRule
678
- */
679
- CSSOM.CSSStyleRule.parse = function(ruleText) {
680
- var i = 0;
681
- var state = "selector";
682
- var index;
683
- var j = i;
684
- var buffer = "";
685
-
686
- var SIGNIFICANT_WHITESPACE = {
687
- "selector": true,
688
- "value": true
689
- };
690
-
691
- var styleRule = new CSSOM.CSSStyleRule();
692
- var name, priority="";
693
-
694
- for (var character; (character = ruleText.charAt(i)); i++) {
695
-
696
- switch (character) {
697
-
698
- case " ":
699
- case "\t":
700
- case "\r":
701
- case "\n":
702
- case "\f":
703
- if (SIGNIFICANT_WHITESPACE[state]) {
704
- // Squash 2 or more white-spaces in the row into 1
705
- switch (ruleText.charAt(i - 1)) {
706
- case " ":
707
- case "\t":
708
- case "\r":
709
- case "\n":
710
- case "\f":
711
- break;
712
- default:
713
- buffer += " ";
714
- break;
715
- }
716
- }
717
- break;
718
-
719
- // String
720
- case '"':
721
- j = i + 1;
722
- index = ruleText.indexOf('"', j) + 1;
723
- if (!index) {
724
- throw '" is missing';
725
- }
726
- buffer += ruleText.slice(i, index);
727
- i = index - 1;
728
- break;
729
-
730
- case "'":
731
- j = i + 1;
732
- index = ruleText.indexOf("'", j) + 1;
733
- if (!index) {
734
- throw "' is missing";
735
- }
736
- buffer += ruleText.slice(i, index);
737
- i = index - 1;
738
- break;
739
-
740
- // Comment
741
- case "/":
742
- if (ruleText.charAt(i + 1) === "*") {
743
- i += 2;
744
- index = ruleText.indexOf("*/", i);
745
- if (index === -1) {
746
- throw new SyntaxError("Missing */");
747
- } else {
748
- i = index + 1;
749
- }
750
- } else {
751
- buffer += character;
752
- }
753
- break;
754
-
755
- case "{":
756
- if (state === "selector") {
757
- styleRule.selectorText = buffer.trim();
758
- buffer = "";
759
- state = "name";
760
- }
761
- break;
762
-
763
- case ":":
764
- if (state === "name") {
765
- name = buffer.trim();
766
- buffer = "";
767
- state = "value";
768
- } else {
769
- buffer += character;
770
- }
771
- break;
772
-
773
- case "!":
774
- if (state === "value" && ruleText.indexOf("!important", i) === i) {
775
- priority = "important";
776
- i += "important".length;
777
- } else {
778
- buffer += character;
779
- }
780
- break;
781
-
782
- case ";":
783
- if (state === "value") {
784
- styleRule.style.setProperty(name, buffer.trim(), priority);
785
- priority = "";
786
- buffer = "";
787
- state = "name";
788
- } else {
789
- buffer += character;
790
- }
791
- break;
792
-
793
- case "}":
794
- if (state === "value") {
795
- styleRule.style.setProperty(name, buffer.trim(), priority);
796
- priority = "";
797
- buffer = "";
798
- } else if (state === "name") {
799
- break;
800
- } else {
801
- buffer += character;
802
- }
803
- state = "selector";
804
- break;
805
-
806
- default:
807
- buffer += character;
808
- break;
809
-
810
- }
811
- }
812
-
813
- return styleRule;
814
-
815
- };
816
-
817
-
818
-
819
777
 
820
778
 
821
779
 
@@ -888,9 +846,15 @@ CSSOM.CSSMediaRule = function CSSMediaRule() {
888
846
  this.__media = new CSSOM.MediaList();
889
847
  };
890
848
 
891
- CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
849
+ CSSOM.CSSMediaRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
892
850
  CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
893
- CSSOM.CSSMediaRule.prototype.type = 4;
851
+
852
+ Object.setPrototypeOf(CSSOM.CSSMediaRule, CSSOM.CSSConditionRule);
853
+
854
+ Object.defineProperty(CSSOM.CSSMediaRule.prototype, "type", {
855
+ value: 4,
856
+ writable: false
857
+ });
894
858
 
895
859
  // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
896
860
  Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
@@ -926,9 +890,7 @@ Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
926
890
  }
927
891
  values = valuesArr.join("\n ") + "\n}";
928
892
  return "@media " + this.media.mediaText + values;
929
- },
930
- configurable: true,
931
- enumerable: true
893
+ }
932
894
  }
933
895
  });
934
896
 
@@ -946,9 +908,15 @@ CSSOM.CSSContainerRule = function CSSContainerRule() {
946
908
  CSSOM.CSSConditionRule.call(this);
947
909
  };
948
910
 
949
- CSSOM.CSSContainerRule.prototype = new CSSOM.CSSConditionRule();
911
+ CSSOM.CSSContainerRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
950
912
  CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
951
- CSSOM.CSSContainerRule.prototype.type = 17;
913
+
914
+ Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule);
915
+
916
+ Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", {
917
+ value: 17,
918
+ writable: false
919
+ });
952
920
 
953
921
  Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
954
922
  "cssText": {
@@ -965,9 +933,7 @@ Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
965
933
  }
966
934
  values = valuesArr.join("\n ") + "\n}";
967
935
  return "@container " + this.conditionText + values;
968
- },
969
- configurable: true,
970
- enumerable: true
936
+ }
971
937
  },
972
938
  "containerName": {
973
939
  get: function() {
@@ -1002,9 +968,15 @@ CSSOM.CSSSupportsRule = function CSSSupportsRule() {
1002
968
  CSSOM.CSSConditionRule.call(this);
1003
969
  };
1004
970
 
1005
- CSSOM.CSSSupportsRule.prototype = new CSSOM.CSSConditionRule();
971
+ CSSOM.CSSSupportsRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
1006
972
  CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
1007
- CSSOM.CSSSupportsRule.prototype.type = 12;
973
+
974
+ Object.setPrototypeOf(CSSOM.CSSSupportsRule, CSSOM.CSSConditionRule);
975
+
976
+ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "type", {
977
+ value: 12,
978
+ writable: false
979
+ });
1008
980
 
1009
981
  Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
1010
982
  get: function() {
@@ -1041,9 +1013,11 @@ CSSOM.CSSImportRule = function CSSImportRule() {
1041
1013
  this.__styleSheet = new CSSOM.CSSStyleSheet();
1042
1014
  };
1043
1015
 
1044
- CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
1016
+ CSSOM.CSSImportRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1045
1017
  CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
1046
1018
 
1019
+ Object.setPrototypeOf(CSSOM.CSSImportRule, CSSOM.CSSRule);
1020
+
1047
1021
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
1048
1022
  value: 3,
1049
1023
  writable: false
@@ -1053,8 +1027,53 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1053
1027
  get: function() {
1054
1028
  var mediaText = this.media.mediaText;
1055
1029
  return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
1030
+ }
1031
+ });
1032
+
1033
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
1034
+ get: function() {
1035
+ return this.__href;
1036
+ }
1037
+ });
1038
+
1039
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
1040
+ get: function() {
1041
+ return this.__media;
1056
1042
  },
1057
- set: function(cssText) {
1043
+ set: function(value) {
1044
+ if (typeof value === "string") {
1045
+ this.__media.mediaText = value;
1046
+ } else {
1047
+ this.__media = value;
1048
+ }
1049
+ }
1050
+ });
1051
+
1052
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
1053
+ get: function() {
1054
+ return this.__layerName;
1055
+ }
1056
+ });
1057
+
1058
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
1059
+ get: function() {
1060
+ return this.__supportsText;
1061
+ }
1062
+ });
1063
+
1064
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
1065
+ get: function() {
1066
+ return this.__styleSheet;
1067
+ }
1068
+ });
1069
+
1070
+ /**
1071
+ * NON-STANDARD
1072
+ * Rule text parser.
1073
+ * @param {string} cssText
1074
+ */
1075
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "parse", {
1076
+ value: function(cssText) {
1058
1077
  var i = 0;
1059
1078
 
1060
1079
  /**
@@ -1233,43 +1252,6 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1233
1252
  }
1234
1253
  });
1235
1254
 
1236
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
1237
- get: function() {
1238
- return this.__href;
1239
- }
1240
- });
1241
-
1242
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
1243
- get: function() {
1244
- return this.__media;
1245
- },
1246
- set: function(value) {
1247
- if (typeof value === "string") {
1248
- this.__media.mediaText = value;
1249
- } else {
1250
- this.__media = value;
1251
- }
1252
- }
1253
- });
1254
-
1255
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
1256
- get: function() {
1257
- return this.__layerName;
1258
- }
1259
- });
1260
-
1261
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
1262
- get: function() {
1263
- return this.__supportsText;
1264
- }
1265
- });
1266
-
1267
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
1268
- get: function() {
1269
- return this.__styleSheet;
1270
- }
1271
- });
1272
-
1273
1255
 
1274
1256
 
1275
1257
 
@@ -1285,29 +1267,52 @@ CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
1285
1267
  this.__namespaceURI = "";
1286
1268
  };
1287
1269
 
1288
- CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
1270
+ CSSOM.CSSNamespaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1289
1271
  CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
1290
1272
 
1273
+ Object.setPrototypeOf(CSSOM.CSSNamespaceRule, CSSOM.CSSRule);
1274
+
1291
1275
  Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
1292
1276
  value: 10,
1293
- writable: false
1277
+ writable: false
1294
1278
  });
1295
1279
 
1296
1280
  Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1297
1281
  get: function() {
1298
1282
  return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
1299
- },
1300
- set: function(cssText) {
1301
- var newPrefix = "";
1302
- var newNamespaceURI = "";
1303
-
1304
- // Remove @namespace and trim
1305
- var text = cssText.trim();
1306
- if (text.indexOf('@namespace') === 0) {
1307
- text = text.slice('@namespace'.length).trim();
1308
- }
1283
+ }
1284
+ });
1309
1285
 
1310
- // Remove trailing semicolon if present
1286
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
1287
+ get: function() {
1288
+ return this.__prefix;
1289
+ }
1290
+ });
1291
+
1292
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
1293
+ get: function() {
1294
+ return this.__namespaceURI;
1295
+ }
1296
+ });
1297
+
1298
+
1299
+ /**
1300
+ * NON-STANDARD
1301
+ * Rule text parser.
1302
+ * @param {string} cssText
1303
+ */
1304
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "parse", {
1305
+ value: function(cssText) {
1306
+ var newPrefix = "";
1307
+ var newNamespaceURI = "";
1308
+
1309
+ // Remove @namespace and trim
1310
+ var text = cssText.trim();
1311
+ if (text.indexOf('@namespace') === 0) {
1312
+ text = text.slice('@namespace'.length).trim();
1313
+ }
1314
+
1315
+ // Remove trailing semicolon if present
1311
1316
  if (text.charAt(text.length - 1) === ';') {
1312
1317
  text = text.slice(0, -1).trim();
1313
1318
  }
@@ -1345,19 +1350,6 @@ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1345
1350
  }
1346
1351
  });
1347
1352
 
1348
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
1349
- get: function() {
1350
- return this.__prefix;
1351
- }
1352
- });
1353
-
1354
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
1355
- get: function() {
1356
- return this.__namespaceURI;
1357
- }
1358
- });
1359
-
1360
-
1361
1353
 
1362
1354
 
1363
1355
 
@@ -1371,9 +1363,16 @@ CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
1371
1363
  this.__style.parentRule = this;
1372
1364
  };
1373
1365
 
1374
- CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule();
1366
+ CSSOM.CSSFontFaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1375
1367
  CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
1376
- CSSOM.CSSFontFaceRule.prototype.type = 5;
1368
+
1369
+ Object.setPrototypeOf(CSSOM.CSSFontFaceRule, CSSOM.CSSRule);
1370
+
1371
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "type", {
1372
+ value: 5,
1373
+ writable: false
1374
+ });
1375
+
1377
1376
  //FIXME
1378
1377
  //CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1379
1378
  //CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1414,9 +1413,16 @@ CSSOM.CSSHostRule = function CSSHostRule() {
1414
1413
  this.cssRules = new CSSOM.CSSRuleList();
1415
1414
  };
1416
1415
 
1417
- CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule();
1416
+ CSSOM.CSSHostRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1418
1417
  CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
1419
- CSSOM.CSSHostRule.prototype.type = 1001;
1418
+
1419
+ Object.setPrototypeOf(CSSOM.CSSHostRule, CSSOM.CSSRule);
1420
+
1421
+ Object.defineProperty(CSSOM.CSSHostRule.prototype, "type", {
1422
+ value: 1001,
1423
+ writable: false
1424
+ });
1425
+
1420
1426
  //FIXME
1421
1427
  //CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1422
1428
  //CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1451,9 +1457,16 @@ CSSOM.CSSStartingStyleRule = function CSSStartingStyleRule() {
1451
1457
  CSSOM.CSSGroupingRule.call(this);
1452
1458
  };
1453
1459
 
1454
- CSSOM.CSSStartingStyleRule.prototype = new CSSOM.CSSGroupingRule();
1460
+ CSSOM.CSSStartingStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
1455
1461
  CSSOM.CSSStartingStyleRule.prototype.constructor = CSSOM.CSSStartingStyleRule;
1456
- CSSOM.CSSStartingStyleRule.prototype.type = 1002;
1462
+
1463
+ Object.setPrototypeOf(CSSOM.CSSStartingStyleRule, CSSOM.CSSGroupingRule);
1464
+
1465
+ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "type", {
1466
+ value: 1002,
1467
+ writable: false
1468
+ });
1469
+
1457
1470
  //FIXME
1458
1471
  //CSSOM.CSSStartingStyleRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1459
1472
  //CSSOM.CSSStartingStyleRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1481,15 +1494,38 @@ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
1481
1494
 
1482
1495
 
1483
1496
  /**
1484
- * @constructor
1485
1497
  * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
1486
1498
  */
1487
1499
  CSSOM.StyleSheet = function StyleSheet() {
1500
+ this.__href = null;
1501
+ this.__ownerNode = null;
1502
+ this.__title = null;
1488
1503
  this.__media = new CSSOM.MediaList();
1489
1504
  this.__parentStyleSheet = null;
1505
+ this.disabled = false;
1490
1506
  };
1491
1507
 
1492
1508
  Object.defineProperties(CSSOM.StyleSheet.prototype, {
1509
+ type: {
1510
+ get: function() {
1511
+ return "text/css";
1512
+ }
1513
+ },
1514
+ href: {
1515
+ get: function() {
1516
+ return this.__href;
1517
+ }
1518
+ },
1519
+ ownerNode: {
1520
+ get: function() {
1521
+ return this.__ownerNode;
1522
+ }
1523
+ },
1524
+ title: {
1525
+ get: function() {
1526
+ return this.__title;
1527
+ }
1528
+ },
1493
1529
  media: {
1494
1530
  get: function() {
1495
1531
  return this.__media;
@@ -1515,21 +1551,52 @@ Object.defineProperties(CSSOM.StyleSheet.prototype, {
1515
1551
 
1516
1552
  /**
1517
1553
  * @constructor
1554
+ * @param {CSSStyleSheetInit} [opts] - CSSStyleSheetInit options.
1555
+ * @param {string} [opts.baseURL] - The base URL of the stylesheet.
1556
+ * @param {boolean} [opts.disabled] - The disabled attribute of the stylesheet.
1557
+ * @param {MediaList | string} [opts.media] - The media attribute of the stylesheet.
1518
1558
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
1519
1559
  */
1520
- CSSOM.CSSStyleSheet = function CSSStyleSheet() {
1560
+ CSSOM.CSSStyleSheet = function CSSStyleSheet(opts) {
1521
1561
  CSSOM.StyleSheet.call(this);
1522
1562
  this.__constructed = true;
1523
- this.cssRules = new CSSOM.CSSRuleList();
1563
+ this.__cssRules = new CSSOM.CSSRuleList();
1564
+ this.__ownerRule = null;
1565
+
1566
+ if (opts && typeof opts === "object") {
1567
+ if (opts.baseURL && typeof opts.baseURL === "string") {
1568
+ this.__baseURL = opts.baseURL;
1569
+ }
1570
+ if (opts.media && typeof opts.media === "string") {
1571
+ this.media.mediaText = opts.media;
1572
+ }
1573
+ if (typeof opts.disabled === "boolean") {
1574
+ this.disabled = opts.disabled;
1575
+ }
1576
+ }
1524
1577
  };
1525
1578
 
1526
1579
 
1527
- CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
1580
+ CSSOM.CSSStyleSheet.prototype = Object.create(CSSOM.StyleSheet.prototype);
1528
1581
  CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
1529
1582
 
1583
+ Object.setPrototypeOf(CSSOM.CSSStyleSheet, CSSOM.StyleSheet);
1584
+
1585
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "cssRules", {
1586
+ get: function() {
1587
+ return this.__cssRules;
1588
+ }
1589
+ });
1590
+
1530
1591
  Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
1531
1592
  get: function() {
1532
- return this.cssRules;
1593
+ return this.__cssRules;
1594
+ }
1595
+ });
1596
+
1597
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "ownerRule", {
1598
+ get: function() {
1599
+ return this.__ownerRule;
1533
1600
  }
1534
1601
  });
1535
1602
 
@@ -1741,8 +1808,8 @@ CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
1741
1808
  */
1742
1809
  CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1743
1810
  var _Promise;
1744
- if (this.__globalObject) {
1745
- _Promise = this.__globalObject['Promise'];
1811
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()['Promise']) {
1812
+ _Promise = CSSOM.getGlobalObject()['Promise'];
1746
1813
  } else {
1747
1814
  _Promise = Promise;
1748
1815
  }
@@ -1772,7 +1839,7 @@ CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1772
1839
  }
1773
1840
  }
1774
1841
  // Set sheet's CSS rules to rules.
1775
- sheet.cssRules = rules;
1842
+ sheet.__cssRules = rules;
1776
1843
  // Unset sheet’s disallow modification flag.
1777
1844
  delete sheet.__disallowModification;
1778
1845
  // Resolve promise with sheet.
@@ -1807,7 +1874,7 @@ CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
1807
1874
  }
1808
1875
  }
1809
1876
  // Set sheet's CSS rules to rules.
1810
- sheet.cssRules = rules;
1877
+ sheet.__cssRules = rules;
1811
1878
  }
1812
1879
 
1813
1880
  /**
@@ -1869,9 +1936,15 @@ CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
1869
1936
  });
1870
1937
  };
1871
1938
 
1872
- CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule();
1939
+ CSSOM.CSSKeyframesRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1873
1940
  CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
1874
- CSSOM.CSSKeyframesRule.prototype.type = 7;
1941
+
1942
+ Object.setPrototypeOf(CSSOM.CSSKeyframesRule, CSSOM.CSSRule);
1943
+
1944
+ Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "type", {
1945
+ value: 7,
1946
+ writable: false
1947
+ });
1875
1948
 
1876
1949
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
1877
1950
  Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
@@ -2071,9 +2144,16 @@ CSSOM.CSSKeyframeRule = function CSSKeyframeRule() {
2071
2144
  this.__style.parentRule = this;
2072
2145
  };
2073
2146
 
2074
- CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule();
2147
+ CSSOM.CSSKeyframeRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2075
2148
  CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule;
2076
- CSSOM.CSSKeyframeRule.prototype.type = 8;
2149
+
2150
+ Object.setPrototypeOf(CSSOM.CSSKeyframeRule, CSSOM.CSSRule);
2151
+
2152
+ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "type", {
2153
+ value: 8,
2154
+ writable: false
2155
+ });
2156
+
2077
2157
  //FIXME
2078
2158
  //CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
2079
2159
  //CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2172,9 +2252,16 @@ CSSOM.CSSDocumentRule = function CSSDocumentRule() {
2172
2252
  this.cssRules = new CSSOM.CSSRuleList();
2173
2253
  };
2174
2254
 
2175
- CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule();
2255
+ CSSOM.CSSDocumentRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2176
2256
  CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule;
2177
- CSSOM.CSSDocumentRule.prototype.type = 10;
2257
+
2258
+ Object.setPrototypeOf(CSSOM.CSSDocumentRule, CSSOM.CSSRule);
2259
+
2260
+ Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "type", {
2261
+ value: 10,
2262
+ writable: false
2263
+ });
2264
+
2178
2265
  //FIXME
2179
2266
  //CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
2180
2267
  //CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2243,9 +2330,11 @@ CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
2243
2330
  this._idx = idx;
2244
2331
  };
2245
2332
 
2246
- CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue();
2333
+ CSSOM.CSSValueExpression.prototype = Object.create(CSSOM.CSSValue.prototype);
2247
2334
  CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
2248
2335
 
2336
+ Object.setPrototypeOf(CSSOM.CSSValueExpression, CSSOM.CSSValue);
2337
+
2249
2338
  /**
2250
2339
  * parse css expression() value
2251
2340
  *
@@ -2580,9 +2669,10 @@ CSSOM.CSSScopeRule = function CSSScopeRule() {
2580
2669
  this.__end = null;
2581
2670
  };
2582
2671
 
2583
- CSSOM.CSSScopeRule.prototype = new CSSOM.CSSGroupingRule();
2672
+ CSSOM.CSSScopeRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2584
2673
  CSSOM.CSSScopeRule.prototype.constructor = CSSOM.CSSScopeRule;
2585
2674
 
2675
+ Object.setPrototypeOf(CSSOM.CSSScopeRule, CSSOM.CSSGroupingRule);
2586
2676
 
2587
2677
  Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
2588
2678
  type: {
@@ -2631,9 +2721,15 @@ CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
2631
2721
  this.name = "";
2632
2722
  };
2633
2723
 
2634
- CSSOM.CSSLayerBlockRule.prototype = new CSSOM.CSSGroupingRule();
2724
+ CSSOM.CSSLayerBlockRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2635
2725
  CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
2636
- CSSOM.CSSLayerBlockRule.prototype.type = 18;
2726
+
2727
+ Object.setPrototypeOf(CSSOM.CSSLayerBlockRule, CSSOM.CSSRule);
2728
+
2729
+ Object.defineProperty(CSSOM.CSSLayerBlockRule.prototype, "type", {
2730
+ value: 18,
2731
+ writable: false
2732
+ });
2637
2733
 
2638
2734
  Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
2639
2735
  cssText: {
@@ -2650,9 +2746,7 @@ Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
2650
2746
  }
2651
2747
  values = valuesArr.join("\n ") + "\n}";
2652
2748
  return "@layer" + (this.name ? " " + this.name : "") + values;
2653
- },
2654
- configurable: true,
2655
- enumerable: true,
2749
+ }
2656
2750
  },
2657
2751
  });
2658
2752
 
@@ -2668,17 +2762,21 @@ CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
2668
2762
  this.nameList = [];
2669
2763
  };
2670
2764
 
2671
- CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
2765
+ CSSOM.CSSLayerStatementRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2672
2766
  CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
2673
- CSSOM.CSSLayerStatementRule.prototype.type = 0;
2767
+
2768
+ Object.setPrototypeOf(CSSOM.CSSLayerStatementRule, CSSOM.CSSRule);
2769
+
2770
+ Object.defineProperty(CSSOM.CSSLayerStatementRule.prototype, "type", {
2771
+ value: 0,
2772
+ writable: false
2773
+ });
2674
2774
 
2675
2775
  Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2676
2776
  cssText: {
2677
2777
  get: function () {
2678
2778
  return "@layer " + this.nameList.join(", ") + ";";
2679
- },
2680
- configurable: true,
2681
- enumerable: true,
2779
+ }
2682
2780
  },
2683
2781
  });
2684
2782
 
@@ -2696,9 +2794,11 @@ CSSOM.CSSPageRule = function CSSPageRule() {
2696
2794
  this.__style.parentRule = this;
2697
2795
  };
2698
2796
 
2699
- CSSOM.CSSPageRule.prototype = new CSSOM.CSSGroupingRule();
2797
+ CSSOM.CSSPageRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2700
2798
  CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
2701
2799
 
2800
+ Object.setPrototypeOf(CSSOM.CSSPageRule, CSSOM.CSSGroupingRule);
2801
+
2702
2802
  Object.defineProperty(CSSOM.CSSPageRule.prototype, "type", {
2703
2803
  value: 6,
2704
2804
  writable: false
@@ -2793,161 +2893,9 @@ Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
2793
2893
  values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
2794
2894
  }
2795
2895
  return "@page" + (this.selectorText ? " " + this.selectorText : "") + values;
2796
- },
2797
- set: function(cssText) {
2798
- if (typeof value === "string") {
2799
- var rule = CSSOM.CSSPageRule.parse(cssText);
2800
- this.__style = rule.style;
2801
- this.selectorText = rule.selectorText;
2802
- }
2803
2896
  }
2804
2897
  });
2805
2898
 
2806
- /**
2807
- * NON-STANDARD
2808
- * lightweight version of parse.js.
2809
- * @param {string} ruleText
2810
- * @return CSSPageRule
2811
- */
2812
- CSSOM.CSSPageRule.parse = function(ruleText) {
2813
- var i = 0;
2814
- var state = "selector";
2815
- var index;
2816
- var j = i;
2817
- var buffer = "";
2818
-
2819
- var SIGNIFICANT_WHITESPACE = {
2820
- "selector": true,
2821
- "value": true
2822
- };
2823
-
2824
- var pageRule = new CSSOM.CSSPageRule();
2825
- var name, priority="";
2826
-
2827
- for (var character; (character = ruleText.charAt(i)); i++) {
2828
-
2829
- switch (character) {
2830
-
2831
- case " ":
2832
- case "\t":
2833
- case "\r":
2834
- case "\n":
2835
- case "\f":
2836
- if (SIGNIFICANT_WHITESPACE[state]) {
2837
- // Squash 2 or more white-spaces in the row into 1
2838
- switch (ruleText.charAt(i - 1)) {
2839
- case " ":
2840
- case "\t":
2841
- case "\r":
2842
- case "\n":
2843
- case "\f":
2844
- break;
2845
- default:
2846
- buffer += " ";
2847
- break;
2848
- }
2849
- }
2850
- break;
2851
-
2852
- // String
2853
- case '"':
2854
- j = i + 1;
2855
- index = ruleText.indexOf('"', j) + 1;
2856
- if (!index) {
2857
- throw '" is missing';
2858
- }
2859
- buffer += ruleText.slice(i, index);
2860
- i = index - 1;
2861
- break;
2862
-
2863
- case "'":
2864
- j = i + 1;
2865
- index = ruleText.indexOf("'", j) + 1;
2866
- if (!index) {
2867
- throw "' is missing";
2868
- }
2869
- buffer += ruleText.slice(i, index);
2870
- i = index - 1;
2871
- break;
2872
-
2873
- // Comment
2874
- case "/":
2875
- if (ruleText.charAt(i + 1) === "*") {
2876
- i += 2;
2877
- index = ruleText.indexOf("*/", i);
2878
- if (index === -1) {
2879
- throw new SyntaxError("Missing */");
2880
- } else {
2881
- i = index + 1;
2882
- }
2883
- } else {
2884
- buffer += character;
2885
- }
2886
- break;
2887
-
2888
- case "{":
2889
- if (state === "selector") {
2890
- pageRule.selectorText = buffer.trim();
2891
- buffer = "";
2892
- state = "name";
2893
- }
2894
- break;
2895
-
2896
- case ":":
2897
- if (state === "name") {
2898
- name = buffer.trim();
2899
- buffer = "";
2900
- state = "value";
2901
- } else {
2902
- buffer += character;
2903
- }
2904
- break;
2905
-
2906
- case "!":
2907
- if (state === "value" && ruleText.indexOf("!important", i) === i) {
2908
- priority = "important";
2909
- i += "important".length;
2910
- } else {
2911
- buffer += character;
2912
- }
2913
- break;
2914
-
2915
- case ";":
2916
- if (state === "value") {
2917
- pageRule.style.setProperty(name, buffer.trim(), priority);
2918
- priority = "";
2919
- buffer = "";
2920
- state = "name";
2921
- } else {
2922
- buffer += character;
2923
- }
2924
- break;
2925
-
2926
- case "}":
2927
- if (state === "value") {
2928
- pageRule.style.setProperty(name, buffer.trim(), priority);
2929
- priority = "";
2930
- buffer = "";
2931
- } else if (state === "name") {
2932
- break;
2933
- } else {
2934
- buffer += character;
2935
- }
2936
- state = "selector";
2937
- break;
2938
-
2939
- default:
2940
- buffer += character;
2941
- break;
2942
-
2943
- }
2944
- }
2945
-
2946
- return pageRule;
2947
-
2948
- };
2949
-
2950
-
2951
2899
 
2952
2900
 
2953
2901
 
@@ -2956,7 +2904,9 @@ CSSOM.CSSPageRule.parse = function(ruleText) {
2956
2904
  *
2957
2905
  * @param {string} token - The CSS string to parse.
2958
2906
  * @param {object} [opts] - Optional parsing options.
2959
- * @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
2907
+ * @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.
2908
+ * @param {Element | ProcessingInstruction} [opts.ownerNode] - The owner node of the stylesheet.
2909
+ * @param {CSSRule} [opts.ownerRule] - The owner rule of the stylesheet.
2960
2910
  * @param {CSSOM.CSSStyleSheet} [opts.styleSheet] - Reuse a style sheet instead of creating a new one (e.g. as `parentStyleSheet`)
2961
2911
  * @param {CSSOM.CSSRuleList} [opts.cssRules] - Prepare all rules in this list instead of mutating the style sheet continually
2962
2912
  * @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
@@ -3010,6 +2960,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3010
2960
  styleSheet = opts.styleSheet;
3011
2961
  } else {
3012
2962
  styleSheet = new CSSOM.CSSStyleSheet()
2963
+ styleSheet.__constructed = false;
3013
2964
  }
3014
2965
 
3015
2966
  var topScope;
@@ -3020,7 +2971,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3020
2971
  }
3021
2972
 
3022
2973
  if (opts && opts.globalObject) {
3023
- styleSheet.__globalObject = opts.globalObject;
2974
+ CSSOM.setup({ globalObject: opts.globalObject });
2975
+ }
2976
+
2977
+ if (opts && opts.ownerNode) {
2978
+ styleSheet.__ownerNode = opts.ownerNode;
2979
+ }
2980
+
2981
+ if (opts && opts.ownerRule) {
2982
+ styleSheet.__ownerRule = opts.ownerRule;
3024
2983
  }
3025
2984
 
3026
2985
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
@@ -3032,7 +2991,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3032
2991
  var ancestorRules = [];
3033
2992
  var prevScope;
3034
2993
 
3035
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
2994
+ var name, priority = "", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
3036
2995
 
3037
2996
  // Track defined namespace prefixes for validation
3038
2997
  var definedNamespacePrefixes = {};
@@ -3130,14 +3089,14 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3130
3089
  var ruleClosingMatch = matchBalancedBlock(str, fromIndex);
3131
3090
  if (ruleClosingMatch) {
3132
3091
  var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
3133
- i+= ignoreRange;
3092
+ i += ignoreRange;
3134
3093
  if (token.charAt(i) === '}') {
3135
3094
  i -= 1;
3136
3095
  }
3137
3096
  } else {
3138
3097
  i += str.length;
3139
3098
  }
3140
- return i;
3099
+ return i;
3141
3100
  }
3142
3101
 
3143
3102
  /**
@@ -3147,29 +3106,29 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3147
3106
  */
3148
3107
  function parseScopePrelude(preludeContent) {
3149
3108
  var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
3150
-
3109
+
3151
3110
  // Restore the parentheses that were consumed by the split
3152
3111
  if (parts.length === 2) {
3153
3112
  parts[0] = parts[0] + ')';
3154
3113
  parts[1] = '(' + parts[1];
3155
3114
  }
3156
-
3115
+
3157
3116
  var hasStart = parts[0] &&
3158
3117
  parts[0].charAt(0) === '(' &&
3159
3118
  parts[0].charAt(parts[0].length - 1) === ')';
3160
3119
  var hasEnd = parts[1] &&
3161
3120
  parts[1].charAt(0) === '(' &&
3162
3121
  parts[1].charAt(parts[1].length - 1) === ')';
3163
-
3122
+
3164
3123
  // Handle case: @scope to (<end>)
3165
3124
  var hasOnlyEnd = !hasStart &&
3166
3125
  !hasEnd &&
3167
3126
  parts[0].indexOf('to (') === 0 &&
3168
3127
  parts[0].charAt(parts[0].length - 1) === ')';
3169
-
3128
+
3170
3129
  var startSelector = '';
3171
3130
  var endSelector = '';
3172
-
3131
+
3173
3132
  if (hasStart) {
3174
3133
  startSelector = parts[0].slice(1, -1).trim();
3175
3134
  }
@@ -3179,7 +3138,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3179
3138
  if (hasOnlyEnd) {
3180
3139
  endSelector = parts[0].slice(4, -1).trim();
3181
3140
  }
3182
-
3141
+
3183
3142
  return {
3184
3143
  startSelector: startSelector,
3185
3144
  endSelector: endSelector,
@@ -3217,11 +3176,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3217
3176
  var inDoubleQuote = false;
3218
3177
  var inAttr = false;
3219
3178
  var stack = useStack ? [] : null;
3220
-
3179
+
3221
3180
  for (var i = 0; i < selector.length; i++) {
3222
3181
  var char = selector[i];
3223
3182
  var prevChar = i > 0 ? selector[i - 1] : '';
3224
-
3183
+
3225
3184
  if (inSingleQuote) {
3226
3185
  if (char === "'" && prevChar !== "\\") {
3227
3186
  inSingleQuote = false;
@@ -3272,7 +3231,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3272
3231
  }
3273
3232
  }
3274
3233
  }
3275
-
3234
+
3276
3235
  // Check if everything is balanced
3277
3236
  if (useStack) {
3278
3237
  return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
@@ -3322,7 +3281,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3322
3281
  /(?:^|[\s>+~,\[])cue\s*\(/i,
3323
3282
  /(?:^|[\s>+~,\[])cue-region\s*\(/i
3324
3283
  ];
3325
-
3284
+
3326
3285
  for (var i = 0; i < invalidPatterns.length; i++) {
3327
3286
  if (invalidPatterns[i].test(selector)) {
3328
3287
  return true;
@@ -3368,7 +3327,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3368
3327
  isValid = true;
3369
3328
  }
3370
3329
  }
3371
-
3330
+
3372
3331
  // Additional validation for @scope rule
3373
3332
  if (isValid && atRuleKey === "@scope") {
3374
3333
  var openBraceIndex = ruleSlice.indexOf('{');
@@ -3387,7 +3346,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3387
3346
  var hasStart = parsedScopePrelude.hasStart;
3388
3347
  var hasEnd = parsedScopePrelude.hasEnd;
3389
3348
  var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
3390
-
3349
+
3391
3350
  // Validation rules for @scope:
3392
3351
  // 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
3393
3352
  if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
@@ -3423,13 +3382,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3423
3382
  if (openBraceIndex !== -1) {
3424
3383
  // Extract the rule prelude (everything between the at-rule and {)
3425
3384
  var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
3426
-
3385
+
3427
3386
  // Skip past at-rule keyword and whitespace
3428
3387
  var preludeContent = rulePrelude.slice("@page".length).trim();
3429
3388
 
3430
3389
  if (preludeContent.length > 0) {
3431
3390
  var trimmedValue = preludeContent.trim();
3432
-
3391
+
3433
3392
  // Empty selector is valid for @page
3434
3393
  if (trimmedValue !== '') {
3435
3394
  // Parse @page selectorText for page name and pseudo-pages
@@ -3453,7 +3412,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3453
3412
 
3454
3413
  // Validate pseudo-pages if present
3455
3414
  if (pseudoPages) {
3456
- var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
3415
+ var pseudos = pseudoPages.split(':').filter(function (p) { return p; });
3457
3416
  var validPseudos = ['left', 'right', 'first', 'blank'];
3458
3417
  var allValid = true;
3459
3418
  for (var j = 0; j < pseudos.length; j++) {
@@ -3462,7 +3421,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3462
3421
  break;
3463
3422
  }
3464
3423
  }
3465
-
3424
+
3466
3425
  if (!allValid) {
3467
3426
  isValid = false;
3468
3427
  }
@@ -3471,15 +3430,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3471
3430
  isValid = false;
3472
3431
  }
3473
3432
  }
3474
-
3433
+
3475
3434
  }
3476
3435
  }
3477
3436
  }
3478
-
3437
+
3479
3438
  if (!isValid) {
3480
3439
  // If it's invalid the browser will simply ignore the entire invalid block
3481
3440
  // Use regex to find the closing brace of the invalid rule
3482
-
3441
+
3483
3442
  // Regex used above is not ES5 compliant. Using alternative.
3484
3443
  // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
3485
3444
  var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
@@ -3494,7 +3453,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3494
3453
  // Check if there's a semicolon before the invalid at-rule and the first opening brace
3495
3454
  if (atRuleKey === "@layer") {
3496
3455
  var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
3497
- if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
3456
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";") {
3498
3457
  // Ignore the rule block until the semicolon
3499
3458
  i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
3500
3459
  state = "before-selector";
@@ -3512,7 +3471,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3512
3471
 
3513
3472
  // Helper functions for looseSelectorValidator
3514
3473
  // Defined outside to avoid recreation on every validation call
3515
-
3474
+
3516
3475
  /**
3517
3476
  * Check if character is a valid identifier start
3518
3477
  * @param {string} c - Character to check
@@ -3584,19 +3543,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3584
3543
  // Helper: Parse namespace prefix (optional)
3585
3544
  function parseNamespace() {
3586
3545
  var start = i;
3587
-
3546
+
3588
3547
  // Match: *| or identifier| or |
3589
3548
  if (i < len && selector[i] === '*') {
3590
3549
  i++;
3591
3550
  } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
3592
3551
  parseIdentifier();
3593
3552
  }
3594
-
3553
+
3595
3554
  if (i < len && selector[i] === '|') {
3596
3555
  i++;
3597
3556
  return true;
3598
3557
  }
3599
-
3558
+
3600
3559
  // Rollback if no pipe found
3601
3560
  i = start;
3602
3561
  return false;
@@ -3607,15 +3566,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3607
3566
  if (i >= len || selector[i] !== '(') {
3608
3567
  return false;
3609
3568
  }
3610
-
3569
+
3611
3570
  i++; // Skip opening paren
3612
3571
  var depth = 1;
3613
3572
  var inString = false;
3614
3573
  var stringChar = '';
3615
-
3574
+
3616
3575
  while (i < len && depth > 0) {
3617
3576
  var c = selector[i];
3618
-
3577
+
3619
3578
  if (c === '\\' && i + 1 < len) {
3620
3579
  i += 2; // Skip escaped character
3621
3580
  } else if (!inString && (c === '"' || c === '\'')) {
@@ -3635,7 +3594,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3635
3594
  i++;
3636
3595
  }
3637
3596
  }
3638
-
3597
+
3639
3598
  return depth === 0;
3640
3599
  }
3641
3600
 
@@ -3737,7 +3696,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3737
3696
  // Match type selector with optional namespace: [namespace|]identifier
3738
3697
  else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|')) {
3739
3698
  parseNamespace(); // Optional namespace prefix
3740
-
3699
+
3741
3700
  if (i < len && selector[i] === '*') {
3742
3701
  i++; // Universal selector
3743
3702
  hasMatchedComponent = true;
@@ -3813,36 +3772,36 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3813
3772
  // Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
3814
3773
  // Using iterative parsing instead of regex to avoid exponential backtracking
3815
3774
  var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
3816
-
3775
+
3817
3776
  for (var idx = 0; idx < selector.length; idx++) {
3818
3777
  // Look for pseudo-class/element start
3819
3778
  if (selector[idx] === ':') {
3820
3779
  var pseudoStart = idx;
3821
3780
  idx++;
3822
-
3781
+
3823
3782
  // Skip second colon for pseudo-elements
3824
3783
  if (idx < selector.length && selector[idx] === ':') {
3825
3784
  idx++;
3826
3785
  }
3827
-
3786
+
3828
3787
  // Extract pseudo name
3829
3788
  var nameStart = idx;
3830
3789
  while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
3831
3790
  idx++;
3832
3791
  }
3833
-
3792
+
3834
3793
  if (idx === nameStart) {
3835
3794
  continue; // No name found
3836
3795
  }
3837
-
3796
+
3838
3797
  var pseudoName = selector.substring(nameStart, idx).toLowerCase();
3839
-
3798
+
3840
3799
  // Check if this pseudo has arguments
3841
3800
  if (idx < selector.length && selector[idx] === '(') {
3842
3801
  idx++;
3843
3802
  var contentStart = idx;
3844
3803
  var depth = 1;
3845
-
3804
+
3846
3805
  // Find matching closing paren (handle nesting)
3847
3806
  while (idx < selector.length && depth > 0) {
3848
3807
  if (selector[idx] === '\\') {
@@ -3857,10 +3816,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3857
3816
  idx++;
3858
3817
  }
3859
3818
  }
3860
-
3819
+
3861
3820
  if (depth === 0) {
3862
3821
  var pseudoContent = selector.substring(contentStart, idx - 1);
3863
-
3822
+
3864
3823
  // Check if this pseudo should not have quoted strings
3865
3824
  for (var j = 0; j < noQuotesPseudos.length; j++) {
3866
3825
  if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
@@ -3875,7 +3834,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3875
3834
  // Use the iterative validator to avoid regex backtracking issues
3876
3835
  return looseSelectorValidator(selector);
3877
3836
  }
3878
-
3837
+
3879
3838
  /**
3880
3839
  * Regular expression to match CSS pseudo-classes with arguments.
3881
3840
  *
@@ -3894,7 +3853,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3894
3853
  *
3895
3854
  * REPLACED WITH FUNCTION to avoid exponential backtracking.
3896
3855
  */
3897
-
3856
+
3898
3857
  /**
3899
3858
  * Extract pseudo-classes with arguments from a selector using iterative parsing.
3900
3859
  * Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
@@ -3910,30 +3869,30 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3910
3869
  */
3911
3870
  function extractPseudoClasses(selector) {
3912
3871
  var matches = [];
3913
-
3872
+
3914
3873
  for (var i = 0; i < selector.length; i++) {
3915
3874
  // Look for pseudo-class start (single or double colon)
3916
3875
  if (selector[i] === ':') {
3917
3876
  var pseudoStart = i;
3918
3877
  i++;
3919
-
3878
+
3920
3879
  // Skip second colon for pseudo-elements (::)
3921
3880
  if (i < selector.length && selector[i] === ':') {
3922
3881
  i++;
3923
3882
  }
3924
-
3883
+
3925
3884
  // Extract pseudo name
3926
3885
  var nameStart = i;
3927
3886
  while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
3928
3887
  i++;
3929
3888
  }
3930
-
3889
+
3931
3890
  if (i === nameStart) {
3932
3891
  continue; // No name found
3933
3892
  }
3934
-
3893
+
3935
3894
  var pseudoName = selector.substring(nameStart, i);
3936
-
3895
+
3937
3896
  // Check if this pseudo has arguments
3938
3897
  if (i < selector.length && selector[i] === '(') {
3939
3898
  i++;
@@ -3941,11 +3900,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3941
3900
  var depth = 1;
3942
3901
  var inSingleQuote = false;
3943
3902
  var inDoubleQuote = false;
3944
-
3903
+
3945
3904
  // Find matching closing paren (handle nesting and strings)
3946
3905
  while (i < selector.length && depth > 0) {
3947
3906
  var ch = selector[i];
3948
-
3907
+
3949
3908
  if (ch === '\\') {
3950
3909
  i += 2; // Skip escaped character
3951
3910
  } else if (ch === "'" && !inDoubleQuote) {
@@ -3964,21 +3923,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3964
3923
  i++;
3965
3924
  }
3966
3925
  }
3967
-
3926
+
3968
3927
  if (depth === 0) {
3969
3928
  var pseudoArgs = selector.substring(argsStart, i - 1);
3970
3929
  var fullMatch = selector.substring(pseudoStart, i);
3971
-
3930
+
3972
3931
  // Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
3973
3932
  matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
3974
3933
  }
3975
-
3934
+
3976
3935
  // Move back one since loop will increment
3977
3936
  i--;
3978
3937
  }
3979
3938
  }
3980
3939
  }
3981
-
3940
+
3982
3941
  return matches;
3983
3942
  }
3984
3943
 
@@ -4082,6 +4041,23 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4082
4041
  var pseudoClass = pseudoClassMatches[j][1];
4083
4042
  if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
4084
4043
  var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
4044
+
4045
+ // Check if ANY selector in the list contains & (nesting selector)
4046
+ // If so, skip validation for the entire selector list since & will be replaced at runtime
4047
+ var hasAmpersand = false;
4048
+ for (var k = 0; k < nestedSelectors.length; k++) {
4049
+ if (/&/.test(nestedSelectors[k])) {
4050
+ hasAmpersand = true;
4051
+ break;
4052
+ }
4053
+ }
4054
+
4055
+ // If any selector has &, skip validation for this entire pseudo-class
4056
+ if (hasAmpersand) {
4057
+ continue;
4058
+ }
4059
+
4060
+ // Otherwise, validate each selector normally
4085
4061
  for (var i = 0; i < nestedSelectors.length; i++) {
4086
4062
  var nestedSelector = nestedSelectors[i];
4087
4063
  if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
@@ -4118,10 +4094,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4118
4094
  var inAttr = false;
4119
4095
  var inSingleQuote = false;
4120
4096
  var inDoubleQuote = false;
4121
-
4097
+
4122
4098
  for (var i = 0; i < selector.length; i++) {
4123
4099
  var char = selector[i];
4124
-
4100
+
4125
4101
  if (inSingleQuote) {
4126
4102
  if (char === "'" && selector[i - 1] !== "\\") {
4127
4103
  inSingleQuote = false;
@@ -4148,18 +4124,18 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4148
4124
  }
4149
4125
  }
4150
4126
  }
4151
-
4127
+
4152
4128
  if (pipeIndex === -1) {
4153
4129
  return true; // No namespace, always valid
4154
4130
  }
4155
-
4131
+
4156
4132
  var namespacePrefix = selector.substring(0, pipeIndex);
4157
-
4133
+
4158
4134
  // Universal namespace (*|) and default namespace (|) are always valid
4159
4135
  if (namespacePrefix === '*' || namespacePrefix === '') {
4160
4136
  return true;
4161
4137
  }
4162
-
4138
+
4163
4139
  // Check if the custom namespace prefix is defined
4164
4140
  return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
4165
4141
  }
@@ -4168,22 +4144,92 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4168
4144
  * Processes a CSS selector text
4169
4145
  *
4170
4146
  * @param {string} selectorText - The CSS selector text to process
4171
- * @returns {string} The processed selector text with normalized whitespace
4147
+ * @returns {string} The processed selector text with normalized whitespace and invalid selectors removed
4172
4148
  */
4173
4149
  function processSelectorText(selectorText) {
4174
- // TODO: Remove invalid selectors that appears inside pseudo classes
4175
- // TODO: The same processing here needs to be reused in CSSStyleRule.selectorText setter
4176
- // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
4177
-
4178
- /**
4179
- * Normalizes whitespace and preserving quoted strings.
4180
- * Replaces all newline characters (CRLF, CR, or LF) with spaces while keeping quoted
4181
- * strings (single or double quotes) intact, including any escaped characters within them.
4182
- */
4183
- return selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
4150
+ // Normalize whitespace first
4151
+ var normalized = selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function (match, _, newline) {
4184
4152
  if (newline) return " ";
4185
4153
  return match;
4186
4154
  });
4155
+
4156
+ // Recursively process pseudo-classes to handle nesting
4157
+ return processNestedPseudoClasses(normalized);
4158
+ }
4159
+
4160
+ /**
4161
+ * Recursively processes pseudo-classes to filter invalid selectors
4162
+ *
4163
+ * @param {string} selectorText - The CSS selector text to process
4164
+ * @param {number} depth - Current recursion depth (to prevent infinite loops)
4165
+ * @returns {string} The processed selector text with invalid selectors removed
4166
+ */
4167
+ function processNestedPseudoClasses(selectorText, depth) {
4168
+ // Prevent infinite recursion
4169
+ if (typeof depth === 'undefined') {
4170
+ depth = 0;
4171
+ }
4172
+ if (depth > 10) {
4173
+ return selectorText;
4174
+ }
4175
+
4176
+ var pseudoClassMatches = extractPseudoClasses(selectorText);
4177
+
4178
+ // If no pseudo-classes found, return as-is
4179
+ if (pseudoClassMatches.length === 0) {
4180
+ return selectorText;
4181
+ }
4182
+
4183
+ // Build result by processing matches from right to left (to preserve positions)
4184
+ var result = selectorText;
4185
+
4186
+ for (var j = pseudoClassMatches.length - 1; j >= 0; j--) {
4187
+ var pseudoClass = pseudoClassMatches[j][1];
4188
+ if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
4189
+ var fullMatch = pseudoClassMatches[j][0];
4190
+ var pseudoArgs = pseudoClassMatches[j][2];
4191
+ var matchStart = pseudoClassMatches[j][3];
4192
+
4193
+ // Check if ANY selector contains & BEFORE processing
4194
+ var nestedSelectorsRaw = parseAndSplitNestedSelectors(pseudoArgs);
4195
+ var hasAmpersand = false;
4196
+ for (var k = 0; k < nestedSelectorsRaw.length; k++) {
4197
+ if (/&/.test(nestedSelectorsRaw[k])) {
4198
+ hasAmpersand = true;
4199
+ break;
4200
+ }
4201
+ }
4202
+
4203
+ // If & is present, skip all processing (keep everything unchanged)
4204
+ if (hasAmpersand) {
4205
+ continue;
4206
+ }
4207
+
4208
+ // Recursively process the arguments
4209
+ var processedArgs = processNestedPseudoClasses(pseudoArgs, depth + 1);
4210
+ var nestedSelectors = parseAndSplitNestedSelectors(processedArgs);
4211
+
4212
+ // Filter out invalid selectors
4213
+ var validSelectors = [];
4214
+ for (var i = 0; i < nestedSelectors.length; i++) {
4215
+ var nestedSelector = nestedSelectors[i];
4216
+ if (basicSelectorValidator(nestedSelector)) {
4217
+ validSelectors.push(nestedSelector);
4218
+ }
4219
+ }
4220
+
4221
+ // Reconstruct the pseudo-class with only valid selectors
4222
+ var newArgs = validSelectors.join(', ');
4223
+ var newPseudoClass = ':' + pseudoClass + '(' + newArgs + ')';
4224
+
4225
+ // Replace in the result string using position (processing right to left preserves positions)
4226
+ result = result.substring(0, matchStart) + newPseudoClass + result.substring(matchStart + fullMatch.length);
4227
+ }
4228
+ }
4229
+
4230
+ return result;
4231
+
4232
+ return normalized;
4187
4233
  }
4188
4234
 
4189
4235
  /**
@@ -4197,6 +4243,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4197
4243
  // TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
4198
4244
  // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
4199
4245
 
4246
+ // Check for empty selector lists in pseudo-classes (e.g., :is(), :not(), :where(), :has())
4247
+ // These are invalid after filtering out invalid selectors
4248
+ if (/:(?:is|not|where|has)\(\s*\)/.test(selectorText)) {
4249
+ return false;
4250
+ }
4251
+
4200
4252
  // Check for newlines inside single or double quotes using regex
4201
4253
  // This matches any quoted string (single or double) containing a newline
4202
4254
  var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
@@ -4236,12 +4288,22 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4236
4288
  // Print the error but continue parsing the sheet
4237
4289
  try {
4238
4290
  throw error;
4239
- } catch(e) {
4291
+ } catch (e) {
4240
4292
  errorHandler && errorHandler(e);
4241
4293
  }
4242
4294
  };
4243
4295
 
4296
+ // Helper functions to check character types
4297
+ function isSelectorStartChar(char) {
4298
+ return '.:#&*['.indexOf(char) !== -1;
4299
+ }
4300
+
4301
+ function isWhitespaceChar(char) {
4302
+ return ' \t\n\r'.indexOf(char) !== -1;
4303
+ }
4304
+
4244
4305
  var endingIndex = token.length - 1;
4306
+ var initialEndingIndex = endingIndex;
4245
4307
 
4246
4308
  for (var character; (character = token.charAt(i)); i++) {
4247
4309
  if (i === endingIndex) {
@@ -4251,8 +4313,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4251
4313
  case "layerBlock":
4252
4314
  if (character !== ";") {
4253
4315
  token += ";";
4254
- break;
4316
+ endingIndex += 1;
4255
4317
  }
4318
+ break;
4256
4319
  case "value":
4257
4320
  if (character !== "}") {
4258
4321
  if (character === ";") {
@@ -4280,7 +4343,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4280
4343
  }
4281
4344
  }
4282
4345
  }
4283
-
4346
+
4284
4347
  // Handle escape sequences before processing special characters
4285
4348
  // If we encounter a backslash, add both the backslash and the next character to buffer
4286
4349
  // and skip the next iteration to prevent the escaped character from being interpreted
@@ -4289,813 +4352,991 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4289
4352
  i++; // Skip the next character
4290
4353
  continue;
4291
4354
  }
4292
-
4293
- switch (character) {
4294
4355
 
4295
- case " ":
4296
- case "\t":
4297
- case "\r":
4298
- case "\n":
4299
- case "\f":
4300
- if (SIGNIFICANT_WHITESPACE[state]) {
4301
- buffer += character;
4302
- }
4303
- break;
4356
+ switch (character) {
4304
4357
 
4305
- // String
4306
- case '"':
4307
- index = i + 1;
4308
- do {
4309
- index = token.indexOf('"', index) + 1;
4310
- if (!index) {
4311
- parseError('Unmatched "');
4358
+ case " ":
4359
+ case "\t":
4360
+ case "\r":
4361
+ case "\n":
4362
+ case "\f":
4363
+ if (SIGNIFICANT_WHITESPACE[state]) {
4364
+ buffer += character;
4312
4365
  }
4313
- } while (token[index - 2] === '\\');
4314
- if (index === 0) {
4315
4366
  break;
4316
- }
4317
- buffer += token.slice(i, index);
4318
- i = index - 1;
4319
- switch (state) {
4320
- case 'before-value':
4321
- state = 'value';
4322
- break;
4323
- case 'importRule-begin':
4324
- state = 'importRule';
4325
- if (i === endingIndex) {
4326
- token += ';'
4367
+
4368
+ // String
4369
+ case '"':
4370
+ index = i + 1;
4371
+ do {
4372
+ index = token.indexOf('"', index) + 1;
4373
+ if (!index) {
4374
+ parseError('Unmatched "');
4327
4375
  }
4376
+ } while (token[index - 2] === '\\');
4377
+ if (index === 0) {
4328
4378
  break;
4329
- case 'namespaceRule-begin':
4330
- state = 'namespaceRule';
4331
- if (i === endingIndex) {
4332
- token += ';'
4379
+ }
4380
+ buffer += token.slice(i, index);
4381
+ i = index - 1;
4382
+ switch (state) {
4383
+ case 'before-value':
4384
+ state = 'value';
4385
+ break;
4386
+ case 'importRule-begin':
4387
+ state = 'importRule';
4388
+ if (i === endingIndex) {
4389
+ token += ';'
4390
+ }
4391
+ break;
4392
+ case 'namespaceRule-begin':
4393
+ state = 'namespaceRule';
4394
+ if (i === endingIndex) {
4395
+ token += ';'
4396
+ }
4397
+ break;
4398
+ }
4399
+ break;
4400
+
4401
+ case "'":
4402
+ index = i + 1;
4403
+ do {
4404
+ index = token.indexOf("'", index) + 1;
4405
+ if (!index) {
4406
+ parseError("Unmatched '");
4333
4407
  }
4408
+ } while (token[index - 2] === '\\');
4409
+ if (index === 0) {
4334
4410
  break;
4335
- }
4336
- break;
4337
-
4338
- case "'":
4339
- index = i + 1;
4340
- do {
4341
- index = token.indexOf("'", index) + 1;
4342
- if (!index) {
4343
- parseError("Unmatched '");
4344
4411
  }
4345
- } while (token[index - 2] === '\\');
4346
- if (index === 0) {
4412
+ buffer += token.slice(i, index);
4413
+ i = index - 1;
4414
+ switch (state) {
4415
+ case 'before-value':
4416
+ state = 'value';
4417
+ break;
4418
+ case 'importRule-begin':
4419
+ state = 'importRule';
4420
+ break;
4421
+ case 'namespaceRule-begin':
4422
+ state = 'namespaceRule';
4423
+ break;
4424
+ }
4347
4425
  break;
4348
- }
4349
- buffer += token.slice(i, index);
4350
- i = index - 1;
4351
- switch (state) {
4352
- case 'before-value':
4353
- state = 'value';
4354
- break;
4355
- case 'importRule-begin':
4356
- state = 'importRule';
4357
- break;
4358
- case 'namespaceRule-begin':
4359
- state = 'namespaceRule';
4360
- break;
4361
- }
4362
- break;
4363
4426
 
4364
- // Comment
4365
- case "/":
4366
- if (token.charAt(i + 1) === "*") {
4367
- i += 2;
4368
- index = token.indexOf("*/", i);
4369
- if (index === -1) {
4370
- i = token.length - 1;
4371
- buffer = "";
4427
+ // Comment
4428
+ case "/":
4429
+ if (token.charAt(i + 1) === "*") {
4430
+ i += 2;
4431
+ index = token.indexOf("*/", i);
4432
+ if (index === -1) {
4433
+ i = token.length - 1;
4434
+ buffer = "";
4435
+ } else {
4436
+ i = index + 1;
4437
+ }
4372
4438
  } else {
4373
- i = index + 1;
4439
+ buffer += character;
4374
4440
  }
4375
- } else {
4376
- buffer += character;
4377
- }
4378
- if (state === "importRule-begin") {
4379
- buffer += " ";
4380
- state = "importRule";
4381
- }
4382
- if (state === "namespaceRule-begin") {
4383
- buffer += " ";
4384
- state = "namespaceRule";
4385
- }
4386
- break;
4387
-
4388
- // At-rule
4389
- case "@":
4390
- if (nestedSelectorRule) {
4391
- if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
4392
- currentScope.cssRules.push(styleRule);
4441
+ if (state === "importRule-begin") {
4442
+ buffer += " ";
4443
+ state = "importRule";
4393
4444
  }
4394
- if (nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
4395
- styleRule = nestedSelectorRule.parentRule;
4445
+ if (state === "namespaceRule-begin") {
4446
+ buffer += " ";
4447
+ state = "namespaceRule";
4396
4448
  }
4397
- nestedSelectorRule = null;
4398
- }
4399
- if (token.indexOf("@-moz-document", i) === i) {
4400
- validateAtRule("@-moz-document", function(){
4401
- state = "documentRule-begin";
4402
- documentRule = new CSSOM.CSSDocumentRule();
4403
- documentRule.__starts = i;
4404
- i += "-moz-document".length;
4405
- });
4406
- buffer = "";
4407
- break;
4408
- } else if (token.indexOf("@media", i) === i) {
4409
- validateAtRule("@media", function(){
4410
- state = "atBlock";
4411
- mediaRule = new CSSOM.CSSMediaRule();
4412
- mediaRule.__starts = i;
4413
- i += "media".length;
4414
- });
4415
- buffer = "";
4416
- break;
4417
- } else if (token.indexOf("@container", i) === i) {
4418
- validateAtRule("@container", function(){
4419
- state = "containerBlock";
4420
- containerRule = new CSSOM.CSSContainerRule();
4421
- containerRule.__starts = i;
4422
- i += "container".length;
4423
- });
4424
- buffer = "";
4425
- break;
4426
- } else if (token.indexOf("@counter-style", i) === i) {
4427
- validateAtRule("@counter-style", function(){
4428
- state = "counterStyleBlock"
4429
- counterStyleRule = new CSSOM.CSSCounterStyleRule();
4430
- counterStyleRule.__starts = i;
4431
- i += "counter-style".length;
4432
- }, true);
4433
- buffer = "";
4434
- break;
4435
- } else if (token.indexOf("@scope", i) === i) {
4436
- validateAtRule("@scope", function(){
4437
- state = "scopeBlock";
4438
- scopeRule = new CSSOM.CSSScopeRule();
4439
- scopeRule.__starts = i;
4440
- i += "scope".length;
4441
- });
4442
- buffer = "";
4443
- break;
4444
- } else if (token.indexOf("@layer", i) === i) {
4445
- validateAtRule("@layer", function(){
4446
- state = "layerBlock"
4447
- layerBlockRule = new CSSOM.CSSLayerBlockRule();
4448
- layerBlockRule.__starts = i;
4449
- i += "layer".length;
4450
- });
4451
- buffer = "";
4452
- break;
4453
- } else if (token.indexOf("@page", i) === i) {
4454
- validateAtRule("@page", function(){
4455
- state = "pageBlock"
4456
- pageRule = new CSSOM.CSSPageRule();
4457
- pageRule.__starts = i;
4458
- i += "page".length;
4459
- });
4460
- buffer = "";
4461
- break;
4462
- } else if (token.indexOf("@supports", i) === i) {
4463
- validateAtRule("@supports", function(){
4464
- state = "conditionBlock";
4465
- supportsRule = new CSSOM.CSSSupportsRule();
4466
- supportsRule.__starts = i;
4467
- i += "supports".length;
4468
- });
4469
- buffer = "";
4470
- break;
4471
- } else if (token.indexOf("@host", i) === i) {
4472
- validateAtRule("@host", function(){
4473
- state = "hostRule-begin";
4474
- i += "host".length;
4475
- hostRule = new CSSOM.CSSHostRule();
4476
- hostRule.__starts = i;
4477
- });
4478
- buffer = "";
4479
- break;
4480
- } else if (token.indexOf("@starting-style", i) === i) {
4481
- validateAtRule("@starting-style", function(){
4482
- state = "startingStyleRule-begin";
4483
- i += "starting-style".length;
4484
- startingStyleRule = new CSSOM.CSSStartingStyleRule();
4485
- startingStyleRule.__starts = i;
4486
- });
4487
- buffer = "";
4488
4449
  break;
4489
- } else if (token.indexOf("@import", i) === i) {
4490
- buffer = "";
4491
- validateAtRule("@import", function(){
4492
- state = "importRule-begin";
4493
- i += "import".length;
4494
- buffer += "@import";
4495
- }, true);
4496
- break;
4497
- } else if (token.indexOf("@namespace", i) === i) {
4498
- buffer = "";
4499
- validateAtRule("@namespace", function(){
4500
- state = "namespaceRule-begin";
4501
- i += "namespace".length;
4502
- buffer += "@namespace";
4503
- }, true);
4504
- break;
4505
- } else if (token.indexOf("@font-face", i) === i) {
4506
- buffer = "";
4507
- validateAtRule("@font-face", function(){
4508
- state = "fontFaceRule-begin";
4509
- i += "font-face".length;
4510
- fontFaceRule = new CSSOM.CSSFontFaceRule();
4511
- fontFaceRule.__starts = i;
4512
- }, true);
4513
- break;
4514
- } else {
4515
- atKeyframesRegExp.lastIndex = i;
4516
- var matchKeyframes = atKeyframesRegExp.exec(token);
4517
- if (matchKeyframes && matchKeyframes.index === i) {
4518
- state = "keyframesRule-begin";
4519
- keyframesRule = new CSSOM.CSSKeyframesRule();
4520
- keyframesRule.__starts = i;
4521
- keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
4522
- i += matchKeyframes[0].length - 1;
4450
+
4451
+ // At-rule
4452
+ case "@":
4453
+ if (nestedSelectorRule) {
4454
+ if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
4455
+ currentScope.cssRules.push(styleRule);
4456
+ }
4457
+ if (nestedSelectorRule.parentRule && nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
4458
+ styleRule = nestedSelectorRule.parentRule;
4459
+ }
4460
+ // Don't reset nestedSelectorRule here - preserve it through @-rules
4461
+ }
4462
+ if (token.indexOf("@-moz-document", i) === i) {
4463
+ validateAtRule("@-moz-document", function () {
4464
+ state = "documentRule-begin";
4465
+ documentRule = new CSSOM.CSSDocumentRule();
4466
+ documentRule.__starts = i;
4467
+ i += "-moz-document".length;
4468
+ });
4523
4469
  buffer = "";
4524
4470
  break;
4525
- } else if (state === "selector") {
4526
- state = "atRule";
4527
- }
4528
- }
4529
- buffer += character;
4530
- break;
4471
+ } else if (token.indexOf("@media", i) === i) {
4472
+ validateAtRule("@media", function () {
4473
+ state = "atBlock";
4474
+ mediaRule = new CSSOM.CSSMediaRule();
4475
+ mediaRule.__starts = i;
4476
+ i += "media".length;
4477
+ });
4478
+ buffer = "";
4479
+ break;
4480
+ } else if (token.indexOf("@container", i) === i) {
4481
+ validateAtRule("@container", function () {
4482
+ state = "containerBlock";
4483
+ containerRule = new CSSOM.CSSContainerRule();
4484
+ containerRule.__starts = i;
4485
+ i += "container".length;
4486
+ });
4487
+ buffer = "";
4488
+ break;
4489
+ } else if (token.indexOf("@counter-style", i) === i) {
4490
+ validateAtRule("@counter-style", function () {
4491
+ state = "counterStyleBlock"
4492
+ counterStyleRule = new CSSOM.CSSCounterStyleRule();
4493
+ counterStyleRule.__starts = i;
4494
+ i += "counter-style".length;
4495
+ }, true);
4496
+ buffer = "";
4497
+ break;
4498
+ } else if (token.indexOf("@scope", i) === i) {
4499
+ validateAtRule("@scope", function () {
4500
+ state = "scopeBlock";
4501
+ scopeRule = new CSSOM.CSSScopeRule();
4502
+ scopeRule.__starts = i;
4503
+ i += "scope".length;
4504
+ });
4505
+ buffer = "";
4506
+ break;
4507
+ } else if (token.indexOf("@layer", i) === i) {
4508
+ validateAtRule("@layer", function () {
4509
+ state = "layerBlock"
4510
+ layerBlockRule = new CSSOM.CSSLayerBlockRule();
4511
+ layerBlockRule.__starts = i;
4512
+ i += "layer".length;
4513
+ });
4514
+ buffer = "";
4515
+ break;
4516
+ } else if (token.indexOf("@page", i) === i) {
4517
+ validateAtRule("@page", function () {
4518
+ state = "pageBlock"
4519
+ pageRule = new CSSOM.CSSPageRule();
4520
+ pageRule.__starts = i;
4521
+ i += "page".length;
4522
+ });
4523
+ buffer = "";
4524
+ break;
4525
+ } else if (token.indexOf("@supports", i) === i) {
4526
+ validateAtRule("@supports", function () {
4527
+ state = "conditionBlock";
4528
+ supportsRule = new CSSOM.CSSSupportsRule();
4529
+ supportsRule.__starts = i;
4530
+ i += "supports".length;
4531
+ });
4532
+ buffer = "";
4533
+ break;
4534
+ } else if (token.indexOf("@host", i) === i) {
4535
+ validateAtRule("@host", function () {
4536
+ state = "hostRule-begin";
4537
+ i += "host".length;
4538
+ hostRule = new CSSOM.CSSHostRule();
4539
+ hostRule.__starts = i;
4540
+ });
4541
+ buffer = "";
4542
+ break;
4543
+ } else if (token.indexOf("@starting-style", i) === i) {
4544
+ validateAtRule("@starting-style", function () {
4545
+ state = "startingStyleRule-begin";
4546
+ i += "starting-style".length;
4547
+ startingStyleRule = new CSSOM.CSSStartingStyleRule();
4548
+ startingStyleRule.__starts = i;
4549
+ });
4550
+ buffer = "";
4551
+ break;
4552
+ } else if (token.indexOf("@import", i) === i) {
4553
+ buffer = "";
4554
+ validateAtRule("@import", function () {
4555
+ state = "importRule-begin";
4556
+ i += "import".length;
4557
+ buffer += "@import";
4558
+ }, true);
4559
+ break;
4560
+ } else if (token.indexOf("@namespace", i) === i) {
4561
+ buffer = "";
4562
+ validateAtRule("@namespace", function () {
4563
+ state = "namespaceRule-begin";
4564
+ i += "namespace".length;
4565
+ buffer += "@namespace";
4566
+ }, true);
4567
+ break;
4568
+ } else if (token.indexOf("@font-face", i) === i) {
4569
+ buffer = "";
4570
+ // @font-face can be nested only inside CSSScopeRule or CSSConditionRule
4571
+ // and only if there's no CSSStyleRule in the parent chain
4572
+ var cannotBeNested = true;
4573
+ if (currentScope !== topScope) {
4574
+ var hasStyleRuleInChain = false;
4575
+ var hasValidParent = false;
4576
+
4577
+ // Check currentScope
4578
+ if (currentScope.constructor.name === 'CSSStyleRule') {
4579
+ hasStyleRuleInChain = true;
4580
+ } else if (currentScope instanceof CSSOM.CSSScopeRule || currentScope instanceof CSSOM.CSSConditionRule) {
4581
+ hasValidParent = true;
4582
+ }
4531
4583
 
4532
- case "{":
4533
- if (currentScope === topScope) {
4534
- nestedSelectorRule = null;
4535
- }
4536
- if (state === 'before-selector') {
4537
- parseError("Unexpected {");
4538
- i = ignoreBalancedBlock(i, token.slice(i));
4539
- break;
4540
- }
4541
- if (state === "selector" || state === "atRule") {
4542
- if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
4543
- var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4544
- if (ruleClosingMatch) {
4545
- styleRule = null;
4584
+ // Check ancestorRules for CSSStyleRule
4585
+ if (!hasStyleRuleInChain) {
4586
+ for (var j = 0; j < ancestorRules.length; j++) {
4587
+ if (ancestorRules[j].constructor.name === 'CSSStyleRule') {
4588
+ hasStyleRuleInChain = true;
4589
+ break;
4590
+ }
4591
+ if (ancestorRules[j] instanceof CSSOM.CSSScopeRule || ancestorRules[j] instanceof CSSOM.CSSConditionRule) {
4592
+ hasValidParent = true;
4593
+ }
4594
+ }
4595
+ }
4596
+
4597
+ // Allow nesting if we have a valid parent and no style rule in the chain
4598
+ if (hasValidParent && !hasStyleRuleInChain) {
4599
+ cannotBeNested = false;
4600
+ }
4601
+ }
4602
+ validateAtRule("@font-face", function () {
4603
+ state = "fontFaceRule-begin";
4604
+ i += "font-face".length;
4605
+ fontFaceRule = new CSSOM.CSSFontFaceRule();
4606
+ fontFaceRule.__starts = i;
4607
+ }, cannotBeNested);
4608
+ break;
4609
+ } else {
4610
+ atKeyframesRegExp.lastIndex = i;
4611
+ var matchKeyframes = atKeyframesRegExp.exec(token);
4612
+ if (matchKeyframes && matchKeyframes.index === i) {
4613
+ state = "keyframesRule-begin";
4614
+ keyframesRule = new CSSOM.CSSKeyframesRule();
4615
+ keyframesRule.__starts = i;
4616
+ keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
4617
+ i += matchKeyframes[0].length - 1;
4546
4618
  buffer = "";
4547
- state = "before-selector";
4548
- i += ruleClosingMatch.index + ruleClosingMatch[0].length;
4549
4619
  break;
4620
+ } else if (state === "selector") {
4621
+ state = "atRule";
4550
4622
  }
4551
4623
  }
4624
+ buffer += character;
4625
+ break;
4552
4626
 
4553
- if (parentRule) {
4554
- styleRule.__parentRule = parentRule;
4555
- pushToAncestorRules(parentRule);
4556
- }
4557
-
4558
- currentScope = parentRule = styleRule;
4559
- styleRule.selectorText = processSelectorText(buffer.trim());
4560
- styleRule.style.__starts = i;
4561
- styleRule.__parentStyleSheet = styleSheet;
4562
- buffer = "";
4563
- state = "before-name";
4564
- } else if (state === "atBlock") {
4565
- mediaRule.media.mediaText = buffer.trim();
4566
-
4567
- if (parentRule) {
4568
- mediaRule.__parentRule = parentRule;
4569
- pushToAncestorRules(parentRule);
4570
- }
4571
-
4572
- currentScope = parentRule = mediaRule;
4573
- mediaRule.__parentStyleSheet = styleSheet;
4574
- buffer = "";
4575
- state = "before-selector";
4576
- } else if (state === "containerBlock") {
4577
- containerRule.__conditionText = buffer.trim();
4578
-
4579
- if (parentRule) {
4580
- containerRule.__parentRule = parentRule;
4581
- pushToAncestorRules(parentRule);
4582
- }
4583
- currentScope = parentRule = containerRule;
4584
- containerRule.__parentStyleSheet = styleSheet;
4585
- buffer = "";
4586
- state = "before-selector";
4587
- } else if (state === "counterStyleBlock") {
4588
- // TODO: Validate counter-style name. At least that it cannot be empty nor multiple
4589
- counterStyleRule.name = buffer.trim().replace(/\n/g, "");
4590
- currentScope = parentRule = counterStyleRule;
4591
- counterStyleRule.__parentStyleSheet = styleSheet;
4592
- buffer = "";
4593
- } else if (state === "conditionBlock") {
4594
- supportsRule.__conditionText = buffer.trim();
4595
-
4596
- if (parentRule) {
4597
- supportsRule.__parentRule = parentRule;
4598
- pushToAncestorRules(parentRule);
4599
- }
4600
-
4601
- currentScope = parentRule = supportsRule;
4602
- supportsRule.__parentStyleSheet = styleSheet;
4603
- buffer = "";
4604
- state = "before-selector";
4605
- } else if (state === "scopeBlock") {
4606
- var parsedScopePrelude = parseScopePrelude(buffer.trim());
4607
-
4608
- if (parsedScopePrelude.hasStart) {
4609
- scopeRule.__start = parsedScopePrelude.startSelector;
4627
+ case "{":
4628
+ if (currentScope === topScope) {
4629
+ nestedSelectorRule = null;
4610
4630
  }
4611
- if (parsedScopePrelude.hasEnd) {
4612
- scopeRule.__end = parsedScopePrelude.endSelector;
4613
- }
4614
- if (parsedScopePrelude.hasOnlyEnd) {
4615
- scopeRule.__end = parsedScopePrelude.endSelector;
4631
+ if (state === 'before-selector') {
4632
+ parseError("Unexpected {");
4633
+ i = ignoreBalancedBlock(i, token.slice(i));
4634
+ break;
4616
4635
  }
4636
+ if (state === "selector" || state === "atRule") {
4637
+ if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
4638
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4639
+ if (ruleClosingMatch) {
4640
+ styleRule = null;
4641
+ buffer = "";
4642
+ state = "before-selector";
4643
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length;
4644
+ break;
4645
+ }
4646
+ }
4617
4647
 
4618
- if (parentRule) {
4619
- scopeRule.__parentRule = parentRule;
4620
- pushToAncestorRules(parentRule);
4621
- }
4622
- currentScope = parentRule = scopeRule;
4623
- scopeRule.__parentStyleSheet = styleSheet;
4624
- buffer = "";
4625
- state = "before-selector";
4626
- } else if (state === "layerBlock") {
4627
- layerBlockRule.name = buffer.trim();
4648
+ // Ensure styleRule exists before trying to set properties on it
4649
+ if (!styleRule) {
4650
+ styleRule = new CSSOM.CSSStyleRule();
4651
+ styleRule.__starts = i;
4652
+ }
4628
4653
 
4629
- var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4654
+ var originalParentRule = parentRule;
4630
4655
 
4631
- if (isValidName) {
4632
4656
  if (parentRule) {
4633
- layerBlockRule.__parentRule = parentRule;
4657
+ styleRule.__parentRule = parentRule;
4634
4658
  pushToAncestorRules(parentRule);
4635
4659
  }
4636
-
4637
- currentScope = parentRule = layerBlockRule;
4638
- layerBlockRule.__parentStyleSheet = styleSheet;
4639
- }
4640
- buffer = "";
4641
- state = "before-selector";
4642
- } else if (state === "pageBlock") {
4643
- pageRule.selectorText = buffer.trim();
4644
-
4645
- if (parentRule) {
4646
- pageRule.__parentRule = parentRule;
4647
- pushToAncestorRules(parentRule);
4648
- }
4649
4660
 
4650
- currentScope = parentRule = pageRule;
4651
- pageRule.__parentStyleSheet = styleSheet;
4652
- styleRule = pageRule;
4653
- buffer = "";
4654
- state = "before-name";
4655
- } else if (state === "hostRule-begin") {
4656
- if (parentRule) {
4657
- pushToAncestorRules(parentRule);
4658
- }
4661
+ currentScope = parentRule = styleRule;
4662
+ var processedSelectorText = processSelectorText(buffer.trim());
4663
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4664
+ if (originalParentRule && originalParentRule.constructor.name === "CSSStyleRule") {
4665
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4666
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4667
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4668
+ }).join(', ');
4669
+ } else {
4670
+ styleRule.selectorText = processedSelectorText;
4671
+ }
4672
+ styleRule.style.__starts = i;
4673
+ styleRule.__parentStyleSheet = styleSheet;
4674
+ buffer = "";
4675
+ state = "before-name";
4676
+ } else if (state === "atBlock") {
4677
+ mediaRule.media.mediaText = buffer.trim();
4659
4678
 
4660
- currentScope = parentRule = hostRule;
4661
- hostRule.__parentStyleSheet = styleSheet;
4662
- buffer = "";
4663
- state = "before-selector";
4664
- } else if (state === "startingStyleRule-begin") {
4665
- if (parentRule) {
4666
- startingStyleRule.__parentRule = parentRule;
4667
- pushToAncestorRules(parentRule);
4668
- }
4679
+ if (parentRule) {
4680
+ mediaRule.__parentRule = parentRule;
4681
+ pushToAncestorRules(parentRule);
4682
+ // If entering @media from within a CSSStyleRule, set nestedSelectorRule
4683
+ // so that & selectors and declarations work correctly inside
4684
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4685
+ nestedSelectorRule = parentRule;
4686
+ }
4687
+ }
4669
4688
 
4670
- currentScope = parentRule = startingStyleRule;
4671
- startingStyleRule.__parentStyleSheet = styleSheet;
4672
- buffer = "";
4673
- state = "before-selector";
4689
+ currentScope = parentRule = mediaRule;
4690
+ pushToAncestorRules(mediaRule);
4691
+ mediaRule.__parentStyleSheet = styleSheet;
4692
+ styleRule = null; // Reset styleRule when entering @-rule
4693
+ buffer = "";
4694
+ state = "before-selector";
4695
+ } else if (state === "containerBlock") {
4696
+ containerRule.__conditionText = buffer.trim();
4674
4697
 
4675
- } else if (state === "fontFaceRule-begin") {
4676
- if (parentRule) {
4677
- fontFaceRule.__parentRule = parentRule;
4678
- }
4679
- fontFaceRule.__parentStyleSheet = styleSheet;
4680
- styleRule = fontFaceRule;
4681
- buffer = "";
4682
- state = "before-name";
4683
- } else if (state === "keyframesRule-begin") {
4684
- keyframesRule.name = buffer.trim();
4685
- if (parentRule) {
4686
- pushToAncestorRules(parentRule);
4687
- keyframesRule.__parentRule = parentRule;
4688
- }
4689
- keyframesRule.__parentStyleSheet = styleSheet;
4690
- currentScope = parentRule = keyframesRule;
4691
- buffer = "";
4692
- state = "keyframeRule-begin";
4693
- } else if (state === "keyframeRule-begin") {
4694
- styleRule = new CSSOM.CSSKeyframeRule();
4695
- styleRule.keyText = buffer.trim();
4696
- styleRule.__starts = i;
4697
- buffer = "";
4698
- state = "before-name";
4699
- } else if (state === "documentRule-begin") {
4700
- // FIXME: what if this '{' is in the url text of the match function?
4701
- documentRule.matcher.matcherText = buffer.trim();
4702
- if (parentRule) {
4703
- pushToAncestorRules(parentRule);
4704
- documentRule.__parentRule = parentRule;
4705
- }
4706
- currentScope = parentRule = documentRule;
4707
- documentRule.__parentStyleSheet = styleSheet;
4708
- buffer = "";
4709
- state = "before-selector";
4710
- } else if (state === "before-name" || state === "name") {
4711
- if (styleRule.constructor.name === "CSSNestedDeclarations") {
4712
- if (styleRule.style.length) {
4713
- parentRule.cssRules.push(styleRule);
4714
- styleRule.__parentRule = parentRule;
4715
- styleRule.__parentStyleSheet = styleSheet;
4716
- pushToAncestorRules(parentRule);
4717
- } else {
4718
- // If the styleRule is empty, we can assume that it's a nested selector
4698
+ if (parentRule) {
4699
+ containerRule.__parentRule = parentRule;
4719
4700
  pushToAncestorRules(parentRule);
4701
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4702
+ nestedSelectorRule = parentRule;
4703
+ }
4720
4704
  }
4721
- } else {
4722
- currentScope = parentRule = styleRule;
4723
- pushToAncestorRules(parentRule);
4724
- styleRule.__parentStyleSheet = styleSheet;
4725
- }
4726
-
4727
- styleRule = new CSSOM.CSSStyleRule();
4728
- var processedSelectorText = processSelectorText(buffer.trim());
4729
- // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4730
- if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
4731
- styleRule.selectorText = processedSelectorText;
4732
- } else {
4733
- styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
4734
- // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4735
- return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4736
- }).join(', ');
4737
- }
4738
- styleRule.style.__starts = i - buffer.length;
4739
- styleRule.__parentRule = parentRule;
4740
- nestedSelectorRule = styleRule;
4705
+ currentScope = parentRule = containerRule;
4706
+ pushToAncestorRules(containerRule);
4707
+ containerRule.__parentStyleSheet = styleSheet;
4708
+ styleRule = null; // Reset styleRule when entering @-rule
4709
+ buffer = "";
4710
+ state = "before-selector";
4711
+ } else if (state === "counterStyleBlock") {
4712
+ var counterStyleName = buffer.trim().replace(/\n/g, "");
4713
+ // Validate: name cannot be empty, contain whitespace, or contain dots
4714
+ var isValidCounterStyleName = counterStyleName.length > 0 && !/[\s.]/.test(counterStyleName);
4715
+
4716
+ if (isValidCounterStyleName) {
4717
+ counterStyleRule.name = counterStyleName;
4718
+ currentScope = parentRule = counterStyleRule;
4719
+ counterStyleRule.__parentStyleSheet = styleSheet;
4720
+ }
4721
+ buffer = "";
4722
+ } else if (state === "conditionBlock") {
4723
+ supportsRule.__conditionText = buffer.trim();
4741
4724
 
4742
- buffer = "";
4743
- state = "before-name";
4744
- }
4745
- break;
4725
+ if (parentRule) {
4726
+ supportsRule.__parentRule = parentRule;
4727
+ pushToAncestorRules(parentRule);
4728
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4729
+ nestedSelectorRule = parentRule;
4730
+ }
4731
+ }
4746
4732
 
4747
- case ":":
4748
- if (state === "name") {
4749
- // It can be a nested selector, let's check
4750
- var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
4751
- var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
4752
- if (hasOpenBraceBefore) {
4753
- // Is a selector
4754
- buffer += character;
4755
- } else {
4756
- // Is a declaration
4757
- name = buffer.trim();
4733
+ currentScope = parentRule = supportsRule;
4734
+ pushToAncestorRules(supportsRule);
4735
+ supportsRule.__parentStyleSheet = styleSheet;
4736
+ styleRule = null; // Reset styleRule when entering @-rule
4758
4737
  buffer = "";
4759
- state = "before-value";
4760
- }
4761
- } else {
4762
- buffer += character;
4763
- }
4764
- break;
4738
+ state = "before-selector";
4739
+ } else if (state === "scopeBlock") {
4740
+ var parsedScopePrelude = parseScopePrelude(buffer.trim());
4765
4741
 
4766
- case "(":
4767
- if (state === 'value') {
4768
- // ie css expression mode
4769
- if (buffer.trim() === 'expression') {
4770
- var info = (new CSSOM.CSSValueExpression(token, i)).parse();
4742
+ if (parsedScopePrelude.hasStart) {
4743
+ scopeRule.__start = parsedScopePrelude.startSelector;
4744
+ }
4745
+ if (parsedScopePrelude.hasEnd) {
4746
+ scopeRule.__end = parsedScopePrelude.endSelector;
4747
+ }
4748
+ if (parsedScopePrelude.hasOnlyEnd) {
4749
+ scopeRule.__end = parsedScopePrelude.endSelector;
4750
+ }
4771
4751
 
4772
- if (info.error) {
4773
- parseError(info.error);
4774
- } else {
4775
- buffer += info.expression;
4776
- i = info.idx;
4752
+ if (parentRule) {
4753
+ scopeRule.__parentRule = parentRule;
4754
+ pushToAncestorRules(parentRule);
4755
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4756
+ nestedSelectorRule = parentRule;
4757
+ }
4777
4758
  }
4778
- } else {
4779
- state = 'value-parenthesis';
4780
- //always ensure this is reset to 1 on transition
4781
- //from value to value-parenthesis
4782
- valueParenthesisDepth = 1;
4783
- buffer += character;
4784
- }
4785
- } else if (state === 'value-parenthesis') {
4786
- valueParenthesisDepth++;
4787
- buffer += character;
4788
- } else {
4789
- buffer += character;
4790
- }
4791
- break;
4759
+ currentScope = parentRule = scopeRule;
4760
+ pushToAncestorRules(scopeRule);
4761
+ scopeRule.__parentStyleSheet = styleSheet;
4762
+ styleRule = null; // Reset styleRule when entering @-rule
4763
+ buffer = "";
4764
+ state = "before-selector";
4765
+ } else if (state === "layerBlock") {
4766
+ layerBlockRule.name = buffer.trim();
4792
4767
 
4793
- case ")":
4794
- if (state === 'value-parenthesis') {
4795
- valueParenthesisDepth--;
4796
- if (valueParenthesisDepth === 0) state = 'value';
4797
- }
4798
- buffer += character;
4799
- break;
4768
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4800
4769
 
4801
- case "!":
4802
- if (state === "value" && token.indexOf("!important", i) === i) {
4803
- priority = "important";
4804
- i += "important".length;
4805
- } else {
4806
- buffer += character;
4807
- }
4808
- break;
4770
+ if (isValidName) {
4771
+ if (parentRule) {
4772
+ layerBlockRule.__parentRule = parentRule;
4773
+ pushToAncestorRules(parentRule);
4774
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4775
+ nestedSelectorRule = parentRule;
4776
+ }
4777
+ }
4809
4778
 
4810
- case ";":
4811
- switch (state) {
4812
- case "before-value":
4813
- case "before-name":
4814
- parseError("Unexpected ;");
4779
+ currentScope = parentRule = layerBlockRule;
4780
+ pushToAncestorRules(layerBlockRule);
4781
+ layerBlockRule.__parentStyleSheet = styleSheet;
4782
+ }
4783
+ styleRule = null; // Reset styleRule when entering @-rule
4815
4784
  buffer = "";
4816
- state = "before-name";
4817
- break;
4818
- case "value":
4819
- styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4820
- priority = "";
4785
+ state = "before-selector";
4786
+ } else if (state === "pageBlock") {
4787
+ pageRule.selectorText = buffer.trim();
4788
+
4789
+ if (parentRule) {
4790
+ pageRule.__parentRule = parentRule;
4791
+ pushToAncestorRules(parentRule);
4792
+ }
4793
+
4794
+ currentScope = parentRule = pageRule;
4795
+ pageRule.__parentStyleSheet = styleSheet;
4796
+ styleRule = pageRule;
4821
4797
  buffer = "";
4822
4798
  state = "before-name";
4823
- break;
4824
- case "atRule":
4825
- buffer = "";
4826
- state = "before-selector";
4827
- break;
4828
- case "importRule":
4829
- var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
4830
- return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
4831
- });
4832
- if (isValid) {
4833
- importRule = new CSSOM.CSSImportRule();
4834
- importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
4835
- importRule.cssText = buffer + character;
4836
- topScope.cssRules.push(importRule);
4799
+ } else if (state === "hostRule-begin") {
4800
+ if (parentRule) {
4801
+ pushToAncestorRules(parentRule);
4837
4802
  }
4803
+
4804
+ currentScope = parentRule = hostRule;
4805
+ pushToAncestorRules(hostRule);
4806
+ hostRule.__parentStyleSheet = styleSheet;
4838
4807
  buffer = "";
4839
4808
  state = "before-selector";
4840
- break;
4841
- case "namespaceRule":
4842
- var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
4843
- return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
4844
- });
4845
- if (isValid) {
4846
- try {
4847
- // Validate namespace syntax before creating the rule
4848
- var testNamespaceRule = new CSSOM.CSSNamespaceRule();
4849
- testNamespaceRule.cssText = buffer + character;
4850
-
4851
- namespaceRule = testNamespaceRule;
4852
- namespaceRule.__parentStyleSheet = styleSheet;
4853
- topScope.cssRules.push(namespaceRule);
4854
-
4855
- // Track the namespace prefix for validation
4856
- if (namespaceRule.prefix) {
4857
- definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
4858
- }
4859
- } catch(e) {
4860
- parseError(e.message);
4809
+ } else if (state === "startingStyleRule-begin") {
4810
+ if (parentRule) {
4811
+ startingStyleRule.__parentRule = parentRule;
4812
+ pushToAncestorRules(parentRule);
4813
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4814
+ nestedSelectorRule = parentRule;
4861
4815
  }
4862
4816
  }
4817
+
4818
+ currentScope = parentRule = startingStyleRule;
4819
+ pushToAncestorRules(startingStyleRule);
4820
+ startingStyleRule.__parentStyleSheet = styleSheet;
4821
+ styleRule = null; // Reset styleRule when entering @-rule
4863
4822
  buffer = "";
4864
4823
  state = "before-selector";
4865
- break;
4866
- case "layerBlock":
4867
- var nameListStr = buffer.trim().split(",").map(function (name) {
4868
- return name.trim();
4869
- });
4870
- var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
4871
- return name.trim().match(cssCustomIdentifierRegExp) === null;
4872
- });
4873
4824
 
4874
- if (!isInvalid) {
4875
- layerStatementRule = new CSSOM.CSSLayerStatementRule();
4876
- layerStatementRule.__parentStyleSheet = styleSheet;
4877
- layerStatementRule.__starts = layerBlockRule.__starts;
4878
- layerStatementRule.__ends = i;
4879
- layerStatementRule.nameList = nameListStr;
4880
- topScope.cssRules.push(layerStatementRule);
4825
+ } else if (state === "fontFaceRule-begin") {
4826
+ if (parentRule) {
4827
+ fontFaceRule.__parentRule = parentRule;
4881
4828
  }
4829
+ fontFaceRule.__parentStyleSheet = styleSheet;
4830
+ styleRule = fontFaceRule;
4882
4831
  buffer = "";
4883
- state = "before-selector";
4884
- break;
4885
- default:
4886
- buffer += character;
4887
- break;
4888
- }
4889
- break;
4890
-
4891
- case "}":
4892
- if (state === "counterStyleBlock") {
4893
- // FIXME : Implement cssText get setter that parses the real implementation
4894
- counterStyleRule.cssText = "@counter-style " + counterStyleRule.name + " { " + buffer.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function(match, quote) {
4895
- return quote ? match : ' ';
4896
- }) + " }";
4897
- buffer = "";
4898
- state = "before-selector";
4899
- }
4900
-
4901
- switch (state) {
4902
- case "value":
4903
- styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4904
- priority = "";
4905
- /* falls through */
4906
- case "before-value":
4907
- case "before-name":
4908
- case "name":
4909
- styleRule.__ends = i + 1;
4910
-
4911
- if (parentRule === styleRule) {
4912
- parentRule = ancestorRules.pop()
4832
+ state = "before-name";
4833
+ } else if (state === "keyframesRule-begin") {
4834
+ keyframesRule.name = buffer.trim();
4835
+ if (parentRule) {
4836
+ pushToAncestorRules(parentRule);
4837
+ keyframesRule.__parentRule = parentRule;
4913
4838
  }
4914
-
4839
+ keyframesRule.__parentStyleSheet = styleSheet;
4840
+ currentScope = parentRule = keyframesRule;
4841
+ buffer = "";
4842
+ state = "keyframeRule-begin";
4843
+ } else if (state === "keyframeRule-begin") {
4844
+ styleRule = new CSSOM.CSSKeyframeRule();
4845
+ styleRule.keyText = buffer.trim();
4846
+ styleRule.__starts = i;
4847
+ buffer = "";
4848
+ state = "before-name";
4849
+ } else if (state === "documentRule-begin") {
4850
+ // FIXME: what if this '{' is in the url text of the match function?
4851
+ documentRule.matcher.matcherText = buffer.trim();
4915
4852
  if (parentRule) {
4916
- styleRule.__parentRule = parentRule;
4853
+ pushToAncestorRules(parentRule);
4854
+ documentRule.__parentRule = parentRule;
4917
4855
  }
4918
- styleRule.__parentStyleSheet = styleSheet;
4919
-
4920
- if (currentScope === styleRule) {
4921
- currentScope = parentRule || topScope;
4856
+ currentScope = parentRule = documentRule;
4857
+ pushToAncestorRules(documentRule);
4858
+ documentRule.__parentStyleSheet = styleSheet;
4859
+ buffer = "";
4860
+ state = "before-selector";
4861
+ } else if (state === "before-name" || state === "name") {
4862
+ // @font-face and similar rules don't support nested selectors
4863
+ // If we encounter a nested selector block inside them, skip it
4864
+ if (styleRule.constructor.name === "CSSFontFaceRule" ||
4865
+ styleRule.constructor.name === "CSSKeyframeRule" ||
4866
+ (styleRule.constructor.name === "CSSPageRule" && parentRule === styleRule)) {
4867
+ // Skip the nested block
4868
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4869
+ if (ruleClosingMatch) {
4870
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
4871
+ buffer = "";
4872
+ state = "before-name";
4873
+ break;
4874
+ }
4922
4875
  }
4923
4876
 
4924
- if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
4925
- if (styleRule === nestedSelectorRule) {
4926
- nestedSelectorRule = null;
4927
- }
4928
- parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
4929
- } else {
4930
- if (styleRule.parentRule) {
4931
- styleRule.parentRule.cssRules.push(styleRule);
4877
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
4878
+ if (styleRule.style.length) {
4879
+ parentRule.cssRules.push(styleRule);
4880
+ styleRule.__parentRule = parentRule;
4881
+ styleRule.__parentStyleSheet = styleSheet;
4882
+ pushToAncestorRules(parentRule);
4932
4883
  } else {
4933
- currentScope.cssRules.push(styleRule);
4884
+ // If the styleRule is empty, we can assume that it's a nested selector
4885
+ pushToAncestorRules(parentRule);
4934
4886
  }
4887
+ } else {
4888
+ currentScope = parentRule = styleRule;
4889
+ pushToAncestorRules(parentRule);
4890
+ styleRule.__parentStyleSheet = styleSheet;
4935
4891
  }
4892
+
4893
+ styleRule = new CSSOM.CSSStyleRule();
4894
+ var processedSelectorText = processSelectorText(buffer.trim());
4895
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4896
+ if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
4897
+ styleRule.selectorText = processedSelectorText;
4898
+ } else {
4899
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4900
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4901
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4902
+ }).join(', ');
4903
+ }
4904
+ styleRule.style.__starts = i - buffer.length;
4905
+ styleRule.__parentRule = parentRule;
4906
+ // Only set nestedSelectorRule if we're directly inside a CSSStyleRule or CSSScopeRule,
4907
+ // not inside other grouping rules like @media/@supports
4908
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
4909
+ nestedSelectorRule = styleRule;
4910
+ }
4911
+
4936
4912
  buffer = "";
4937
- if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
4938
- state = "keyframeRule-begin";
4913
+ state = "before-name";
4914
+ }
4915
+ break;
4916
+
4917
+ case ":":
4918
+ if (state === "name") {
4919
+ // It can be a nested selector, let's check
4920
+ var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
4921
+ var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
4922
+ if (hasOpenBraceBefore) {
4923
+ // Is a selector
4924
+ buffer += character;
4939
4925
  } else {
4940
- state = "before-selector";
4926
+ // Is a declaration
4927
+ name = buffer.trim();
4928
+ buffer = "";
4929
+ state = "before-value";
4941
4930
  }
4931
+ } else {
4932
+ buffer += character;
4933
+ }
4934
+ break;
4942
4935
 
4943
- if (styleRule.constructor.name === "CSSNestedDeclarations") {
4944
- if (currentScope !== topScope) {
4945
- nestedSelectorRule = currentScope;
4936
+ case "(":
4937
+ if (state === 'value') {
4938
+ // ie css expression mode
4939
+ if (buffer.trim() === 'expression') {
4940
+ var info = (new CSSOM.CSSValueExpression(token, i)).parse();
4941
+
4942
+ if (info.error) {
4943
+ parseError(info.error);
4944
+ } else {
4945
+ buffer += info.expression;
4946
+ i = info.idx;
4946
4947
  }
4947
- styleRule = null;
4948
4948
  } else {
4949
- styleRule = null;
4950
- break;
4949
+ state = 'value-parenthesis';
4950
+ //always ensure this is reset to 1 on transition
4951
+ //from value to value-parenthesis
4952
+ valueParenthesisDepth = 1;
4953
+ buffer += character;
4951
4954
  }
4952
- case "keyframeRule-begin":
4953
- case "before-selector":
4954
- case "selector":
4955
- // End of media/supports/document rule.
4956
- if (!parentRule) {
4957
- parseError("Unexpected }");
4958
-
4959
- var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
4960
- if (hasPreviousStyleRule) {
4961
- i = ignoreBalancedBlock(i, token.slice(i), 1);
4955
+ } else if (state === 'value-parenthesis') {
4956
+ valueParenthesisDepth++;
4957
+ buffer += character;
4958
+ } else {
4959
+ buffer += character;
4960
+ }
4961
+ break;
4962
+
4963
+ case ")":
4964
+ if (state === 'value-parenthesis') {
4965
+ valueParenthesisDepth--;
4966
+ if (valueParenthesisDepth === 0) state = 'value';
4967
+ }
4968
+ buffer += character;
4969
+ break;
4970
+
4971
+ case "!":
4972
+ if (state === "value" && token.indexOf("!important", i) === i) {
4973
+ priority = "important";
4974
+ i += "important".length;
4975
+ } else {
4976
+ buffer += character;
4977
+ }
4978
+ break;
4979
+
4980
+ case ";":
4981
+ switch (state) {
4982
+ case "before-value":
4983
+ case "before-name":
4984
+ parseError("Unexpected ;");
4985
+ buffer = "";
4986
+ state = "before-name";
4987
+ break;
4988
+ case "value":
4989
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4990
+ priority = "";
4991
+ buffer = "";
4992
+ state = "before-name";
4993
+ break;
4994
+ case "atRule":
4995
+ buffer = "";
4996
+ state = "before-selector";
4997
+ break;
4998
+ case "importRule":
4999
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
5000
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
5001
+ });
5002
+ if (isValid) {
5003
+ importRule = new CSSOM.CSSImportRule();
5004
+ importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
5005
+ importRule.parse(buffer + character);
5006
+ topScope.cssRules.push(importRule);
4962
5007
  }
4963
-
5008
+ buffer = "";
5009
+ state = "before-selector";
4964
5010
  break;
4965
- }
4966
-
4967
- while (ancestorRules.length > 0) {
4968
- parentRule = ancestorRules.pop();
4969
-
4970
- if (
4971
- parentRule.constructor.name === "CSSStyleRule"
4972
- || parentRule.constructor.name === "CSSMediaRule"
4973
- || parentRule.constructor.name === "CSSSupportsRule"
4974
- || parentRule.constructor.name === "CSSContainerRule"
4975
- || parentRule.constructor.name === "CSSScopeRule"
4976
- || parentRule.constructor.name === "CSSLayerBlockRule"
4977
- || parentRule.constructor.name === "CSSStartingStyleRule"
4978
- ) {
4979
- if (nestedSelectorRule) {
4980
- if (nestedSelectorRule.parentRule) {
4981
- prevScope = nestedSelectorRule;
4982
- currentScope = nestedSelectorRule.parentRule;
4983
- if (currentScope.cssRules.findIndex(function (rule) {
4984
- return rule === prevScope
4985
- }) === -1) {
4986
- currentScope.cssRules.push(prevScope);
4987
- }
4988
- nestedSelectorRule = currentScope;
5011
+ case "namespaceRule":
5012
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
5013
+ return ['CSSImportRule', 'CSSLayerStatementRule', 'CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
5014
+ });
5015
+ if (isValid) {
5016
+ try {
5017
+ // Validate namespace syntax before creating the rule
5018
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
5019
+ testNamespaceRule.parse(buffer + character);
5020
+
5021
+ namespaceRule = testNamespaceRule;
5022
+ namespaceRule.__parentStyleSheet = styleSheet;
5023
+ topScope.cssRules.push(namespaceRule);
5024
+
5025
+ // Track the namespace prefix for validation
5026
+ if (namespaceRule.prefix) {
5027
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
5028
+ }
5029
+ } catch (e) {
5030
+ parseError(e.message);
5031
+ }
5032
+ }
5033
+ buffer = "";
5034
+ state = "before-selector";
5035
+ break;
5036
+ case "layerBlock":
5037
+ var nameListStr = buffer.trim().split(",").map(function (name) {
5038
+ return name.trim();
5039
+ });
5040
+ var isInvalid = nameListStr.some(function (name) {
5041
+ return name.trim().match(cssCustomIdentifierRegExp) === null;
5042
+ });
5043
+
5044
+ // Check if there's a CSSStyleRule in the parent chain
5045
+ var hasStyleRuleParent = false;
5046
+ if (parentRule) {
5047
+ var checkParent = parentRule;
5048
+ while (checkParent) {
5049
+ if (checkParent.constructor.name === "CSSStyleRule") {
5050
+ hasStyleRuleParent = true;
5051
+ break;
4989
5052
  }
5053
+ checkParent = checkParent.__parentRule;
5054
+ }
5055
+ }
5056
+
5057
+ if (!isInvalid && !hasStyleRuleParent) {
5058
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
5059
+ layerStatementRule.__parentStyleSheet = styleSheet;
5060
+ layerStatementRule.__starts = layerBlockRule.__starts;
5061
+ layerStatementRule.__ends = i;
5062
+ layerStatementRule.nameList = nameListStr;
5063
+
5064
+ // Add to parent rule if nested, otherwise to top scope
5065
+ if (parentRule) {
5066
+ layerStatementRule.__parentRule = parentRule;
5067
+ parentRule.cssRules.push(layerStatementRule);
4990
5068
  } else {
4991
- prevScope = currentScope;
4992
- parentRule !== prevScope && parentRule.cssRules.push(prevScope);
4993
- break;
5069
+ topScope.cssRules.push(layerStatementRule);
4994
5070
  }
4995
5071
  }
4996
- }
4997
-
4998
- if (currentScope.parentRule == null) {
4999
- currentScope.__ends = i + 1;
5000
- if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
5001
- return rule === currentScope
5002
- }) === -1) {
5003
- topScope.cssRules.push(currentScope);
5072
+ buffer = "";
5073
+ state = "before-selector";
5074
+ break;
5075
+ default:
5076
+ buffer += character;
5077
+ break;
5078
+ }
5079
+ break;
5080
+
5081
+ case "}":
5082
+ if (state === "counterStyleBlock") {
5083
+ // FIXME : Implement missing properties on CSSCounterStyleRule interface and update parse method
5084
+ // For now it's just assigning entire rule text
5085
+ counterStyleRule.parse("@counter-style " + counterStyleRule.name + " { " + buffer + " }");
5086
+ buffer = "";
5087
+ state = "before-selector";
5088
+ }
5089
+
5090
+ switch (state) {
5091
+ case "value":
5092
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
5093
+ priority = "";
5094
+ /* falls through */
5095
+ case "before-value":
5096
+ case "before-name":
5097
+ case "name":
5098
+ styleRule.__ends = i + 1;
5099
+
5100
+ if (parentRule === styleRule) {
5101
+ parentRule = ancestorRules.pop()
5102
+ }
5103
+
5104
+ if (parentRule) {
5105
+ styleRule.__parentRule = parentRule;
5004
5106
  }
5005
- currentScope = topScope;
5006
- if (nestedSelectorRule === parentRule) {
5007
- // Check if this selector is really starting inside another selector
5008
- var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
5009
- var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
5010
- var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
5011
- var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
5012
- var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
5013
-
5014
- if (openingBraceLen === closingBraceLen) {
5015
- // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
5016
- nestedSelectorRule.__ends = i + 1;
5107
+ styleRule.__parentStyleSheet = styleSheet;
5108
+
5109
+ if (currentScope === styleRule) {
5110
+ currentScope = parentRule || topScope;
5111
+ }
5112
+
5113
+ if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
5114
+ if (styleRule === nestedSelectorRule) {
5017
5115
  nestedSelectorRule = null;
5018
- parentRule = null;
5019
5116
  }
5117
+ parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
5118
+ } else {
5119
+ if (styleRule.parentRule) {
5120
+ styleRule.parentRule.cssRules.push(styleRule);
5121
+ } else {
5122
+ currentScope.cssRules.push(styleRule);
5123
+ }
5124
+ }
5125
+ buffer = "";
5126
+ if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
5127
+ state = "keyframeRule-begin";
5020
5128
  } else {
5021
- parentRule = null;
5129
+ state = "before-selector";
5130
+ }
5022
5131
 
5132
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
5133
+ if (currentScope !== topScope) {
5134
+ // Only set nestedSelectorRule if currentScope is CSSStyleRule or CSSScopeRule
5135
+ // Not for other grouping rules like @media/@supports
5136
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5137
+ nestedSelectorRule = currentScope;
5138
+ }
5139
+ }
5140
+ styleRule = null;
5141
+ } else {
5142
+ // Update nestedSelectorRule when closing a CSSStyleRule
5143
+ if (styleRule === nestedSelectorRule) {
5144
+ var selector = styleRule.selectorText && styleRule.selectorText.trim();
5145
+ // Check if this is proper nesting (&.class, &:pseudo) vs prepended & (& :is, & .class with space)
5146
+ // Prepended & has pattern "& X" where X starts with : or .
5147
+ var isPrependedAmpersand = selector && selector.match(/^&\s+[:\.]/);
5148
+
5149
+ // Check if parent is a grouping rule that can contain nested selectors
5150
+ var isGroupingRule = currentScope && currentScope instanceof CSSOM.CSSGroupingRule;
5151
+
5152
+ if (!isPrependedAmpersand && isGroupingRule) {
5153
+ // Proper nesting - set nestedSelectorRule to parent for more nested selectors
5154
+ // But only if it's a CSSStyleRule or CSSScopeRule, not other grouping rules like @media
5155
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5156
+ nestedSelectorRule = currentScope;
5157
+ }
5158
+ // If currentScope is another type of grouping rule (like @media), keep nestedSelectorRule unchanged
5159
+ } else {
5160
+ // Prepended & or not nested in grouping rule - reset to prevent CSSNestedDeclarations
5161
+ nestedSelectorRule = null;
5162
+ }
5163
+ } else if (nestedSelectorRule && currentScope instanceof CSSOM.CSSGroupingRule) {
5164
+ // When closing a nested rule that's not the nestedSelectorRule itself,
5165
+ // maintain nestedSelectorRule if we're still inside a grouping rule
5166
+ // This ensures declarations after nested selectors inside @media/@supports etc. work correctly
5167
+ }
5168
+ styleRule = null;
5169
+ break;
5023
5170
  }
5024
- } else {
5025
- currentScope = parentRule;
5026
- }
5171
+ case "keyframeRule-begin":
5172
+ case "before-selector":
5173
+ case "selector":
5174
+ // End of media/supports/document rule.
5175
+ if (!parentRule) {
5176
+ parseError("Unexpected }");
5177
+
5178
+ var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
5179
+ if (hasPreviousStyleRule) {
5180
+ i = ignoreBalancedBlock(i, token.slice(i), 1);
5181
+ }
5027
5182
 
5028
- buffer = "";
5029
- state = "before-selector";
5030
- break;
5031
- }
5032
- break;
5183
+ break;
5184
+ }
5033
5185
 
5034
- default:
5035
- switch (state) {
5036
- case "before-selector":
5037
- state = "selector";
5038
- if ((styleRule || scopeRule) && parentRule) {
5039
- // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
5040
- // If Declaration inside Nested Selector let's keep the same styleRule
5041
- if (
5042
- parentRule.constructor.name === "CSSStyleRule"
5043
- || parentRule.constructor.name === "CSSMediaRule"
5044
- || parentRule.constructor.name === "CSSSupportsRule"
5045
- || parentRule.constructor.name === "CSSContainerRule"
5046
- || parentRule.constructor.name === "CSSScopeRule"
5047
- || parentRule.constructor.name === "CSSLayerBlockRule"
5048
- || parentRule.constructor.name === "CSSStartingStyleRule"
5049
- ) {
5050
- // parentRule.__parentRule = styleRule;
5051
- state = "before-name";
5052
- if (styleRule !== parentRule) {
5053
- styleRule = new CSSOM.CSSNestedDeclarations();
5054
- styleRule.__starts = i;
5186
+ while (ancestorRules.length > 0) {
5187
+ parentRule = ancestorRules.pop();
5188
+
5189
+ if (parentRule instanceof CSSOM.CSSGroupingRule && (parentRule.constructor.name !== 'CSSStyleRule' || parentRule.__parentRule)) {
5190
+ if (nestedSelectorRule) {
5191
+ if (nestedSelectorRule.parentRule) {
5192
+ prevScope = nestedSelectorRule;
5193
+ currentScope = nestedSelectorRule.parentRule;
5194
+ if (currentScope.cssRules.findIndex(function (rule) {
5195
+ return rule === prevScope
5196
+ }) === -1) {
5197
+ currentScope.cssRules.push(prevScope);
5198
+ }
5199
+ nestedSelectorRule = currentScope;
5200
+ } else {
5201
+ // If nestedSelectorRule doesn't have a parentRule, we're closing a grouping rule
5202
+ // inside a top-level CSSStyleRule. We need to push currentScope to the parentRule.
5203
+ prevScope = currentScope;
5204
+ // Push to actual parent from ancestorRules if available
5205
+ var actualParent = ancestorRules.length > 0 ? ancestorRules[ancestorRules.length - 1] : nestedSelectorRule;
5206
+ actualParent !== prevScope && actualParent.cssRules.push(prevScope);
5207
+ // Update currentScope to the nestedSelectorRule before breaking
5208
+ currentScope = actualParent;
5209
+ parentRule = actualParent;
5210
+ break;
5211
+ }
5212
+ } else {
5213
+ prevScope = currentScope;
5214
+ parentRule !== prevScope && parentRule.cssRules.push(prevScope);
5215
+ break;
5216
+ }
5055
5217
  }
5056
5218
  }
5057
-
5058
- } else if (nestedSelectorRule && parentRule && (
5059
- parentRule.constructor.name === "CSSStyleRule"
5060
- || parentRule.constructor.name === "CSSMediaRule"
5061
- || parentRule.constructor.name === "CSSSupportsRule"
5062
- || parentRule.constructor.name === "CSSContainerRule"
5063
- || parentRule.constructor.name === "CSSLayerBlockRule"
5064
- || parentRule.constructor.name === "CSSStartingStyleRule"
5065
- )) {
5066
- state = "before-name";
5067
- if (parentRule.cssRules.length) {
5068
- currentScope = nestedSelectorRule = parentRule;
5069
- styleRule = new CSSOM.CSSNestedDeclarations();
5070
- styleRule.__starts = i;
5071
- } else {
5072
- if (parentRule.constructor.name === "CSSStyleRule") {
5073
- styleRule = parentRule;
5219
+
5220
+ // If currentScope has a __parentRule and wasn't added yet, add it
5221
+ if (ancestorRules.length === 0 && currentScope.__parentRule && currentScope.__parentRule.cssRules) {
5222
+ if (currentScope.__parentRule.cssRules.findIndex(function (rule) {
5223
+ return rule === currentScope
5224
+ }) === -1) {
5225
+ currentScope.__parentRule.cssRules.push(currentScope);
5226
+ }
5227
+ }
5228
+
5229
+ // Only handle top-level rule closing if we processed all ancestors
5230
+ if (ancestorRules.length === 0 && currentScope.parentRule == null) {
5231
+ currentScope.__ends = i + 1;
5232
+ if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
5233
+ return rule === currentScope
5234
+ }) === -1) {
5235
+ topScope.cssRules.push(currentScope);
5236
+ }
5237
+ currentScope = topScope;
5238
+ if (nestedSelectorRule === parentRule) {
5239
+ // Check if this selector is really starting inside another selector
5240
+ var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
5241
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
5242
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
5243
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
5244
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
5245
+
5246
+ if (openingBraceLen === closingBraceLen) {
5247
+ // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
5248
+ nestedSelectorRule.__ends = i + 1;
5249
+ nestedSelectorRule = null;
5250
+ parentRule = null;
5251
+ }
5074
5252
  } else {
5253
+ parentRule = null;
5254
+ }
5255
+ } else {
5256
+ currentScope = parentRule;
5257
+ }
5258
+
5259
+ buffer = "";
5260
+ state = "before-selector";
5261
+ break;
5262
+ }
5263
+ break;
5264
+
5265
+ default:
5266
+ switch (state) {
5267
+ case "before-selector":
5268
+ state = "selector";
5269
+ if ((styleRule || scopeRule) && parentRule) {
5270
+ // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
5271
+ // If Declaration inside Nested Selector let's keep the same styleRule
5272
+ if (!isSelectorStartChar(character) && !isWhitespaceChar(character) && parentRule instanceof CSSOM.CSSGroupingRule) {
5273
+ // parentRule.__parentRule = styleRule;
5274
+ state = "before-name";
5275
+ if (styleRule !== parentRule) {
5276
+ styleRule = new CSSOM.CSSNestedDeclarations();
5277
+ styleRule.__starts = i;
5278
+ }
5279
+ }
5280
+
5281
+ } else if (nestedSelectorRule && parentRule && parentRule instanceof CSSOM.CSSGroupingRule) {
5282
+ if (isSelectorStartChar(character)) {
5283
+ // If starting with a selector character, create CSSStyleRule instead of CSSNestedDeclarations
5075
5284
  styleRule = new CSSOM.CSSStyleRule();
5076
- styleRule.__starts = i;
5285
+ styleRule.__starts = i;
5286
+ } else if (!isWhitespaceChar(character)) {
5287
+ // Starting a declaration (not whitespace, not a selector)
5288
+ state = "before-name";
5289
+ // Check if we should create CSSNestedDeclarations
5290
+ // This happens if: parent has cssRules OR nestedSelectorRule exists (indicating CSSStyleRule in hierarchy)
5291
+ if (parentRule.cssRules.length || nestedSelectorRule) {
5292
+ currentScope = parentRule;
5293
+ // Only set nestedSelectorRule if parentRule is CSSStyleRule or CSSScopeRule
5294
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
5295
+ nestedSelectorRule = parentRule;
5296
+ }
5297
+ styleRule = new CSSOM.CSSNestedDeclarations();
5298
+ styleRule.__starts = i;
5299
+ } else {
5300
+ if (parentRule.constructor.name === "CSSStyleRule") {
5301
+ styleRule = parentRule;
5302
+ } else {
5303
+ styleRule = new CSSOM.CSSStyleRule();
5304
+ styleRule.__starts = i;
5305
+ }
5306
+ }
5077
5307
  }
5078
5308
  }
5079
- } else {
5080
- styleRule = new CSSOM.CSSStyleRule();
5081
- styleRule.__starts = i;
5082
- }
5083
- break;
5084
- case "before-name":
5085
- state = "name";
5086
- break;
5087
- case "before-value":
5088
- state = "value";
5089
- break;
5090
- case "importRule-begin":
5091
- state = "importRule";
5092
- break;
5093
- case "namespaceRule-begin":
5094
- state = "namespaceRule";
5095
- break;
5309
+ break;
5310
+ case "before-name":
5311
+ state = "name";
5312
+ break;
5313
+ case "before-value":
5314
+ state = "value";
5315
+ break;
5316
+ case "importRule-begin":
5317
+ state = "importRule";
5318
+ break;
5319
+ case "namespaceRule-begin":
5320
+ state = "namespaceRule";
5321
+ break;
5322
+ }
5323
+ buffer += character;
5324
+ break;
5325
+ }
5326
+
5327
+ // Auto-close all unclosed nested structures
5328
+ // Check AFTER processing the character, at the ORIGINAL ending index
5329
+ // Only add closing braces if CSS is incomplete (not at top scope)
5330
+ if (i === initialEndingIndex && (currentScope !== topScope || ancestorRules.length > 0)) {
5331
+ var needsClosing = ancestorRules.length;
5332
+ if (currentScope !== topScope && ancestorRules.indexOf(currentScope) === -1) {
5333
+ needsClosing += 1;
5334
+ }
5335
+ // Add closing braces for all unclosed structures
5336
+ for (var closeIdx = 0; closeIdx < needsClosing; closeIdx++) {
5337
+ token += "}";
5338
+ endingIndex += 1;
5096
5339
  }
5097
- buffer += character;
5098
- break;
5099
5340
  }
5100
5341
  }
5101
5342