@acemir/cssom 0.9.24 → 0.9.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/CSSOM.js 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
 
@@ -842,12 +800,20 @@ CSSOM.MediaList.prototype = {
842
800
  * @param {string} value
843
801
  */
844
802
  set mediaText(value) {
845
- var values = value.split(",").filter(function(text){
846
- return !!text;
847
- });
848
- var length = this.length = values.length;
849
- for (var i=0; i<length; i++) {
850
- this[i] = values[i].trim();
803
+ if (typeof value === "string") {
804
+ var values = value.split(",").filter(function(text){
805
+ return !!text;
806
+ });
807
+ var length = this.length = values.length;
808
+ for (var i=0; i<length; i++) {
809
+ this[i] = values[i].trim();
810
+ }
811
+ } else if (value === null) {
812
+ var length = this.length;
813
+ for (var i = 0; i < length; i++) {
814
+ delete this[i];
815
+ }
816
+ this.length = 0;
851
817
  }
852
818
  },
853
819
 
@@ -869,8 +835,15 @@ CSSOM.MediaList.prototype = {
869
835
  if (index !== -1) {
870
836
  Array.prototype.splice.call(this, index, 1);
871
837
  }
872
- }
838
+ },
839
+
840
+ item: function(index) {
841
+ return this[index] || null;
842
+ },
873
843
 
844
+ toString: function() {
845
+ return this.mediaText;
846
+ }
874
847
  };
875
848
 
876
849
 
@@ -888,9 +861,15 @@ CSSOM.CSSMediaRule = function CSSMediaRule() {
888
861
  this.__media = new CSSOM.MediaList();
889
862
  };
890
863
 
891
- CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
864
+ CSSOM.CSSMediaRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
892
865
  CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
893
- CSSOM.CSSMediaRule.prototype.type = 4;
866
+
867
+ Object.setPrototypeOf(CSSOM.CSSMediaRule, CSSOM.CSSConditionRule);
868
+
869
+ Object.defineProperty(CSSOM.CSSMediaRule.prototype, "type", {
870
+ value: 4,
871
+ writable: false
872
+ });
894
873
 
895
874
  // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
896
875
  Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
@@ -926,9 +905,7 @@ Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
926
905
  }
927
906
  values = valuesArr.join("\n ") + "\n}";
928
907
  return "@media " + this.media.mediaText + values;
929
- },
930
- configurable: true,
931
- enumerable: true
908
+ }
932
909
  }
933
910
  });
934
911
 
@@ -946,9 +923,15 @@ CSSOM.CSSContainerRule = function CSSContainerRule() {
946
923
  CSSOM.CSSConditionRule.call(this);
947
924
  };
948
925
 
949
- CSSOM.CSSContainerRule.prototype = new CSSOM.CSSConditionRule();
926
+ CSSOM.CSSContainerRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
950
927
  CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
951
- CSSOM.CSSContainerRule.prototype.type = 17;
928
+
929
+ Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule);
930
+
931
+ Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", {
932
+ value: 17,
933
+ writable: false
934
+ });
952
935
 
953
936
  Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
954
937
  "cssText": {
@@ -965,9 +948,7 @@ Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
965
948
  }
966
949
  values = valuesArr.join("\n ") + "\n}";
967
950
  return "@container " + this.conditionText + values;
968
- },
969
- configurable: true,
970
- enumerable: true
951
+ }
971
952
  },
972
953
  "containerName": {
973
954
  get: function() {
@@ -1002,9 +983,15 @@ CSSOM.CSSSupportsRule = function CSSSupportsRule() {
1002
983
  CSSOM.CSSConditionRule.call(this);
1003
984
  };
1004
985
 
1005
- CSSOM.CSSSupportsRule.prototype = new CSSOM.CSSConditionRule();
986
+ CSSOM.CSSSupportsRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
1006
987
  CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
1007
- CSSOM.CSSSupportsRule.prototype.type = 12;
988
+
989
+ Object.setPrototypeOf(CSSOM.CSSSupportsRule, CSSOM.CSSConditionRule);
990
+
991
+ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "type", {
992
+ value: 12,
993
+ writable: false
994
+ });
1008
995
 
1009
996
  Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
1010
997
  get: function() {
@@ -1041,9 +1028,11 @@ CSSOM.CSSImportRule = function CSSImportRule() {
1041
1028
  this.__styleSheet = new CSSOM.CSSStyleSheet();
1042
1029
  };
1043
1030
 
1044
- CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
1031
+ CSSOM.CSSImportRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1045
1032
  CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
1046
1033
 
1034
+ Object.setPrototypeOf(CSSOM.CSSImportRule, CSSOM.CSSRule);
1035
+
1047
1036
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
1048
1037
  value: 3,
1049
1038
  writable: false
@@ -1053,8 +1042,53 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1053
1042
  get: function() {
1054
1043
  var mediaText = this.media.mediaText;
1055
1044
  return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
1045
+ }
1046
+ });
1047
+
1048
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
1049
+ get: function() {
1050
+ return this.__href;
1051
+ }
1052
+ });
1053
+
1054
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
1055
+ get: function() {
1056
+ return this.__media;
1056
1057
  },
1057
- set: function(cssText) {
1058
+ set: function(value) {
1059
+ if (typeof value === "string") {
1060
+ this.__media.mediaText = value;
1061
+ } else {
1062
+ this.__media = value;
1063
+ }
1064
+ }
1065
+ });
1066
+
1067
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
1068
+ get: function() {
1069
+ return this.__layerName;
1070
+ }
1071
+ });
1072
+
1073
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
1074
+ get: function() {
1075
+ return this.__supportsText;
1076
+ }
1077
+ });
1078
+
1079
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
1080
+ get: function() {
1081
+ return this.__styleSheet;
1082
+ }
1083
+ });
1084
+
1085
+ /**
1086
+ * NON-STANDARD
1087
+ * Rule text parser.
1088
+ * @param {string} cssText
1089
+ */
1090
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "parse", {
1091
+ value: function(cssText) {
1058
1092
  var i = 0;
1059
1093
 
1060
1094
  /**
@@ -1233,43 +1267,6 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1233
1267
  }
1234
1268
  });
1235
1269
 
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
1270
 
1274
1271
 
1275
1272
 
@@ -1285,20 +1282,43 @@ CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
1285
1282
  this.__namespaceURI = "";
1286
1283
  };
1287
1284
 
1288
- CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
1285
+ CSSOM.CSSNamespaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1289
1286
  CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
1290
1287
 
1288
+ Object.setPrototypeOf(CSSOM.CSSNamespaceRule, CSSOM.CSSRule);
1289
+
1291
1290
  Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
1292
1291
  value: 10,
1293
- writable: false
1292
+ writable: false
1294
1293
  });
1295
1294
 
1296
1295
  Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1297
1296
  get: function() {
1298
1297
  return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
1299
- },
1300
- set: function(cssText) {
1301
- var newPrefix = "";
1298
+ }
1299
+ });
1300
+
1301
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
1302
+ get: function() {
1303
+ return this.__prefix;
1304
+ }
1305
+ });
1306
+
1307
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
1308
+ get: function() {
1309
+ return this.__namespaceURI;
1310
+ }
1311
+ });
1312
+
1313
+
1314
+ /**
1315
+ * NON-STANDARD
1316
+ * Rule text parser.
1317
+ * @param {string} cssText
1318
+ */
1319
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "parse", {
1320
+ value: function(cssText) {
1321
+ var newPrefix = "";
1302
1322
  var newNamespaceURI = "";
1303
1323
 
1304
1324
  // Remove @namespace and trim
@@ -1345,19 +1365,6 @@ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1345
1365
  }
1346
1366
  });
1347
1367
 
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
1368
 
1362
1369
 
1363
1370
 
@@ -1371,9 +1378,16 @@ CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
1371
1378
  this.__style.parentRule = this;
1372
1379
  };
1373
1380
 
1374
- CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule();
1381
+ CSSOM.CSSFontFaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1375
1382
  CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
1376
- CSSOM.CSSFontFaceRule.prototype.type = 5;
1383
+
1384
+ Object.setPrototypeOf(CSSOM.CSSFontFaceRule, CSSOM.CSSRule);
1385
+
1386
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "type", {
1387
+ value: 5,
1388
+ writable: false
1389
+ });
1390
+
1377
1391
  //FIXME
1378
1392
  //CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1379
1393
  //CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1414,9 +1428,16 @@ CSSOM.CSSHostRule = function CSSHostRule() {
1414
1428
  this.cssRules = new CSSOM.CSSRuleList();
1415
1429
  };
1416
1430
 
1417
- CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule();
1431
+ CSSOM.CSSHostRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1418
1432
  CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
1419
- CSSOM.CSSHostRule.prototype.type = 1001;
1433
+
1434
+ Object.setPrototypeOf(CSSOM.CSSHostRule, CSSOM.CSSRule);
1435
+
1436
+ Object.defineProperty(CSSOM.CSSHostRule.prototype, "type", {
1437
+ value: 1001,
1438
+ writable: false
1439
+ });
1440
+
1420
1441
  //FIXME
1421
1442
  //CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1422
1443
  //CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1451,9 +1472,16 @@ CSSOM.CSSStartingStyleRule = function CSSStartingStyleRule() {
1451
1472
  CSSOM.CSSGroupingRule.call(this);
1452
1473
  };
1453
1474
 
1454
- CSSOM.CSSStartingStyleRule.prototype = new CSSOM.CSSGroupingRule();
1475
+ CSSOM.CSSStartingStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
1455
1476
  CSSOM.CSSStartingStyleRule.prototype.constructor = CSSOM.CSSStartingStyleRule;
1456
- CSSOM.CSSStartingStyleRule.prototype.type = 1002;
1477
+
1478
+ Object.setPrototypeOf(CSSOM.CSSStartingStyleRule, CSSOM.CSSGroupingRule);
1479
+
1480
+ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "type", {
1481
+ value: 1002,
1482
+ writable: false
1483
+ });
1484
+
1457
1485
  //FIXME
1458
1486
  //CSSOM.CSSStartingStyleRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1459
1487
  //CSSOM.CSSStartingStyleRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1481,15 +1509,38 @@ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
1481
1509
 
1482
1510
 
1483
1511
  /**
1484
- * @constructor
1485
1512
  * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
1486
1513
  */
1487
1514
  CSSOM.StyleSheet = function StyleSheet() {
1515
+ this.__href = null;
1516
+ this.__ownerNode = null;
1517
+ this.__title = null;
1488
1518
  this.__media = new CSSOM.MediaList();
1489
1519
  this.__parentStyleSheet = null;
1520
+ this.disabled = false;
1490
1521
  };
1491
1522
 
1492
1523
  Object.defineProperties(CSSOM.StyleSheet.prototype, {
1524
+ type: {
1525
+ get: function() {
1526
+ return "text/css";
1527
+ }
1528
+ },
1529
+ href: {
1530
+ get: function() {
1531
+ return this.__href;
1532
+ }
1533
+ },
1534
+ ownerNode: {
1535
+ get: function() {
1536
+ return this.__ownerNode;
1537
+ }
1538
+ },
1539
+ title: {
1540
+ get: function() {
1541
+ return this.__title;
1542
+ }
1543
+ },
1493
1544
  media: {
1494
1545
  get: function() {
1495
1546
  return this.__media;
@@ -1515,21 +1566,52 @@ Object.defineProperties(CSSOM.StyleSheet.prototype, {
1515
1566
 
1516
1567
  /**
1517
1568
  * @constructor
1569
+ * @param {CSSStyleSheetInit} [opts] - CSSStyleSheetInit options.
1570
+ * @param {string} [opts.baseURL] - The base URL of the stylesheet.
1571
+ * @param {boolean} [opts.disabled] - The disabled attribute of the stylesheet.
1572
+ * @param {MediaList | string} [opts.media] - The media attribute of the stylesheet.
1518
1573
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
1519
1574
  */
1520
- CSSOM.CSSStyleSheet = function CSSStyleSheet() {
1575
+ CSSOM.CSSStyleSheet = function CSSStyleSheet(opts) {
1521
1576
  CSSOM.StyleSheet.call(this);
1522
1577
  this.__constructed = true;
1523
- this.cssRules = new CSSOM.CSSRuleList();
1578
+ this.__cssRules = new CSSOM.CSSRuleList();
1579
+ this.__ownerRule = null;
1580
+
1581
+ if (opts && typeof opts === "object") {
1582
+ if (opts.baseURL && typeof opts.baseURL === "string") {
1583
+ this.__baseURL = opts.baseURL;
1584
+ }
1585
+ if (opts.media && typeof opts.media === "string") {
1586
+ this.media.mediaText = opts.media;
1587
+ }
1588
+ if (typeof opts.disabled === "boolean") {
1589
+ this.disabled = opts.disabled;
1590
+ }
1591
+ }
1524
1592
  };
1525
1593
 
1526
1594
 
1527
- CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
1595
+ CSSOM.CSSStyleSheet.prototype = Object.create(CSSOM.StyleSheet.prototype);
1528
1596
  CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
1529
1597
 
1598
+ Object.setPrototypeOf(CSSOM.CSSStyleSheet, CSSOM.StyleSheet);
1599
+
1600
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "cssRules", {
1601
+ get: function() {
1602
+ return this.__cssRules;
1603
+ }
1604
+ });
1605
+
1530
1606
  Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
1531
1607
  get: function() {
1532
- return this.cssRules;
1608
+ return this.__cssRules;
1609
+ }
1610
+ });
1611
+
1612
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "ownerRule", {
1613
+ get: function() {
1614
+ return this.__ownerRule;
1533
1615
  }
1534
1616
  });
1535
1617
 
@@ -1729,6 +1811,9 @@ CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1729
1811
  };
1730
1812
 
1731
1813
  CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
1814
+ if (index === void 0) {
1815
+ index = 0;
1816
+ }
1732
1817
  this.deleteRule(index);
1733
1818
  };
1734
1819
 
@@ -1741,11 +1826,17 @@ CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
1741
1826
  */
1742
1827
  CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1743
1828
  var _Promise;
1744
- if (this.__globalObject) {
1745
- _Promise = this.__globalObject['Promise'];
1829
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()['Promise']) {
1830
+ _Promise = CSSOM.getGlobalObject()['Promise'];
1746
1831
  } else {
1747
1832
  _Promise = Promise;
1748
1833
  }
1834
+ var _setTimeout;
1835
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()['setTimeout']) {
1836
+ _setTimeout = CSSOM.getGlobalObject()['setTimeout'];
1837
+ } else {
1838
+ _setTimeout = setTimeout;
1839
+ }
1749
1840
  var sheet = this;
1750
1841
  return new _Promise(function (resolve, reject) {
1751
1842
  // If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
@@ -1758,7 +1849,7 @@ CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1758
1849
  sheet.__disallowModification = true;
1759
1850
 
1760
1851
  // In parallel, do these steps:
1761
- setTimeout(function() {
1852
+ _setTimeout(function() {
1762
1853
  // Let rules be the result of running parse a stylesheet's contents from text.
1763
1854
  var rules = new CSSOM.CSSRuleList();
1764
1855
  CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
@@ -1772,7 +1863,7 @@ CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1772
1863
  }
1773
1864
  }
1774
1865
  // Set sheet's CSS rules to rules.
1775
- sheet.cssRules = rules;
1866
+ sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
1776
1867
  // Unset sheet’s disallow modification flag.
1777
1868
  delete sheet.__disallowModification;
1778
1869
  // Resolve promise with sheet.
@@ -1807,7 +1898,7 @@ CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
1807
1898
  }
1808
1899
  }
1809
1900
  // Set sheet's CSS rules to rules.
1810
- sheet.cssRules = rules;
1901
+ sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
1811
1902
  }
1812
1903
 
1813
1904
  /**
@@ -1869,9 +1960,15 @@ CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
1869
1960
  });
1870
1961
  };
1871
1962
 
1872
- CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule();
1963
+ CSSOM.CSSKeyframesRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1873
1964
  CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
1874
- CSSOM.CSSKeyframesRule.prototype.type = 7;
1965
+
1966
+ Object.setPrototypeOf(CSSOM.CSSKeyframesRule, CSSOM.CSSRule);
1967
+
1968
+ Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "type", {
1969
+ value: 7,
1970
+ writable: false
1971
+ });
1875
1972
 
1876
1973
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
1877
1974
  Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
@@ -2071,9 +2168,16 @@ CSSOM.CSSKeyframeRule = function CSSKeyframeRule() {
2071
2168
  this.__style.parentRule = this;
2072
2169
  };
2073
2170
 
2074
- CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule();
2171
+ CSSOM.CSSKeyframeRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2075
2172
  CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule;
2076
- CSSOM.CSSKeyframeRule.prototype.type = 8;
2173
+
2174
+ Object.setPrototypeOf(CSSOM.CSSKeyframeRule, CSSOM.CSSRule);
2175
+
2176
+ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "type", {
2177
+ value: 8,
2178
+ writable: false
2179
+ });
2180
+
2077
2181
  //FIXME
2078
2182
  //CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
2079
2183
  //CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2172,9 +2276,16 @@ CSSOM.CSSDocumentRule = function CSSDocumentRule() {
2172
2276
  this.cssRules = new CSSOM.CSSRuleList();
2173
2277
  };
2174
2278
 
2175
- CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule();
2279
+ CSSOM.CSSDocumentRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2176
2280
  CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule;
2177
- CSSOM.CSSDocumentRule.prototype.type = 10;
2281
+
2282
+ Object.setPrototypeOf(CSSOM.CSSDocumentRule, CSSOM.CSSRule);
2283
+
2284
+ Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "type", {
2285
+ value: 10,
2286
+ writable: false
2287
+ });
2288
+
2178
2289
  //FIXME
2179
2290
  //CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
2180
2291
  //CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2243,9 +2354,11 @@ CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
2243
2354
  this._idx = idx;
2244
2355
  };
2245
2356
 
2246
- CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue();
2357
+ CSSOM.CSSValueExpression.prototype = Object.create(CSSOM.CSSValue.prototype);
2247
2358
  CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
2248
2359
 
2360
+ Object.setPrototypeOf(CSSOM.CSSValueExpression, CSSOM.CSSValue);
2361
+
2249
2362
  /**
2250
2363
  * parse css expression() value
2251
2364
  *
@@ -2580,9 +2693,10 @@ CSSOM.CSSScopeRule = function CSSScopeRule() {
2580
2693
  this.__end = null;
2581
2694
  };
2582
2695
 
2583
- CSSOM.CSSScopeRule.prototype = new CSSOM.CSSGroupingRule();
2696
+ CSSOM.CSSScopeRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2584
2697
  CSSOM.CSSScopeRule.prototype.constructor = CSSOM.CSSScopeRule;
2585
2698
 
2699
+ Object.setPrototypeOf(CSSOM.CSSScopeRule, CSSOM.CSSGroupingRule);
2586
2700
 
2587
2701
  Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
2588
2702
  type: {
@@ -2631,9 +2745,15 @@ CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
2631
2745
  this.name = "";
2632
2746
  };
2633
2747
 
2634
- CSSOM.CSSLayerBlockRule.prototype = new CSSOM.CSSGroupingRule();
2748
+ CSSOM.CSSLayerBlockRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2635
2749
  CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
2636
- CSSOM.CSSLayerBlockRule.prototype.type = 18;
2750
+
2751
+ Object.setPrototypeOf(CSSOM.CSSLayerBlockRule, CSSOM.CSSRule);
2752
+
2753
+ Object.defineProperty(CSSOM.CSSLayerBlockRule.prototype, "type", {
2754
+ value: 18,
2755
+ writable: false
2756
+ });
2637
2757
 
2638
2758
  Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
2639
2759
  cssText: {
@@ -2650,9 +2770,7 @@ Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
2650
2770
  }
2651
2771
  values = valuesArr.join("\n ") + "\n}";
2652
2772
  return "@layer" + (this.name ? " " + this.name : "") + values;
2653
- },
2654
- configurable: true,
2655
- enumerable: true,
2773
+ }
2656
2774
  },
2657
2775
  });
2658
2776
 
@@ -2668,17 +2786,21 @@ CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
2668
2786
  this.nameList = [];
2669
2787
  };
2670
2788
 
2671
- CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
2789
+ CSSOM.CSSLayerStatementRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2672
2790
  CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
2673
- CSSOM.CSSLayerStatementRule.prototype.type = 0;
2791
+
2792
+ Object.setPrototypeOf(CSSOM.CSSLayerStatementRule, CSSOM.CSSRule);
2793
+
2794
+ Object.defineProperty(CSSOM.CSSLayerStatementRule.prototype, "type", {
2795
+ value: 0,
2796
+ writable: false
2797
+ });
2674
2798
 
2675
2799
  Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2676
2800
  cssText: {
2677
2801
  get: function () {
2678
2802
  return "@layer " + this.nameList.join(", ") + ";";
2679
- },
2680
- configurable: true,
2681
- enumerable: true,
2803
+ }
2682
2804
  },
2683
2805
  });
2684
2806
 
@@ -2696,9 +2818,11 @@ CSSOM.CSSPageRule = function CSSPageRule() {
2696
2818
  this.__style.parentRule = this;
2697
2819
  };
2698
2820
 
2699
- CSSOM.CSSPageRule.prototype = new CSSOM.CSSGroupingRule();
2821
+ CSSOM.CSSPageRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2700
2822
  CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
2701
2823
 
2824
+ Object.setPrototypeOf(CSSOM.CSSPageRule, CSSOM.CSSGroupingRule);
2825
+
2702
2826
  Object.defineProperty(CSSOM.CSSPageRule.prototype, "type", {
2703
2827
  value: 6,
2704
2828
  writable: false
@@ -2793,161 +2917,9 @@ Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
2793
2917
  values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
2794
2918
  }
2795
2919
  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
2920
  }
2804
2921
  });
2805
2922
 
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
2923
 
2952
2924
 
2953
2925
 
@@ -2956,7 +2928,9 @@ CSSOM.CSSPageRule.parse = function(ruleText) {
2956
2928
  *
2957
2929
  * @param {string} token - The CSS string to parse.
2958
2930
  * @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.
2931
+ * @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.
2932
+ * @param {Element | ProcessingInstruction} [opts.ownerNode] - The owner node of the stylesheet.
2933
+ * @param {CSSRule} [opts.ownerRule] - The owner rule of the stylesheet.
2960
2934
  * @param {CSSOM.CSSStyleSheet} [opts.styleSheet] - Reuse a style sheet instead of creating a new one (e.g. as `parentStyleSheet`)
2961
2935
  * @param {CSSOM.CSSRuleList} [opts.cssRules] - Prepare all rules in this list instead of mutating the style sheet continually
2962
2936
  * @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
@@ -3010,6 +2984,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3010
2984
  styleSheet = opts.styleSheet;
3011
2985
  } else {
3012
2986
  styleSheet = new CSSOM.CSSStyleSheet()
2987
+ styleSheet.__constructed = false;
3013
2988
  }
3014
2989
 
3015
2990
  var topScope;
@@ -3020,7 +2995,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3020
2995
  }
3021
2996
 
3022
2997
  if (opts && opts.globalObject) {
3023
- styleSheet.__globalObject = opts.globalObject;
2998
+ CSSOM.setup({ globalObject: opts.globalObject });
2999
+ }
3000
+
3001
+ if (opts && opts.ownerNode) {
3002
+ styleSheet.__ownerNode = opts.ownerNode;
3003
+ var ownerNodeMedia = opts.ownerNode.media || (opts.ownerNode.getAttribute && opts.ownerNode.getAttribute("media"));
3004
+ if (ownerNodeMedia) {
3005
+ styleSheet.media.mediaText = ownerNodeMedia;
3006
+ }
3007
+ }
3008
+
3009
+ if (opts && opts.ownerRule) {
3010
+ styleSheet.__ownerRule = opts.ownerRule;
3024
3011
  }
3025
3012
 
3026
3013
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
@@ -3032,7 +3019,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3032
3019
  var ancestorRules = [];
3033
3020
  var prevScope;
3034
3021
 
3035
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
3022
+ var name, priority = "", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
3036
3023
 
3037
3024
  // Track defined namespace prefixes for validation
3038
3025
  var definedNamespacePrefixes = {};
@@ -3130,14 +3117,14 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3130
3117
  var ruleClosingMatch = matchBalancedBlock(str, fromIndex);
3131
3118
  if (ruleClosingMatch) {
3132
3119
  var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
3133
- i+= ignoreRange;
3120
+ i += ignoreRange;
3134
3121
  if (token.charAt(i) === '}') {
3135
3122
  i -= 1;
3136
3123
  }
3137
3124
  } else {
3138
3125
  i += str.length;
3139
3126
  }
3140
- return i;
3127
+ return i;
3141
3128
  }
3142
3129
 
3143
3130
  /**
@@ -3147,29 +3134,29 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3147
3134
  */
3148
3135
  function parseScopePrelude(preludeContent) {
3149
3136
  var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
3150
-
3137
+
3151
3138
  // Restore the parentheses that were consumed by the split
3152
3139
  if (parts.length === 2) {
3153
3140
  parts[0] = parts[0] + ')';
3154
3141
  parts[1] = '(' + parts[1];
3155
3142
  }
3156
-
3143
+
3157
3144
  var hasStart = parts[0] &&
3158
3145
  parts[0].charAt(0) === '(' &&
3159
3146
  parts[0].charAt(parts[0].length - 1) === ')';
3160
3147
  var hasEnd = parts[1] &&
3161
3148
  parts[1].charAt(0) === '(' &&
3162
3149
  parts[1].charAt(parts[1].length - 1) === ')';
3163
-
3150
+
3164
3151
  // Handle case: @scope to (<end>)
3165
3152
  var hasOnlyEnd = !hasStart &&
3166
3153
  !hasEnd &&
3167
3154
  parts[0].indexOf('to (') === 0 &&
3168
3155
  parts[0].charAt(parts[0].length - 1) === ')';
3169
-
3156
+
3170
3157
  var startSelector = '';
3171
3158
  var endSelector = '';
3172
-
3159
+
3173
3160
  if (hasStart) {
3174
3161
  startSelector = parts[0].slice(1, -1).trim();
3175
3162
  }
@@ -3179,7 +3166,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3179
3166
  if (hasOnlyEnd) {
3180
3167
  endSelector = parts[0].slice(4, -1).trim();
3181
3168
  }
3182
-
3169
+
3183
3170
  return {
3184
3171
  startSelector: startSelector,
3185
3172
  endSelector: endSelector,
@@ -3217,11 +3204,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3217
3204
  var inDoubleQuote = false;
3218
3205
  var inAttr = false;
3219
3206
  var stack = useStack ? [] : null;
3220
-
3207
+
3221
3208
  for (var i = 0; i < selector.length; i++) {
3222
3209
  var char = selector[i];
3223
3210
  var prevChar = i > 0 ? selector[i - 1] : '';
3224
-
3211
+
3225
3212
  if (inSingleQuote) {
3226
3213
  if (char === "'" && prevChar !== "\\") {
3227
3214
  inSingleQuote = false;
@@ -3272,7 +3259,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3272
3259
  }
3273
3260
  }
3274
3261
  }
3275
-
3262
+
3276
3263
  // Check if everything is balanced
3277
3264
  if (useStack) {
3278
3265
  return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
@@ -3322,7 +3309,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3322
3309
  /(?:^|[\s>+~,\[])cue\s*\(/i,
3323
3310
  /(?:^|[\s>+~,\[])cue-region\s*\(/i
3324
3311
  ];
3325
-
3312
+
3326
3313
  for (var i = 0; i < invalidPatterns.length; i++) {
3327
3314
  if (invalidPatterns[i].test(selector)) {
3328
3315
  return true;
@@ -3368,7 +3355,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3368
3355
  isValid = true;
3369
3356
  }
3370
3357
  }
3371
-
3358
+
3372
3359
  // Additional validation for @scope rule
3373
3360
  if (isValid && atRuleKey === "@scope") {
3374
3361
  var openBraceIndex = ruleSlice.indexOf('{');
@@ -3387,7 +3374,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3387
3374
  var hasStart = parsedScopePrelude.hasStart;
3388
3375
  var hasEnd = parsedScopePrelude.hasEnd;
3389
3376
  var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
3390
-
3377
+
3391
3378
  // Validation rules for @scope:
3392
3379
  // 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
3393
3380
  if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
@@ -3423,13 +3410,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3423
3410
  if (openBraceIndex !== -1) {
3424
3411
  // Extract the rule prelude (everything between the at-rule and {)
3425
3412
  var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
3426
-
3413
+
3427
3414
  // Skip past at-rule keyword and whitespace
3428
3415
  var preludeContent = rulePrelude.slice("@page".length).trim();
3429
3416
 
3430
3417
  if (preludeContent.length > 0) {
3431
3418
  var trimmedValue = preludeContent.trim();
3432
-
3419
+
3433
3420
  // Empty selector is valid for @page
3434
3421
  if (trimmedValue !== '') {
3435
3422
  // Parse @page selectorText for page name and pseudo-pages
@@ -3453,7 +3440,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3453
3440
 
3454
3441
  // Validate pseudo-pages if present
3455
3442
  if (pseudoPages) {
3456
- var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
3443
+ var pseudos = pseudoPages.split(':').filter(function (p) { return p; });
3457
3444
  var validPseudos = ['left', 'right', 'first', 'blank'];
3458
3445
  var allValid = true;
3459
3446
  for (var j = 0; j < pseudos.length; j++) {
@@ -3462,7 +3449,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3462
3449
  break;
3463
3450
  }
3464
3451
  }
3465
-
3452
+
3466
3453
  if (!allValid) {
3467
3454
  isValid = false;
3468
3455
  }
@@ -3471,15 +3458,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3471
3458
  isValid = false;
3472
3459
  }
3473
3460
  }
3474
-
3461
+
3475
3462
  }
3476
3463
  }
3477
3464
  }
3478
-
3465
+
3479
3466
  if (!isValid) {
3480
3467
  // If it's invalid the browser will simply ignore the entire invalid block
3481
3468
  // Use regex to find the closing brace of the invalid rule
3482
-
3469
+
3483
3470
  // Regex used above is not ES5 compliant. Using alternative.
3484
3471
  // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
3485
3472
  var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
@@ -3494,7 +3481,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3494
3481
  // Check if there's a semicolon before the invalid at-rule and the first opening brace
3495
3482
  if (atRuleKey === "@layer") {
3496
3483
  var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
3497
- if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
3484
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";") {
3498
3485
  // Ignore the rule block until the semicolon
3499
3486
  i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
3500
3487
  state = "before-selector";
@@ -3512,7 +3499,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3512
3499
 
3513
3500
  // Helper functions for looseSelectorValidator
3514
3501
  // Defined outside to avoid recreation on every validation call
3515
-
3502
+
3516
3503
  /**
3517
3504
  * Check if character is a valid identifier start
3518
3505
  * @param {string} c - Character to check
@@ -3584,19 +3571,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3584
3571
  // Helper: Parse namespace prefix (optional)
3585
3572
  function parseNamespace() {
3586
3573
  var start = i;
3587
-
3574
+
3588
3575
  // Match: *| or identifier| or |
3589
3576
  if (i < len && selector[i] === '*') {
3590
3577
  i++;
3591
3578
  } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
3592
3579
  parseIdentifier();
3593
3580
  }
3594
-
3581
+
3595
3582
  if (i < len && selector[i] === '|') {
3596
3583
  i++;
3597
3584
  return true;
3598
3585
  }
3599
-
3586
+
3600
3587
  // Rollback if no pipe found
3601
3588
  i = start;
3602
3589
  return false;
@@ -3607,15 +3594,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3607
3594
  if (i >= len || selector[i] !== '(') {
3608
3595
  return false;
3609
3596
  }
3610
-
3597
+
3611
3598
  i++; // Skip opening paren
3612
3599
  var depth = 1;
3613
3600
  var inString = false;
3614
3601
  var stringChar = '';
3615
-
3602
+
3616
3603
  while (i < len && depth > 0) {
3617
3604
  var c = selector[i];
3618
-
3605
+
3619
3606
  if (c === '\\' && i + 1 < len) {
3620
3607
  i += 2; // Skip escaped character
3621
3608
  } else if (!inString && (c === '"' || c === '\'')) {
@@ -3635,7 +3622,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3635
3622
  i++;
3636
3623
  }
3637
3624
  }
3638
-
3625
+
3639
3626
  return depth === 0;
3640
3627
  }
3641
3628
 
@@ -3737,7 +3724,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3737
3724
  // Match type selector with optional namespace: [namespace|]identifier
3738
3725
  else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|')) {
3739
3726
  parseNamespace(); // Optional namespace prefix
3740
-
3727
+
3741
3728
  if (i < len && selector[i] === '*') {
3742
3729
  i++; // Universal selector
3743
3730
  hasMatchedComponent = true;
@@ -3813,36 +3800,36 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3813
3800
  // Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
3814
3801
  // Using iterative parsing instead of regex to avoid exponential backtracking
3815
3802
  var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
3816
-
3803
+
3817
3804
  for (var idx = 0; idx < selector.length; idx++) {
3818
3805
  // Look for pseudo-class/element start
3819
3806
  if (selector[idx] === ':') {
3820
3807
  var pseudoStart = idx;
3821
3808
  idx++;
3822
-
3809
+
3823
3810
  // Skip second colon for pseudo-elements
3824
3811
  if (idx < selector.length && selector[idx] === ':') {
3825
3812
  idx++;
3826
3813
  }
3827
-
3814
+
3828
3815
  // Extract pseudo name
3829
3816
  var nameStart = idx;
3830
3817
  while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
3831
3818
  idx++;
3832
3819
  }
3833
-
3820
+
3834
3821
  if (idx === nameStart) {
3835
3822
  continue; // No name found
3836
3823
  }
3837
-
3824
+
3838
3825
  var pseudoName = selector.substring(nameStart, idx).toLowerCase();
3839
-
3826
+
3840
3827
  // Check if this pseudo has arguments
3841
3828
  if (idx < selector.length && selector[idx] === '(') {
3842
3829
  idx++;
3843
3830
  var contentStart = idx;
3844
3831
  var depth = 1;
3845
-
3832
+
3846
3833
  // Find matching closing paren (handle nesting)
3847
3834
  while (idx < selector.length && depth > 0) {
3848
3835
  if (selector[idx] === '\\') {
@@ -3857,10 +3844,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3857
3844
  idx++;
3858
3845
  }
3859
3846
  }
3860
-
3847
+
3861
3848
  if (depth === 0) {
3862
3849
  var pseudoContent = selector.substring(contentStart, idx - 1);
3863
-
3850
+
3864
3851
  // Check if this pseudo should not have quoted strings
3865
3852
  for (var j = 0; j < noQuotesPseudos.length; j++) {
3866
3853
  if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
@@ -3875,7 +3862,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3875
3862
  // Use the iterative validator to avoid regex backtracking issues
3876
3863
  return looseSelectorValidator(selector);
3877
3864
  }
3878
-
3865
+
3879
3866
  /**
3880
3867
  * Regular expression to match CSS pseudo-classes with arguments.
3881
3868
  *
@@ -3894,7 +3881,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3894
3881
  *
3895
3882
  * REPLACED WITH FUNCTION to avoid exponential backtracking.
3896
3883
  */
3897
-
3884
+
3898
3885
  /**
3899
3886
  * Extract pseudo-classes with arguments from a selector using iterative parsing.
3900
3887
  * Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
@@ -3910,30 +3897,30 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3910
3897
  */
3911
3898
  function extractPseudoClasses(selector) {
3912
3899
  var matches = [];
3913
-
3900
+
3914
3901
  for (var i = 0; i < selector.length; i++) {
3915
3902
  // Look for pseudo-class start (single or double colon)
3916
3903
  if (selector[i] === ':') {
3917
3904
  var pseudoStart = i;
3918
3905
  i++;
3919
-
3906
+
3920
3907
  // Skip second colon for pseudo-elements (::)
3921
3908
  if (i < selector.length && selector[i] === ':') {
3922
3909
  i++;
3923
3910
  }
3924
-
3911
+
3925
3912
  // Extract pseudo name
3926
3913
  var nameStart = i;
3927
3914
  while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
3928
3915
  i++;
3929
3916
  }
3930
-
3917
+
3931
3918
  if (i === nameStart) {
3932
3919
  continue; // No name found
3933
3920
  }
3934
-
3921
+
3935
3922
  var pseudoName = selector.substring(nameStart, i);
3936
-
3923
+
3937
3924
  // Check if this pseudo has arguments
3938
3925
  if (i < selector.length && selector[i] === '(') {
3939
3926
  i++;
@@ -3941,11 +3928,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3941
3928
  var depth = 1;
3942
3929
  var inSingleQuote = false;
3943
3930
  var inDoubleQuote = false;
3944
-
3931
+
3945
3932
  // Find matching closing paren (handle nesting and strings)
3946
3933
  while (i < selector.length && depth > 0) {
3947
3934
  var ch = selector[i];
3948
-
3935
+
3949
3936
  if (ch === '\\') {
3950
3937
  i += 2; // Skip escaped character
3951
3938
  } else if (ch === "'" && !inDoubleQuote) {
@@ -3964,21 +3951,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3964
3951
  i++;
3965
3952
  }
3966
3953
  }
3967
-
3954
+
3968
3955
  if (depth === 0) {
3969
3956
  var pseudoArgs = selector.substring(argsStart, i - 1);
3970
3957
  var fullMatch = selector.substring(pseudoStart, i);
3971
-
3958
+
3972
3959
  // Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
3973
3960
  matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
3974
3961
  }
3975
-
3962
+
3976
3963
  // Move back one since loop will increment
3977
3964
  i--;
3978
3965
  }
3979
3966
  }
3980
3967
  }
3981
-
3968
+
3982
3969
  return matches;
3983
3970
  }
3984
3971
 
@@ -4082,6 +4069,23 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4082
4069
  var pseudoClass = pseudoClassMatches[j][1];
4083
4070
  if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
4084
4071
  var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
4072
+
4073
+ // Check if ANY selector in the list contains & (nesting selector)
4074
+ // If so, skip validation for the entire selector list since & will be replaced at runtime
4075
+ var hasAmpersand = false;
4076
+ for (var k = 0; k < nestedSelectors.length; k++) {
4077
+ if (/&/.test(nestedSelectors[k])) {
4078
+ hasAmpersand = true;
4079
+ break;
4080
+ }
4081
+ }
4082
+
4083
+ // If any selector has &, skip validation for this entire pseudo-class
4084
+ if (hasAmpersand) {
4085
+ continue;
4086
+ }
4087
+
4088
+ // Otherwise, validate each selector normally
4085
4089
  for (var i = 0; i < nestedSelectors.length; i++) {
4086
4090
  var nestedSelector = nestedSelectors[i];
4087
4091
  if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
@@ -4118,10 +4122,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4118
4122
  var inAttr = false;
4119
4123
  var inSingleQuote = false;
4120
4124
  var inDoubleQuote = false;
4121
-
4125
+
4122
4126
  for (var i = 0; i < selector.length; i++) {
4123
4127
  var char = selector[i];
4124
-
4128
+
4125
4129
  if (inSingleQuote) {
4126
4130
  if (char === "'" && selector[i - 1] !== "\\") {
4127
4131
  inSingleQuote = false;
@@ -4148,18 +4152,18 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4148
4152
  }
4149
4153
  }
4150
4154
  }
4151
-
4155
+
4152
4156
  if (pipeIndex === -1) {
4153
4157
  return true; // No namespace, always valid
4154
4158
  }
4155
-
4159
+
4156
4160
  var namespacePrefix = selector.substring(0, pipeIndex);
4157
-
4161
+
4158
4162
  // Universal namespace (*|) and default namespace (|) are always valid
4159
4163
  if (namespacePrefix === '*' || namespacePrefix === '') {
4160
4164
  return true;
4161
4165
  }
4162
-
4166
+
4163
4167
  // Check if the custom namespace prefix is defined
4164
4168
  return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
4165
4169
  }
@@ -4168,22 +4172,92 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4168
4172
  * Processes a CSS selector text
4169
4173
  *
4170
4174
  * @param {string} selectorText - The CSS selector text to process
4171
- * @returns {string} The processed selector text with normalized whitespace
4175
+ * @returns {string} The processed selector text with normalized whitespace and invalid selectors removed
4172
4176
  */
4173
4177
  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) {
4178
+ // Normalize whitespace first
4179
+ var normalized = selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function (match, _, newline) {
4184
4180
  if (newline) return " ";
4185
4181
  return match;
4186
4182
  });
4183
+
4184
+ // Recursively process pseudo-classes to handle nesting
4185
+ return processNestedPseudoClasses(normalized);
4186
+ }
4187
+
4188
+ /**
4189
+ * Recursively processes pseudo-classes to filter invalid selectors
4190
+ *
4191
+ * @param {string} selectorText - The CSS selector text to process
4192
+ * @param {number} depth - Current recursion depth (to prevent infinite loops)
4193
+ * @returns {string} The processed selector text with invalid selectors removed
4194
+ */
4195
+ function processNestedPseudoClasses(selectorText, depth) {
4196
+ // Prevent infinite recursion
4197
+ if (typeof depth === 'undefined') {
4198
+ depth = 0;
4199
+ }
4200
+ if (depth > 10) {
4201
+ return selectorText;
4202
+ }
4203
+
4204
+ var pseudoClassMatches = extractPseudoClasses(selectorText);
4205
+
4206
+ // If no pseudo-classes found, return as-is
4207
+ if (pseudoClassMatches.length === 0) {
4208
+ return selectorText;
4209
+ }
4210
+
4211
+ // Build result by processing matches from right to left (to preserve positions)
4212
+ var result = selectorText;
4213
+
4214
+ for (var j = pseudoClassMatches.length - 1; j >= 0; j--) {
4215
+ var pseudoClass = pseudoClassMatches[j][1];
4216
+ if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
4217
+ var fullMatch = pseudoClassMatches[j][0];
4218
+ var pseudoArgs = pseudoClassMatches[j][2];
4219
+ var matchStart = pseudoClassMatches[j][3];
4220
+
4221
+ // Check if ANY selector contains & BEFORE processing
4222
+ var nestedSelectorsRaw = parseAndSplitNestedSelectors(pseudoArgs);
4223
+ var hasAmpersand = false;
4224
+ for (var k = 0; k < nestedSelectorsRaw.length; k++) {
4225
+ if (/&/.test(nestedSelectorsRaw[k])) {
4226
+ hasAmpersand = true;
4227
+ break;
4228
+ }
4229
+ }
4230
+
4231
+ // If & is present, skip all processing (keep everything unchanged)
4232
+ if (hasAmpersand) {
4233
+ continue;
4234
+ }
4235
+
4236
+ // Recursively process the arguments
4237
+ var processedArgs = processNestedPseudoClasses(pseudoArgs, depth + 1);
4238
+ var nestedSelectors = parseAndSplitNestedSelectors(processedArgs);
4239
+
4240
+ // Filter out invalid selectors
4241
+ var validSelectors = [];
4242
+ for (var i = 0; i < nestedSelectors.length; i++) {
4243
+ var nestedSelector = nestedSelectors[i];
4244
+ if (basicSelectorValidator(nestedSelector)) {
4245
+ validSelectors.push(nestedSelector);
4246
+ }
4247
+ }
4248
+
4249
+ // Reconstruct the pseudo-class with only valid selectors
4250
+ var newArgs = validSelectors.join(', ');
4251
+ var newPseudoClass = ':' + pseudoClass + '(' + newArgs + ')';
4252
+
4253
+ // Replace in the result string using position (processing right to left preserves positions)
4254
+ result = result.substring(0, matchStart) + newPseudoClass + result.substring(matchStart + fullMatch.length);
4255
+ }
4256
+ }
4257
+
4258
+ return result;
4259
+
4260
+ return normalized;
4187
4261
  }
4188
4262
 
4189
4263
  /**
@@ -4197,6 +4271,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4197
4271
  // TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
4198
4272
  // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
4199
4273
 
4274
+ // Check for empty selector lists in pseudo-classes (e.g., :is(), :not(), :where(), :has())
4275
+ // These are invalid after filtering out invalid selectors
4276
+ if (/:(?:is|not|where|has)\(\s*\)/.test(selectorText)) {
4277
+ return false;
4278
+ }
4279
+
4200
4280
  // Check for newlines inside single or double quotes using regex
4201
4281
  // This matches any quoted string (single or double) containing a newline
4202
4282
  var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
@@ -4236,12 +4316,22 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4236
4316
  // Print the error but continue parsing the sheet
4237
4317
  try {
4238
4318
  throw error;
4239
- } catch(e) {
4319
+ } catch (e) {
4240
4320
  errorHandler && errorHandler(e);
4241
4321
  }
4242
4322
  };
4243
4323
 
4324
+ // Helper functions to check character types
4325
+ function isSelectorStartChar(char) {
4326
+ return '.:#&*['.indexOf(char) !== -1;
4327
+ }
4328
+
4329
+ function isWhitespaceChar(char) {
4330
+ return ' \t\n\r'.indexOf(char) !== -1;
4331
+ }
4332
+
4244
4333
  var endingIndex = token.length - 1;
4334
+ var initialEndingIndex = endingIndex;
4245
4335
 
4246
4336
  for (var character; (character = token.charAt(i)); i++) {
4247
4337
  if (i === endingIndex) {
@@ -4251,8 +4341,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4251
4341
  case "layerBlock":
4252
4342
  if (character !== ";") {
4253
4343
  token += ";";
4254
- break;
4344
+ endingIndex += 1;
4255
4345
  }
4346
+ break;
4256
4347
  case "value":
4257
4348
  if (character !== "}") {
4258
4349
  if (character === ";") {
@@ -4280,7 +4371,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4280
4371
  }
4281
4372
  }
4282
4373
  }
4283
-
4374
+
4284
4375
  // Handle escape sequences before processing special characters
4285
4376
  // If we encounter a backslash, add both the backslash and the next character to buffer
4286
4377
  // and skip the next iteration to prevent the escaped character from being interpreted
@@ -4289,813 +4380,991 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
4289
4380
  i++; // Skip the next character
4290
4381
  continue;
4291
4382
  }
4292
-
4293
- switch (character) {
4294
4383
 
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;
4384
+ switch (character) {
4304
4385
 
4305
- // String
4306
- case '"':
4307
- index = i + 1;
4308
- do {
4309
- index = token.indexOf('"', index) + 1;
4310
- if (!index) {
4311
- parseError('Unmatched "');
4386
+ case " ":
4387
+ case "\t":
4388
+ case "\r":
4389
+ case "\n":
4390
+ case "\f":
4391
+ if (SIGNIFICANT_WHITESPACE[state]) {
4392
+ buffer += character;
4312
4393
  }
4313
- } while (token[index - 2] === '\\');
4314
- if (index === 0) {
4315
4394
  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 += ';'
4395
+
4396
+ // String
4397
+ case '"':
4398
+ index = i + 1;
4399
+ do {
4400
+ index = token.indexOf('"', index) + 1;
4401
+ if (!index) {
4402
+ parseError('Unmatched "');
4327
4403
  }
4404
+ } while (token[index - 2] === '\\');
4405
+ if (index === 0) {
4328
4406
  break;
4329
- case 'namespaceRule-begin':
4330
- state = 'namespaceRule';
4331
- if (i === endingIndex) {
4332
- token += ';'
4407
+ }
4408
+ buffer += token.slice(i, index);
4409
+ i = index - 1;
4410
+ switch (state) {
4411
+ case 'before-value':
4412
+ state = 'value';
4413
+ break;
4414
+ case 'importRule-begin':
4415
+ state = 'importRule';
4416
+ if (i === endingIndex) {
4417
+ token += ';'
4418
+ }
4419
+ break;
4420
+ case 'namespaceRule-begin':
4421
+ state = 'namespaceRule';
4422
+ if (i === endingIndex) {
4423
+ token += ';'
4424
+ }
4425
+ break;
4426
+ }
4427
+ break;
4428
+
4429
+ case "'":
4430
+ index = i + 1;
4431
+ do {
4432
+ index = token.indexOf("'", index) + 1;
4433
+ if (!index) {
4434
+ parseError("Unmatched '");
4333
4435
  }
4436
+ } while (token[index - 2] === '\\');
4437
+ if (index === 0) {
4334
4438
  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
4439
  }
4345
- } while (token[index - 2] === '\\');
4346
- if (index === 0) {
4440
+ buffer += token.slice(i, index);
4441
+ i = index - 1;
4442
+ switch (state) {
4443
+ case 'before-value':
4444
+ state = 'value';
4445
+ break;
4446
+ case 'importRule-begin':
4447
+ state = 'importRule';
4448
+ break;
4449
+ case 'namespaceRule-begin':
4450
+ state = 'namespaceRule';
4451
+ break;
4452
+ }
4347
4453
  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
4454
 
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 = "";
4455
+ // Comment
4456
+ case "/":
4457
+ if (token.charAt(i + 1) === "*") {
4458
+ i += 2;
4459
+ index = token.indexOf("*/", i);
4460
+ if (index === -1) {
4461
+ i = token.length - 1;
4462
+ buffer = "";
4463
+ } else {
4464
+ i = index + 1;
4465
+ }
4372
4466
  } else {
4373
- i = index + 1;
4467
+ buffer += character;
4374
4468
  }
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);
4469
+ if (state === "importRule-begin") {
4470
+ buffer += " ";
4471
+ state = "importRule";
4393
4472
  }
4394
- if (nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
4395
- styleRule = nestedSelectorRule.parentRule;
4473
+ if (state === "namespaceRule-begin") {
4474
+ buffer += " ";
4475
+ state = "namespaceRule";
4396
4476
  }
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
4477
  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;
4478
+
4479
+ // At-rule
4480
+ case "@":
4481
+ if (nestedSelectorRule) {
4482
+ if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
4483
+ currentScope.cssRules.push(styleRule);
4484
+ }
4485
+ if (nestedSelectorRule.parentRule && nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
4486
+ styleRule = nestedSelectorRule.parentRule;
4487
+ }
4488
+ // Don't reset nestedSelectorRule here - preserve it through @-rules
4489
+ }
4490
+ if (token.indexOf("@-moz-document", i) === i) {
4491
+ validateAtRule("@-moz-document", function () {
4492
+ state = "documentRule-begin";
4493
+ documentRule = new CSSOM.CSSDocumentRule();
4494
+ documentRule.__starts = i;
4495
+ i += "-moz-document".length;
4496
+ });
4523
4497
  buffer = "";
4524
4498
  break;
4525
- } else if (state === "selector") {
4526
- state = "atRule";
4527
- }
4528
- }
4529
- buffer += character;
4530
- break;
4499
+ } else if (token.indexOf("@media", i) === i) {
4500
+ validateAtRule("@media", function () {
4501
+ state = "atBlock";
4502
+ mediaRule = new CSSOM.CSSMediaRule();
4503
+ mediaRule.__starts = i;
4504
+ i += "media".length;
4505
+ });
4506
+ buffer = "";
4507
+ break;
4508
+ } else if (token.indexOf("@container", i) === i) {
4509
+ validateAtRule("@container", function () {
4510
+ state = "containerBlock";
4511
+ containerRule = new CSSOM.CSSContainerRule();
4512
+ containerRule.__starts = i;
4513
+ i += "container".length;
4514
+ });
4515
+ buffer = "";
4516
+ break;
4517
+ } else if (token.indexOf("@counter-style", i) === i) {
4518
+ validateAtRule("@counter-style", function () {
4519
+ state = "counterStyleBlock"
4520
+ counterStyleRule = new CSSOM.CSSCounterStyleRule();
4521
+ counterStyleRule.__starts = i;
4522
+ i += "counter-style".length;
4523
+ }, true);
4524
+ buffer = "";
4525
+ break;
4526
+ } else if (token.indexOf("@scope", i) === i) {
4527
+ validateAtRule("@scope", function () {
4528
+ state = "scopeBlock";
4529
+ scopeRule = new CSSOM.CSSScopeRule();
4530
+ scopeRule.__starts = i;
4531
+ i += "scope".length;
4532
+ });
4533
+ buffer = "";
4534
+ break;
4535
+ } else if (token.indexOf("@layer", i) === i) {
4536
+ validateAtRule("@layer", function () {
4537
+ state = "layerBlock"
4538
+ layerBlockRule = new CSSOM.CSSLayerBlockRule();
4539
+ layerBlockRule.__starts = i;
4540
+ i += "layer".length;
4541
+ });
4542
+ buffer = "";
4543
+ break;
4544
+ } else if (token.indexOf("@page", i) === i) {
4545
+ validateAtRule("@page", function () {
4546
+ state = "pageBlock"
4547
+ pageRule = new CSSOM.CSSPageRule();
4548
+ pageRule.__starts = i;
4549
+ i += "page".length;
4550
+ });
4551
+ buffer = "";
4552
+ break;
4553
+ } else if (token.indexOf("@supports", i) === i) {
4554
+ validateAtRule("@supports", function () {
4555
+ state = "conditionBlock";
4556
+ supportsRule = new CSSOM.CSSSupportsRule();
4557
+ supportsRule.__starts = i;
4558
+ i += "supports".length;
4559
+ });
4560
+ buffer = "";
4561
+ break;
4562
+ } else if (token.indexOf("@host", i) === i) {
4563
+ validateAtRule("@host", function () {
4564
+ state = "hostRule-begin";
4565
+ i += "host".length;
4566
+ hostRule = new CSSOM.CSSHostRule();
4567
+ hostRule.__starts = i;
4568
+ });
4569
+ buffer = "";
4570
+ break;
4571
+ } else if (token.indexOf("@starting-style", i) === i) {
4572
+ validateAtRule("@starting-style", function () {
4573
+ state = "startingStyleRule-begin";
4574
+ i += "starting-style".length;
4575
+ startingStyleRule = new CSSOM.CSSStartingStyleRule();
4576
+ startingStyleRule.__starts = i;
4577
+ });
4578
+ buffer = "";
4579
+ break;
4580
+ } else if (token.indexOf("@import", i) === i) {
4581
+ buffer = "";
4582
+ validateAtRule("@import", function () {
4583
+ state = "importRule-begin";
4584
+ i += "import".length;
4585
+ buffer += "@import";
4586
+ }, true);
4587
+ break;
4588
+ } else if (token.indexOf("@namespace", i) === i) {
4589
+ buffer = "";
4590
+ validateAtRule("@namespace", function () {
4591
+ state = "namespaceRule-begin";
4592
+ i += "namespace".length;
4593
+ buffer += "@namespace";
4594
+ }, true);
4595
+ break;
4596
+ } else if (token.indexOf("@font-face", i) === i) {
4597
+ buffer = "";
4598
+ // @font-face can be nested only inside CSSScopeRule or CSSConditionRule
4599
+ // and only if there's no CSSStyleRule in the parent chain
4600
+ var cannotBeNested = true;
4601
+ if (currentScope !== topScope) {
4602
+ var hasStyleRuleInChain = false;
4603
+ var hasValidParent = false;
4604
+
4605
+ // Check currentScope
4606
+ if (currentScope.constructor.name === 'CSSStyleRule') {
4607
+ hasStyleRuleInChain = true;
4608
+ } else if (currentScope instanceof CSSOM.CSSScopeRule || currentScope instanceof CSSOM.CSSConditionRule) {
4609
+ hasValidParent = true;
4610
+ }
4531
4611
 
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;
4612
+ // Check ancestorRules for CSSStyleRule
4613
+ if (!hasStyleRuleInChain) {
4614
+ for (var j = 0; j < ancestorRules.length; j++) {
4615
+ if (ancestorRules[j].constructor.name === 'CSSStyleRule') {
4616
+ hasStyleRuleInChain = true;
4617
+ break;
4618
+ }
4619
+ if (ancestorRules[j] instanceof CSSOM.CSSScopeRule || ancestorRules[j] instanceof CSSOM.CSSConditionRule) {
4620
+ hasValidParent = true;
4621
+ }
4622
+ }
4623
+ }
4624
+
4625
+ // Allow nesting if we have a valid parent and no style rule in the chain
4626
+ if (hasValidParent && !hasStyleRuleInChain) {
4627
+ cannotBeNested = false;
4628
+ }
4629
+ }
4630
+ validateAtRule("@font-face", function () {
4631
+ state = "fontFaceRule-begin";
4632
+ i += "font-face".length;
4633
+ fontFaceRule = new CSSOM.CSSFontFaceRule();
4634
+ fontFaceRule.__starts = i;
4635
+ }, cannotBeNested);
4636
+ break;
4637
+ } else {
4638
+ atKeyframesRegExp.lastIndex = i;
4639
+ var matchKeyframes = atKeyframesRegExp.exec(token);
4640
+ if (matchKeyframes && matchKeyframes.index === i) {
4641
+ state = "keyframesRule-begin";
4642
+ keyframesRule = new CSSOM.CSSKeyframesRule();
4643
+ keyframesRule.__starts = i;
4644
+ keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
4645
+ i += matchKeyframes[0].length - 1;
4546
4646
  buffer = "";
4547
- state = "before-selector";
4548
- i += ruleClosingMatch.index + ruleClosingMatch[0].length;
4549
4647
  break;
4648
+ } else if (state === "selector") {
4649
+ state = "atRule";
4550
4650
  }
4551
4651
  }
4652
+ buffer += character;
4653
+ break;
4552
4654
 
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;
4655
+ case "{":
4656
+ if (currentScope === topScope) {
4657
+ nestedSelectorRule = null;
4610
4658
  }
4611
- if (parsedScopePrelude.hasEnd) {
4612
- scopeRule.__end = parsedScopePrelude.endSelector;
4613
- }
4614
- if (parsedScopePrelude.hasOnlyEnd) {
4615
- scopeRule.__end = parsedScopePrelude.endSelector;
4659
+ if (state === 'before-selector') {
4660
+ parseError("Unexpected {");
4661
+ i = ignoreBalancedBlock(i, token.slice(i));
4662
+ break;
4616
4663
  }
4664
+ if (state === "selector" || state === "atRule") {
4665
+ if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
4666
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4667
+ if (ruleClosingMatch) {
4668
+ styleRule = null;
4669
+ buffer = "";
4670
+ state = "before-selector";
4671
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length;
4672
+ break;
4673
+ }
4674
+ }
4617
4675
 
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();
4676
+ // Ensure styleRule exists before trying to set properties on it
4677
+ if (!styleRule) {
4678
+ styleRule = new CSSOM.CSSStyleRule();
4679
+ styleRule.__starts = i;
4680
+ }
4628
4681
 
4629
- var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4682
+ var originalParentRule = parentRule;
4630
4683
 
4631
- if (isValidName) {
4632
4684
  if (parentRule) {
4633
- layerBlockRule.__parentRule = parentRule;
4685
+ styleRule.__parentRule = parentRule;
4634
4686
  pushToAncestorRules(parentRule);
4635
4687
  }
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
4688
 
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
- }
4689
+ currentScope = parentRule = styleRule;
4690
+ var processedSelectorText = processSelectorText(buffer.trim());
4691
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4692
+ if (originalParentRule && originalParentRule.constructor.name === "CSSStyleRule") {
4693
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4694
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4695
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4696
+ }).join(', ');
4697
+ } else {
4698
+ styleRule.selectorText = processedSelectorText;
4699
+ }
4700
+ styleRule.style.__starts = i;
4701
+ styleRule.__parentStyleSheet = styleSheet;
4702
+ buffer = "";
4703
+ state = "before-name";
4704
+ } else if (state === "atBlock") {
4705
+ mediaRule.media.mediaText = buffer.trim();
4659
4706
 
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
- }
4707
+ if (parentRule) {
4708
+ mediaRule.__parentRule = parentRule;
4709
+ pushToAncestorRules(parentRule);
4710
+ // If entering @media from within a CSSStyleRule, set nestedSelectorRule
4711
+ // so that & selectors and declarations work correctly inside
4712
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4713
+ nestedSelectorRule = parentRule;
4714
+ }
4715
+ }
4669
4716
 
4670
- currentScope = parentRule = startingStyleRule;
4671
- startingStyleRule.__parentStyleSheet = styleSheet;
4672
- buffer = "";
4673
- state = "before-selector";
4717
+ currentScope = parentRule = mediaRule;
4718
+ pushToAncestorRules(mediaRule);
4719
+ mediaRule.__parentStyleSheet = styleSheet;
4720
+ styleRule = null; // Reset styleRule when entering @-rule
4721
+ buffer = "";
4722
+ state = "before-selector";
4723
+ } else if (state === "containerBlock") {
4724
+ containerRule.__conditionText = buffer.trim();
4674
4725
 
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
4726
+ if (parentRule) {
4727
+ containerRule.__parentRule = parentRule;
4719
4728
  pushToAncestorRules(parentRule);
4729
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4730
+ nestedSelectorRule = parentRule;
4731
+ }
4720
4732
  }
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;
4733
+ currentScope = parentRule = containerRule;
4734
+ pushToAncestorRules(containerRule);
4735
+ containerRule.__parentStyleSheet = styleSheet;
4736
+ styleRule = null; // Reset styleRule when entering @-rule
4737
+ buffer = "";
4738
+ state = "before-selector";
4739
+ } else if (state === "counterStyleBlock") {
4740
+ var counterStyleName = buffer.trim().replace(/\n/g, "");
4741
+ // Validate: name cannot be empty, contain whitespace, or contain dots
4742
+ var isValidCounterStyleName = counterStyleName.length > 0 && !/[\s.]/.test(counterStyleName);
4743
+
4744
+ if (isValidCounterStyleName) {
4745
+ counterStyleRule.name = counterStyleName;
4746
+ currentScope = parentRule = counterStyleRule;
4747
+ counterStyleRule.__parentStyleSheet = styleSheet;
4748
+ }
4749
+ buffer = "";
4750
+ } else if (state === "conditionBlock") {
4751
+ supportsRule.__conditionText = buffer.trim();
4741
4752
 
4742
- buffer = "";
4743
- state = "before-name";
4744
- }
4745
- break;
4753
+ if (parentRule) {
4754
+ supportsRule.__parentRule = parentRule;
4755
+ pushToAncestorRules(parentRule);
4756
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4757
+ nestedSelectorRule = parentRule;
4758
+ }
4759
+ }
4746
4760
 
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();
4761
+ currentScope = parentRule = supportsRule;
4762
+ pushToAncestorRules(supportsRule);
4763
+ supportsRule.__parentStyleSheet = styleSheet;
4764
+ styleRule = null; // Reset styleRule when entering @-rule
4758
4765
  buffer = "";
4759
- state = "before-value";
4760
- }
4761
- } else {
4762
- buffer += character;
4763
- }
4764
- break;
4766
+ state = "before-selector";
4767
+ } else if (state === "scopeBlock") {
4768
+ var parsedScopePrelude = parseScopePrelude(buffer.trim());
4765
4769
 
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();
4770
+ if (parsedScopePrelude.hasStart) {
4771
+ scopeRule.__start = parsedScopePrelude.startSelector;
4772
+ }
4773
+ if (parsedScopePrelude.hasEnd) {
4774
+ scopeRule.__end = parsedScopePrelude.endSelector;
4775
+ }
4776
+ if (parsedScopePrelude.hasOnlyEnd) {
4777
+ scopeRule.__end = parsedScopePrelude.endSelector;
4778
+ }
4771
4779
 
4772
- if (info.error) {
4773
- parseError(info.error);
4774
- } else {
4775
- buffer += info.expression;
4776
- i = info.idx;
4780
+ if (parentRule) {
4781
+ scopeRule.__parentRule = parentRule;
4782
+ pushToAncestorRules(parentRule);
4783
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4784
+ nestedSelectorRule = parentRule;
4785
+ }
4777
4786
  }
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;
4787
+ currentScope = parentRule = scopeRule;
4788
+ pushToAncestorRules(scopeRule);
4789
+ scopeRule.__parentStyleSheet = styleSheet;
4790
+ styleRule = null; // Reset styleRule when entering @-rule
4791
+ buffer = "";
4792
+ state = "before-selector";
4793
+ } else if (state === "layerBlock") {
4794
+ layerBlockRule.name = buffer.trim();
4792
4795
 
4793
- case ")":
4794
- if (state === 'value-parenthesis') {
4795
- valueParenthesisDepth--;
4796
- if (valueParenthesisDepth === 0) state = 'value';
4797
- }
4798
- buffer += character;
4799
- break;
4796
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4800
4797
 
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;
4798
+ if (isValidName) {
4799
+ if (parentRule) {
4800
+ layerBlockRule.__parentRule = parentRule;
4801
+ pushToAncestorRules(parentRule);
4802
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4803
+ nestedSelectorRule = parentRule;
4804
+ }
4805
+ }
4809
4806
 
4810
- case ";":
4811
- switch (state) {
4812
- case "before-value":
4813
- case "before-name":
4814
- parseError("Unexpected ;");
4807
+ currentScope = parentRule = layerBlockRule;
4808
+ pushToAncestorRules(layerBlockRule);
4809
+ layerBlockRule.__parentStyleSheet = styleSheet;
4810
+ }
4811
+ styleRule = null; // Reset styleRule when entering @-rule
4815
4812
  buffer = "";
4816
- state = "before-name";
4817
- break;
4818
- case "value":
4819
- styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4820
- priority = "";
4813
+ state = "before-selector";
4814
+ } else if (state === "pageBlock") {
4815
+ pageRule.selectorText = buffer.trim();
4816
+
4817
+ if (parentRule) {
4818
+ pageRule.__parentRule = parentRule;
4819
+ pushToAncestorRules(parentRule);
4820
+ }
4821
+
4822
+ currentScope = parentRule = pageRule;
4823
+ pageRule.__parentStyleSheet = styleSheet;
4824
+ styleRule = pageRule;
4821
4825
  buffer = "";
4822
4826
  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);
4827
+ } else if (state === "hostRule-begin") {
4828
+ if (parentRule) {
4829
+ pushToAncestorRules(parentRule);
4837
4830
  }
4831
+
4832
+ currentScope = parentRule = hostRule;
4833
+ pushToAncestorRules(hostRule);
4834
+ hostRule.__parentStyleSheet = styleSheet;
4838
4835
  buffer = "";
4839
4836
  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);
4837
+ } else if (state === "startingStyleRule-begin") {
4838
+ if (parentRule) {
4839
+ startingStyleRule.__parentRule = parentRule;
4840
+ pushToAncestorRules(parentRule);
4841
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4842
+ nestedSelectorRule = parentRule;
4861
4843
  }
4862
4844
  }
4845
+
4846
+ currentScope = parentRule = startingStyleRule;
4847
+ pushToAncestorRules(startingStyleRule);
4848
+ startingStyleRule.__parentStyleSheet = styleSheet;
4849
+ styleRule = null; // Reset styleRule when entering @-rule
4863
4850
  buffer = "";
4864
4851
  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
4852
 
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);
4853
+ } else if (state === "fontFaceRule-begin") {
4854
+ if (parentRule) {
4855
+ fontFaceRule.__parentRule = parentRule;
4881
4856
  }
4857
+ fontFaceRule.__parentStyleSheet = styleSheet;
4858
+ styleRule = fontFaceRule;
4882
4859
  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()
4860
+ state = "before-name";
4861
+ } else if (state === "keyframesRule-begin") {
4862
+ keyframesRule.name = buffer.trim();
4863
+ if (parentRule) {
4864
+ pushToAncestorRules(parentRule);
4865
+ keyframesRule.__parentRule = parentRule;
4913
4866
  }
4914
-
4867
+ keyframesRule.__parentStyleSheet = styleSheet;
4868
+ currentScope = parentRule = keyframesRule;
4869
+ buffer = "";
4870
+ state = "keyframeRule-begin";
4871
+ } else if (state === "keyframeRule-begin") {
4872
+ styleRule = new CSSOM.CSSKeyframeRule();
4873
+ styleRule.keyText = buffer.trim();
4874
+ styleRule.__starts = i;
4875
+ buffer = "";
4876
+ state = "before-name";
4877
+ } else if (state === "documentRule-begin") {
4878
+ // FIXME: what if this '{' is in the url text of the match function?
4879
+ documentRule.matcher.matcherText = buffer.trim();
4915
4880
  if (parentRule) {
4916
- styleRule.__parentRule = parentRule;
4881
+ pushToAncestorRules(parentRule);
4882
+ documentRule.__parentRule = parentRule;
4917
4883
  }
4918
- styleRule.__parentStyleSheet = styleSheet;
4919
-
4920
- if (currentScope === styleRule) {
4921
- currentScope = parentRule || topScope;
4884
+ currentScope = parentRule = documentRule;
4885
+ pushToAncestorRules(documentRule);
4886
+ documentRule.__parentStyleSheet = styleSheet;
4887
+ buffer = "";
4888
+ state = "before-selector";
4889
+ } else if (state === "before-name" || state === "name") {
4890
+ // @font-face and similar rules don't support nested selectors
4891
+ // If we encounter a nested selector block inside them, skip it
4892
+ if (styleRule.constructor.name === "CSSFontFaceRule" ||
4893
+ styleRule.constructor.name === "CSSKeyframeRule" ||
4894
+ (styleRule.constructor.name === "CSSPageRule" && parentRule === styleRule)) {
4895
+ // Skip the nested block
4896
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4897
+ if (ruleClosingMatch) {
4898
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
4899
+ buffer = "";
4900
+ state = "before-name";
4901
+ break;
4902
+ }
4922
4903
  }
4923
4904
 
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);
4905
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
4906
+ if (styleRule.style.length) {
4907
+ parentRule.cssRules.push(styleRule);
4908
+ styleRule.__parentRule = parentRule;
4909
+ styleRule.__parentStyleSheet = styleSheet;
4910
+ pushToAncestorRules(parentRule);
4932
4911
  } else {
4933
- currentScope.cssRules.push(styleRule);
4912
+ // If the styleRule is empty, we can assume that it's a nested selector
4913
+ pushToAncestorRules(parentRule);
4934
4914
  }
4915
+ } else {
4916
+ currentScope = parentRule = styleRule;
4917
+ pushToAncestorRules(parentRule);
4918
+ styleRule.__parentStyleSheet = styleSheet;
4935
4919
  }
4920
+
4921
+ styleRule = new CSSOM.CSSStyleRule();
4922
+ var processedSelectorText = processSelectorText(buffer.trim());
4923
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4924
+ if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
4925
+ styleRule.selectorText = processedSelectorText;
4926
+ } else {
4927
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4928
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4929
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4930
+ }).join(', ');
4931
+ }
4932
+ styleRule.style.__starts = i - buffer.length;
4933
+ styleRule.__parentRule = parentRule;
4934
+ // Only set nestedSelectorRule if we're directly inside a CSSStyleRule or CSSScopeRule,
4935
+ // not inside other grouping rules like @media/@supports
4936
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
4937
+ nestedSelectorRule = styleRule;
4938
+ }
4939
+
4936
4940
  buffer = "";
4937
- if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
4938
- state = "keyframeRule-begin";
4941
+ state = "before-name";
4942
+ }
4943
+ break;
4944
+
4945
+ case ":":
4946
+ if (state === "name") {
4947
+ // It can be a nested selector, let's check
4948
+ var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
4949
+ var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
4950
+ if (hasOpenBraceBefore) {
4951
+ // Is a selector
4952
+ buffer += character;
4939
4953
  } else {
4940
- state = "before-selector";
4954
+ // Is a declaration
4955
+ name = buffer.trim();
4956
+ buffer = "";
4957
+ state = "before-value";
4941
4958
  }
4959
+ } else {
4960
+ buffer += character;
4961
+ }
4962
+ break;
4942
4963
 
4943
- if (styleRule.constructor.name === "CSSNestedDeclarations") {
4944
- if (currentScope !== topScope) {
4945
- nestedSelectorRule = currentScope;
4964
+ case "(":
4965
+ if (state === 'value') {
4966
+ // ie css expression mode
4967
+ if (buffer.trim() === 'expression') {
4968
+ var info = (new CSSOM.CSSValueExpression(token, i)).parse();
4969
+
4970
+ if (info.error) {
4971
+ parseError(info.error);
4972
+ } else {
4973
+ buffer += info.expression;
4974
+ i = info.idx;
4946
4975
  }
4947
- styleRule = null;
4948
4976
  } else {
4949
- styleRule = null;
4950
- break;
4977
+ state = 'value-parenthesis';
4978
+ //always ensure this is reset to 1 on transition
4979
+ //from value to value-parenthesis
4980
+ valueParenthesisDepth = 1;
4981
+ buffer += character;
4951
4982
  }
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);
4983
+ } else if (state === 'value-parenthesis') {
4984
+ valueParenthesisDepth++;
4985
+ buffer += character;
4986
+ } else {
4987
+ buffer += character;
4988
+ }
4989
+ break;
4990
+
4991
+ case ")":
4992
+ if (state === 'value-parenthesis') {
4993
+ valueParenthesisDepth--;
4994
+ if (valueParenthesisDepth === 0) state = 'value';
4995
+ }
4996
+ buffer += character;
4997
+ break;
4998
+
4999
+ case "!":
5000
+ if (state === "value" && token.indexOf("!important", i) === i) {
5001
+ priority = "important";
5002
+ i += "important".length;
5003
+ } else {
5004
+ buffer += character;
5005
+ }
5006
+ break;
5007
+
5008
+ case ";":
5009
+ switch (state) {
5010
+ case "before-value":
5011
+ case "before-name":
5012
+ parseError("Unexpected ;");
5013
+ buffer = "";
5014
+ state = "before-name";
5015
+ break;
5016
+ case "value":
5017
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
5018
+ priority = "";
5019
+ buffer = "";
5020
+ state = "before-name";
5021
+ break;
5022
+ case "atRule":
5023
+ buffer = "";
5024
+ state = "before-selector";
5025
+ break;
5026
+ case "importRule":
5027
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
5028
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
5029
+ });
5030
+ if (isValid) {
5031
+ importRule = new CSSOM.CSSImportRule();
5032
+ importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
5033
+ importRule.parse(buffer + character);
5034
+ topScope.cssRules.push(importRule);
4962
5035
  }
4963
-
5036
+ buffer = "";
5037
+ state = "before-selector";
4964
5038
  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;
5039
+ case "namespaceRule":
5040
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
5041
+ return ['CSSImportRule', 'CSSLayerStatementRule', 'CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
5042
+ });
5043
+ if (isValid) {
5044
+ try {
5045
+ // Validate namespace syntax before creating the rule
5046
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
5047
+ testNamespaceRule.parse(buffer + character);
5048
+
5049
+ namespaceRule = testNamespaceRule;
5050
+ namespaceRule.__parentStyleSheet = styleSheet;
5051
+ topScope.cssRules.push(namespaceRule);
5052
+
5053
+ // Track the namespace prefix for validation
5054
+ if (namespaceRule.prefix) {
5055
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
5056
+ }
5057
+ } catch (e) {
5058
+ parseError(e.message);
5059
+ }
5060
+ }
5061
+ buffer = "";
5062
+ state = "before-selector";
5063
+ break;
5064
+ case "layerBlock":
5065
+ var nameListStr = buffer.trim().split(",").map(function (name) {
5066
+ return name.trim();
5067
+ });
5068
+ var isInvalid = nameListStr.some(function (name) {
5069
+ return name.trim().match(cssCustomIdentifierRegExp) === null;
5070
+ });
5071
+
5072
+ // Check if there's a CSSStyleRule in the parent chain
5073
+ var hasStyleRuleParent = false;
5074
+ if (parentRule) {
5075
+ var checkParent = parentRule;
5076
+ while (checkParent) {
5077
+ if (checkParent.constructor.name === "CSSStyleRule") {
5078
+ hasStyleRuleParent = true;
5079
+ break;
4989
5080
  }
5081
+ checkParent = checkParent.__parentRule;
5082
+ }
5083
+ }
5084
+
5085
+ if (!isInvalid && !hasStyleRuleParent) {
5086
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
5087
+ layerStatementRule.__parentStyleSheet = styleSheet;
5088
+ layerStatementRule.__starts = layerBlockRule.__starts;
5089
+ layerStatementRule.__ends = i;
5090
+ layerStatementRule.nameList = nameListStr;
5091
+
5092
+ // Add to parent rule if nested, otherwise to top scope
5093
+ if (parentRule) {
5094
+ layerStatementRule.__parentRule = parentRule;
5095
+ parentRule.cssRules.push(layerStatementRule);
4990
5096
  } else {
4991
- prevScope = currentScope;
4992
- parentRule !== prevScope && parentRule.cssRules.push(prevScope);
4993
- break;
5097
+ topScope.cssRules.push(layerStatementRule);
4994
5098
  }
4995
5099
  }
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);
5100
+ buffer = "";
5101
+ state = "before-selector";
5102
+ break;
5103
+ default:
5104
+ buffer += character;
5105
+ break;
5106
+ }
5107
+ break;
5108
+
5109
+ case "}":
5110
+ if (state === "counterStyleBlock") {
5111
+ // FIXME : Implement missing properties on CSSCounterStyleRule interface and update parse method
5112
+ // For now it's just assigning entire rule text
5113
+ counterStyleRule.parse("@counter-style " + counterStyleRule.name + " { " + buffer + " }");
5114
+ buffer = "";
5115
+ state = "before-selector";
5116
+ }
5117
+
5118
+ switch (state) {
5119
+ case "value":
5120
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
5121
+ priority = "";
5122
+ /* falls through */
5123
+ case "before-value":
5124
+ case "before-name":
5125
+ case "name":
5126
+ styleRule.__ends = i + 1;
5127
+
5128
+ if (parentRule === styleRule) {
5129
+ parentRule = ancestorRules.pop()
5130
+ }
5131
+
5132
+ if (parentRule) {
5133
+ styleRule.__parentRule = parentRule;
5004
5134
  }
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;
5135
+ styleRule.__parentStyleSheet = styleSheet;
5136
+
5137
+ if (currentScope === styleRule) {
5138
+ currentScope = parentRule || topScope;
5139
+ }
5140
+
5141
+ if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
5142
+ if (styleRule === nestedSelectorRule) {
5017
5143
  nestedSelectorRule = null;
5018
- parentRule = null;
5019
5144
  }
5145
+ parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
5146
+ } else {
5147
+ if (styleRule.parentRule) {
5148
+ styleRule.parentRule.cssRules.push(styleRule);
5149
+ } else {
5150
+ currentScope.cssRules.push(styleRule);
5151
+ }
5152
+ }
5153
+ buffer = "";
5154
+ if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
5155
+ state = "keyframeRule-begin";
5020
5156
  } else {
5021
- parentRule = null;
5157
+ state = "before-selector";
5158
+ }
5022
5159
 
5160
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
5161
+ if (currentScope !== topScope) {
5162
+ // Only set nestedSelectorRule if currentScope is CSSStyleRule or CSSScopeRule
5163
+ // Not for other grouping rules like @media/@supports
5164
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5165
+ nestedSelectorRule = currentScope;
5166
+ }
5167
+ }
5168
+ styleRule = null;
5169
+ } else {
5170
+ // Update nestedSelectorRule when closing a CSSStyleRule
5171
+ if (styleRule === nestedSelectorRule) {
5172
+ var selector = styleRule.selectorText && styleRule.selectorText.trim();
5173
+ // Check if this is proper nesting (&.class, &:pseudo) vs prepended & (& :is, & .class with space)
5174
+ // Prepended & has pattern "& X" where X starts with : or .
5175
+ var isPrependedAmpersand = selector && selector.match(/^&\s+[:\.]/);
5176
+
5177
+ // Check if parent is a grouping rule that can contain nested selectors
5178
+ var isGroupingRule = currentScope && currentScope instanceof CSSOM.CSSGroupingRule;
5179
+
5180
+ if (!isPrependedAmpersand && isGroupingRule) {
5181
+ // Proper nesting - set nestedSelectorRule to parent for more nested selectors
5182
+ // But only if it's a CSSStyleRule or CSSScopeRule, not other grouping rules like @media
5183
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5184
+ nestedSelectorRule = currentScope;
5185
+ }
5186
+ // If currentScope is another type of grouping rule (like @media), keep nestedSelectorRule unchanged
5187
+ } else {
5188
+ // Prepended & or not nested in grouping rule - reset to prevent CSSNestedDeclarations
5189
+ nestedSelectorRule = null;
5190
+ }
5191
+ } else if (nestedSelectorRule && currentScope instanceof CSSOM.CSSGroupingRule) {
5192
+ // When closing a nested rule that's not the nestedSelectorRule itself,
5193
+ // maintain nestedSelectorRule if we're still inside a grouping rule
5194
+ // This ensures declarations after nested selectors inside @media/@supports etc. work correctly
5195
+ }
5196
+ styleRule = null;
5197
+ break;
5023
5198
  }
5024
- } else {
5025
- currentScope = parentRule;
5026
- }
5199
+ case "keyframeRule-begin":
5200
+ case "before-selector":
5201
+ case "selector":
5202
+ // End of media/supports/document rule.
5203
+ if (!parentRule) {
5204
+ parseError("Unexpected }");
5205
+
5206
+ var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
5207
+ if (hasPreviousStyleRule) {
5208
+ i = ignoreBalancedBlock(i, token.slice(i), 1);
5209
+ }
5027
5210
 
5028
- buffer = "";
5029
- state = "before-selector";
5030
- break;
5031
- }
5032
- break;
5211
+ break;
5212
+ }
5033
5213
 
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;
5214
+ while (ancestorRules.length > 0) {
5215
+ parentRule = ancestorRules.pop();
5216
+
5217
+ if (parentRule instanceof CSSOM.CSSGroupingRule && (parentRule.constructor.name !== 'CSSStyleRule' || parentRule.__parentRule)) {
5218
+ if (nestedSelectorRule) {
5219
+ if (nestedSelectorRule.parentRule) {
5220
+ prevScope = nestedSelectorRule;
5221
+ currentScope = nestedSelectorRule.parentRule;
5222
+ if (currentScope.cssRules.findIndex(function (rule) {
5223
+ return rule === prevScope
5224
+ }) === -1) {
5225
+ currentScope.cssRules.push(prevScope);
5226
+ }
5227
+ nestedSelectorRule = currentScope;
5228
+ } else {
5229
+ // If nestedSelectorRule doesn't have a parentRule, we're closing a grouping rule
5230
+ // inside a top-level CSSStyleRule. We need to push currentScope to the parentRule.
5231
+ prevScope = currentScope;
5232
+ // Push to actual parent from ancestorRules if available
5233
+ var actualParent = ancestorRules.length > 0 ? ancestorRules[ancestorRules.length - 1] : nestedSelectorRule;
5234
+ actualParent !== prevScope && actualParent.cssRules.push(prevScope);
5235
+ // Update currentScope to the nestedSelectorRule before breaking
5236
+ currentScope = actualParent;
5237
+ parentRule = actualParent;
5238
+ break;
5239
+ }
5240
+ } else {
5241
+ prevScope = currentScope;
5242
+ parentRule !== prevScope && parentRule.cssRules.push(prevScope);
5243
+ break;
5244
+ }
5055
5245
  }
5056
5246
  }
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;
5247
+
5248
+ // If currentScope has a __parentRule and wasn't added yet, add it
5249
+ if (ancestorRules.length === 0 && currentScope.__parentRule && currentScope.__parentRule.cssRules) {
5250
+ if (currentScope.__parentRule.cssRules.findIndex(function (rule) {
5251
+ return rule === currentScope
5252
+ }) === -1) {
5253
+ currentScope.__parentRule.cssRules.push(currentScope);
5254
+ }
5255
+ }
5256
+
5257
+ // Only handle top-level rule closing if we processed all ancestors
5258
+ if (ancestorRules.length === 0 && currentScope.parentRule == null) {
5259
+ currentScope.__ends = i + 1;
5260
+ if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
5261
+ return rule === currentScope
5262
+ }) === -1) {
5263
+ topScope.cssRules.push(currentScope);
5264
+ }
5265
+ currentScope = topScope;
5266
+ if (nestedSelectorRule === parentRule) {
5267
+ // Check if this selector is really starting inside another selector
5268
+ var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
5269
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
5270
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
5271
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
5272
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
5273
+
5274
+ if (openingBraceLen === closingBraceLen) {
5275
+ // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
5276
+ nestedSelectorRule.__ends = i + 1;
5277
+ nestedSelectorRule = null;
5278
+ parentRule = null;
5279
+ }
5074
5280
  } else {
5281
+ parentRule = null;
5282
+ }
5283
+ } else {
5284
+ currentScope = parentRule;
5285
+ }
5286
+
5287
+ buffer = "";
5288
+ state = "before-selector";
5289
+ break;
5290
+ }
5291
+ break;
5292
+
5293
+ default:
5294
+ switch (state) {
5295
+ case "before-selector":
5296
+ state = "selector";
5297
+ if ((styleRule || scopeRule) && parentRule) {
5298
+ // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
5299
+ // If Declaration inside Nested Selector let's keep the same styleRule
5300
+ if (!isSelectorStartChar(character) && !isWhitespaceChar(character) && parentRule instanceof CSSOM.CSSGroupingRule) {
5301
+ // parentRule.__parentRule = styleRule;
5302
+ state = "before-name";
5303
+ if (styleRule !== parentRule) {
5304
+ styleRule = new CSSOM.CSSNestedDeclarations();
5305
+ styleRule.__starts = i;
5306
+ }
5307
+ }
5308
+
5309
+ } else if (nestedSelectorRule && parentRule && parentRule instanceof CSSOM.CSSGroupingRule) {
5310
+ if (isSelectorStartChar(character)) {
5311
+ // If starting with a selector character, create CSSStyleRule instead of CSSNestedDeclarations
5075
5312
  styleRule = new CSSOM.CSSStyleRule();
5076
- styleRule.__starts = i;
5313
+ styleRule.__starts = i;
5314
+ } else if (!isWhitespaceChar(character)) {
5315
+ // Starting a declaration (not whitespace, not a selector)
5316
+ state = "before-name";
5317
+ // Check if we should create CSSNestedDeclarations
5318
+ // This happens if: parent has cssRules OR nestedSelectorRule exists (indicating CSSStyleRule in hierarchy)
5319
+ if (parentRule.cssRules.length || nestedSelectorRule) {
5320
+ currentScope = parentRule;
5321
+ // Only set nestedSelectorRule if parentRule is CSSStyleRule or CSSScopeRule
5322
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
5323
+ nestedSelectorRule = parentRule;
5324
+ }
5325
+ styleRule = new CSSOM.CSSNestedDeclarations();
5326
+ styleRule.__starts = i;
5327
+ } else {
5328
+ if (parentRule.constructor.name === "CSSStyleRule") {
5329
+ styleRule = parentRule;
5330
+ } else {
5331
+ styleRule = new CSSOM.CSSStyleRule();
5332
+ styleRule.__starts = i;
5333
+ }
5334
+ }
5077
5335
  }
5078
5336
  }
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;
5337
+ break;
5338
+ case "before-name":
5339
+ state = "name";
5340
+ break;
5341
+ case "before-value":
5342
+ state = "value";
5343
+ break;
5344
+ case "importRule-begin":
5345
+ state = "importRule";
5346
+ break;
5347
+ case "namespaceRule-begin":
5348
+ state = "namespaceRule";
5349
+ break;
5350
+ }
5351
+ buffer += character;
5352
+ break;
5353
+ }
5354
+
5355
+ // Auto-close all unclosed nested structures
5356
+ // Check AFTER processing the character, at the ORIGINAL ending index
5357
+ // Only add closing braces if CSS is incomplete (not at top scope)
5358
+ if (i === initialEndingIndex && (currentScope !== topScope || ancestorRules.length > 0)) {
5359
+ var needsClosing = ancestorRules.length;
5360
+ if (currentScope !== topScope && ancestorRules.indexOf(currentScope) === -1) {
5361
+ needsClosing += 1;
5362
+ }
5363
+ // Add closing braces for all unclosed structures
5364
+ for (var closeIdx = 0; closeIdx < needsClosing; closeIdx++) {
5365
+ token += "}";
5366
+ endingIndex += 1;
5096
5367
  }
5097
- buffer += character;
5098
- break;
5099
5368
  }
5100
5369
  }
5101
5370