@acemir/cssom 0.9.23 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/CSSOM.js CHANGED
@@ -1,4 +1,18 @@
1
- var CSSOM = {};
1
+ var __globalObject = null;
2
+
3
+ var CSSOM = {
4
+ setup: function(opts) {
5
+ if (opts.globalObject) {
6
+ __globalObject = opts.globalObject;
7
+ }
8
+ },
9
+ getGlobalObject: function() {
10
+ return __globalObject;
11
+ }
12
+ };
13
+
14
+
15
+
2
16
 
3
17
 
4
18
  // Utility functions for CSSOM error handling
@@ -13,19 +27,8 @@ var CSSOM = {};
13
27
  * @return {Function} The error constructor
14
28
  */
15
29
  function getErrorConstructor(context, errorType) {
16
- // Try parentStyleSheet.__globalObject first
17
- if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
18
- return context.parentStyleSheet.__globalObject[errorType];
19
- }
20
-
21
- // Try __parentStyleSheet (alternative naming)
22
- if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
23
- return context.__parentStyleSheet.__globalObject[errorType];
24
- }
25
-
26
- // Try __globalObject on the context itself
27
- if (context.__globalObject && context.__globalObject[errorType]) {
28
- return context.__globalObject[errorType];
30
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()[errorType]) {
31
+ return CSSOM.getGlobalObject()[errorType];
29
32
  }
30
33
 
31
34
  // Fall back to native constructor
@@ -34,6 +37,19 @@ function getErrorConstructor(context, errorType) {
34
37
  eval(errorType);
35
38
  }
36
39
 
40
+ /**
41
+ * Creates an appropriate error with context-aware constructor.
42
+ *
43
+ * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
44
+ * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
45
+ * @param {string} message - The error message
46
+ * @param {string} [name] - Optional name for DOMException
47
+ */
48
+ function createError(context, errorType, message, name) {
49
+ var ErrorConstructor = getErrorConstructor(context, errorType);
50
+ return new ErrorConstructor(message, name);
51
+ }
52
+
37
53
  /**
38
54
  * Creates and throws an appropriate error with context-aware constructor.
39
55
  *
@@ -43,9 +59,7 @@ function getErrorConstructor(context, errorType) {
43
59
  * @param {string} [name] - Optional name for DOMException
44
60
  */
45
61
  function throwError(context, errorType, message, name) {
46
- var ErrorConstructor = getErrorConstructor(context, errorType);
47
- var error = new ErrorConstructor(message, name);
48
- throw error;
62
+ throw createError(context, errorType, message, name);
49
63
  }
50
64
 
51
65
  /**
@@ -98,6 +112,7 @@ function throwIndexError(context, methodName, objectName, index, maxIndex, name)
98
112
  }
99
113
 
100
114
  var errorUtils = {
115
+ createError: createError,
101
116
  getErrorConstructor: getErrorConstructor,
102
117
  throwError: throwError,
103
118
  throwMissingArguments: throwMissingArguments,
@@ -332,6 +347,16 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
332
347
  enumerable: true
333
348
  },
334
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
+
335
360
  parentRule: {
336
361
  get: function() {
337
362
  return this.__parentRule
@@ -402,9 +427,15 @@ CSSOM.CSSNestedDeclarations = function CSSNestedDeclarations() {
402
427
  this.__style.parentRule = this;
403
428
  };
404
429
 
405
- CSSOM.CSSNestedDeclarations.prototype = new CSSOM.CSSRule();
430
+ CSSOM.CSSNestedDeclarations.prototype = Object.create(CSSOM.CSSRule.prototype);
406
431
  CSSOM.CSSNestedDeclarations.prototype.constructor = CSSOM.CSSNestedDeclarations;
407
- 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
+ });
408
439
 
409
440
  Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", {
410
441
  get: function() {
@@ -422,9 +453,7 @@ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", {
422
453
  Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
423
454
  get: function () {
424
455
  return this.style.cssText;
425
- },
426
- configurable: true,
427
- enumerable: true,
456
+ }
428
457
  });
429
458
 
430
459
 
@@ -436,12 +465,19 @@ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
436
465
  */
437
466
  CSSOM.CSSGroupingRule = function CSSGroupingRule() {
438
467
  CSSOM.CSSRule.call(this);
439
- this.cssRules = new CSSOM.CSSRuleList();
468
+ this.__cssRules = new CSSOM.CSSRuleList();
440
469
  };
441
470
 
442
- CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
471
+ CSSOM.CSSGroupingRule.prototype = Object.create(CSSOM.CSSRule.prototype);
443
472
  CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
444
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
+ });
445
481
 
446
482
  /**
447
483
  * Used to insert a new CSS rule to a list of CSS rules.
@@ -473,13 +509,60 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
473
509
  if (index > this.cssRules.length) {
474
510
  errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
475
511
  }
476
-
477
- var ruleToParse = String(rule);
478
- var parsedSheet = CSSOM.parse(ruleToParse);
479
- if (parsedSheet.cssRules.length !== 1) {
480
- 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');
481
565
  }
482
- var cssRule = parsedSheet.cssRules[0];
483
566
 
484
567
  // Check for rules that cannot be inserted inside a CSSGroupingRule
485
568
  if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
@@ -523,7 +606,9 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
523
606
  if (index >= this.cssRules.length) {
524
607
  errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
525
608
  }
526
- 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);
527
612
  };
528
613
 
529
614
 
@@ -537,11 +622,45 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
537
622
  CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() {
538
623
  CSSOM.CSSRule.call(this);
539
624
  this.name = "";
625
+ this.__props = "";
540
626
  };
541
627
 
542
- CSSOM.CSSCounterStyleRule.prototype = new CSSOM.CSSRule();
628
+ CSSOM.CSSCounterStyleRule.prototype = Object.create(CSSOM.CSSRule.prototype);
543
629
  CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule;
544
- 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
+ });
545
664
 
546
665
 
547
666
 
@@ -556,9 +675,11 @@ CSSOM.CSSConditionRule = function CSSConditionRule() {
556
675
  this.__conditionText = '';
557
676
  };
558
677
 
559
- CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
678
+ CSSOM.CSSConditionRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
560
679
  CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
561
680
 
681
+ Object.setPrototypeOf(CSSOM.CSSConditionRule, CSSOM.CSSGroupingRule);
682
+
562
683
  Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
563
684
  get: function () {
564
685
  return this.__conditionText;
@@ -581,9 +702,11 @@ CSSOM.CSSStyleRule = function CSSStyleRule() {
581
702
  this.__style.parentRule = this;
582
703
  };
583
704
 
584
- CSSOM.CSSStyleRule.prototype = new CSSOM.CSSGroupingRule();
705
+ CSSOM.CSSStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
585
706
  CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
586
707
 
708
+ Object.setPrototypeOf(CSSOM.CSSStyleRule, CSSOM.CSSGroupingRule);
709
+
587
710
  Object.defineProperty(CSSOM.CSSStyleRule.prototype, "type", {
588
711
  value: 1,
589
712
  writable: false
@@ -628,12 +751,17 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
628
751
  get: function() {
629
752
  var text;
630
753
  if (this.selectorText) {
631
- var values = ""
754
+ var values = "";
632
755
  if (this.cssRules.length) {
633
756
  var valuesArr = [" {"];
634
757
  this.style.cssText && valuesArr.push(this.style.cssText);
635
- valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
636
- values = valuesArr.join("\n ") + "\n}"
758
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
759
+ if (rule.cssText !== "") {
760
+ acc.push(rule.cssText);
761
+ }
762
+ return acc;
763
+ }, []).join("\n "));
764
+ values = valuesArr.join("\n ") + "\n}";
637
765
  } else {
638
766
  values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
639
767
  }
@@ -642,163 +770,10 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
642
770
  text = "";
643
771
  }
644
772
  return text;
645
- },
646
- set: function(cssText) {
647
- if (typeof cssText === "string") {
648
- var rule = CSSOM.CSSStyleRule.parse(cssText);
649
- this.__style = rule.style;
650
- this.selectorText = rule.selectorText;
651
- }
652
773
  }
653
774
  });
654
775
 
655
776
 
656
- /**
657
- * NON-STANDARD
658
- * lightweight version of parse.js.
659
- * @param {string} ruleText
660
- * @return CSSStyleRule
661
- */
662
- CSSOM.CSSStyleRule.parse = function(ruleText) {
663
- var i = 0;
664
- var state = "selector";
665
- var index;
666
- var j = i;
667
- var buffer = "";
668
-
669
- var SIGNIFICANT_WHITESPACE = {
670
- "selector": true,
671
- "value": true
672
- };
673
-
674
- var styleRule = new CSSOM.CSSStyleRule();
675
- var name, priority="";
676
-
677
- for (var character; (character = ruleText.charAt(i)); i++) {
678
-
679
- switch (character) {
680
-
681
- case " ":
682
- case "\t":
683
- case "\r":
684
- case "\n":
685
- case "\f":
686
- if (SIGNIFICANT_WHITESPACE[state]) {
687
- // Squash 2 or more white-spaces in the row into 1
688
- switch (ruleText.charAt(i - 1)) {
689
- case " ":
690
- case "\t":
691
- case "\r":
692
- case "\n":
693
- case "\f":
694
- break;
695
- default:
696
- buffer += " ";
697
- break;
698
- }
699
- }
700
- break;
701
-
702
- // String
703
- case '"':
704
- j = i + 1;
705
- index = ruleText.indexOf('"', j) + 1;
706
- if (!index) {
707
- throw '" is missing';
708
- }
709
- buffer += ruleText.slice(i, index);
710
- i = index - 1;
711
- break;
712
-
713
- case "'":
714
- j = i + 1;
715
- index = ruleText.indexOf("'", j) + 1;
716
- if (!index) {
717
- throw "' is missing";
718
- }
719
- buffer += ruleText.slice(i, index);
720
- i = index - 1;
721
- break;
722
-
723
- // Comment
724
- case "/":
725
- if (ruleText.charAt(i + 1) === "*") {
726
- i += 2;
727
- index = ruleText.indexOf("*/", i);
728
- if (index === -1) {
729
- throw new SyntaxError("Missing */");
730
- } else {
731
- i = index + 1;
732
- }
733
- } else {
734
- buffer += character;
735
- }
736
- break;
737
-
738
- case "{":
739
- if (state === "selector") {
740
- styleRule.selectorText = buffer.trim();
741
- buffer = "";
742
- state = "name";
743
- }
744
- break;
745
-
746
- case ":":
747
- if (state === "name") {
748
- name = buffer.trim();
749
- buffer = "";
750
- state = "value";
751
- } else {
752
- buffer += character;
753
- }
754
- break;
755
-
756
- case "!":
757
- if (state === "value" && ruleText.indexOf("!important", i) === i) {
758
- priority = "important";
759
- i += "important".length;
760
- } else {
761
- buffer += character;
762
- }
763
- break;
764
-
765
- case ";":
766
- if (state === "value") {
767
- styleRule.style.setProperty(name, buffer.trim(), priority);
768
- priority = "";
769
- buffer = "";
770
- state = "name";
771
- } else {
772
- buffer += character;
773
- }
774
- break;
775
-
776
- case "}":
777
- if (state === "value") {
778
- styleRule.style.setProperty(name, buffer.trim(), priority);
779
- priority = "";
780
- buffer = "";
781
- } else if (state === "name") {
782
- break;
783
- } else {
784
- buffer += character;
785
- }
786
- state = "selector";
787
- break;
788
-
789
- default:
790
- buffer += character;
791
- break;
792
-
793
- }
794
- }
795
-
796
- return styleRule;
797
-
798
- };
799
-
800
-
801
-
802
777
 
803
778
 
804
779
 
@@ -825,7 +800,9 @@ CSSOM.MediaList.prototype = {
825
800
  * @param {string} value
826
801
  */
827
802
  set mediaText(value) {
828
- var values = value.split(",");
803
+ var values = value.split(",").filter(function(text){
804
+ return !!text;
805
+ });
829
806
  var length = this.length = values.length;
830
807
  for (var i=0; i<length; i++) {
831
808
  this[i] = values[i].trim();
@@ -869,9 +846,15 @@ CSSOM.CSSMediaRule = function CSSMediaRule() {
869
846
  this.__media = new CSSOM.MediaList();
870
847
  };
871
848
 
872
- CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
849
+ CSSOM.CSSMediaRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
873
850
  CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
874
- CSSOM.CSSMediaRule.prototype.type = 4;
851
+
852
+ Object.setPrototypeOf(CSSOM.CSSMediaRule, CSSOM.CSSConditionRule);
853
+
854
+ Object.defineProperty(CSSOM.CSSMediaRule.prototype, "type", {
855
+ value: 4,
856
+ writable: false
857
+ });
875
858
 
876
859
  // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
877
860
  Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
@@ -895,14 +878,19 @@ Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
895
878
  },
896
879
  "cssText": {
897
880
  get: function() {
898
- var cssTexts = [];
899
- for (var i=0, length=this.cssRules.length; i < length; i++) {
900
- cssTexts.push(this.cssRules[i].cssText);
881
+ var values = "";
882
+ var valuesArr = [" {"];
883
+ if (this.cssRules.length) {
884
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
885
+ if (rule.cssText !== "") {
886
+ acc.push(rule.cssText);
887
+ }
888
+ return acc;
889
+ }, []).join("\n "));
901
890
  }
902
- return "@media " + this.media.mediaText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
903
- },
904
- configurable: true,
905
- enumerable: true
891
+ values = valuesArr.join("\n ") + "\n}";
892
+ return "@media " + this.media.mediaText + values;
893
+ }
906
894
  }
907
895
  });
908
896
 
@@ -920,21 +908,32 @@ CSSOM.CSSContainerRule = function CSSContainerRule() {
920
908
  CSSOM.CSSConditionRule.call(this);
921
909
  };
922
910
 
923
- CSSOM.CSSContainerRule.prototype = new CSSOM.CSSConditionRule();
911
+ CSSOM.CSSContainerRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
924
912
  CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
925
- CSSOM.CSSContainerRule.prototype.type = 17;
913
+
914
+ Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule);
915
+
916
+ Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", {
917
+ value: 17,
918
+ writable: false
919
+ });
926
920
 
927
921
  Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
928
922
  "cssText": {
929
923
  get: function() {
930
- var cssTexts = [];
931
- for (var i=0, length=this.cssRules.length; i < length; i++) {
932
- cssTexts.push(this.cssRules[i].cssText);
924
+ var values = "";
925
+ var valuesArr = [" {"];
926
+ if (this.cssRules.length) {
927
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
928
+ if (rule.cssText !== "") {
929
+ acc.push(rule.cssText);
930
+ }
931
+ return acc;
932
+ }, []).join("\n "));
933
933
  }
934
- return "@container " + this.conditionText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
935
- },
936
- configurable: true,
937
- enumerable: true
934
+ values = valuesArr.join("\n ") + "\n}";
935
+ return "@container " + this.conditionText + values;
936
+ }
938
937
  },
939
938
  "containerName": {
940
939
  get: function() {
@@ -969,19 +968,30 @@ CSSOM.CSSSupportsRule = function CSSSupportsRule() {
969
968
  CSSOM.CSSConditionRule.call(this);
970
969
  };
971
970
 
972
- CSSOM.CSSSupportsRule.prototype = new CSSOM.CSSConditionRule();
971
+ CSSOM.CSSSupportsRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
973
972
  CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
974
- CSSOM.CSSSupportsRule.prototype.type = 12;
973
+
974
+ Object.setPrototypeOf(CSSOM.CSSSupportsRule, CSSOM.CSSConditionRule);
975
+
976
+ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "type", {
977
+ value: 12,
978
+ writable: false
979
+ });
975
980
 
976
981
  Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
977
982
  get: function() {
978
- var cssTexts = [];
979
-
980
- for (var i = 0, length = this.cssRules.length; i < length; i++) {
981
- cssTexts.push(this.cssRules[i].cssText);
983
+ var values = "";
984
+ var valuesArr = [" {"];
985
+ if (this.cssRules.length) {
986
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
987
+ if (rule.cssText !== "") {
988
+ acc.push(rule.cssText);
989
+ }
990
+ return acc;
991
+ }, []).join("\n "));
982
992
  }
983
-
984
- return "@supports " + this.conditionText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
993
+ values = valuesArr.join("\n ") + "\n}";
994
+ return "@supports " + this.conditionText + values;
985
995
  }
986
996
  });
987
997
 
@@ -1003,9 +1013,11 @@ CSSOM.CSSImportRule = function CSSImportRule() {
1003
1013
  this.__styleSheet = new CSSOM.CSSStyleSheet();
1004
1014
  };
1005
1015
 
1006
- CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
1016
+ CSSOM.CSSImportRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1007
1017
  CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
1008
1018
 
1019
+ Object.setPrototypeOf(CSSOM.CSSImportRule, CSSOM.CSSRule);
1020
+
1009
1021
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
1010
1022
  value: 3,
1011
1023
  writable: false
@@ -1015,8 +1027,53 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1015
1027
  get: function() {
1016
1028
  var mediaText = this.media.mediaText;
1017
1029
  return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
1030
+ }
1031
+ });
1032
+
1033
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
1034
+ get: function() {
1035
+ return this.__href;
1036
+ }
1037
+ });
1038
+
1039
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
1040
+ get: function() {
1041
+ return this.__media;
1018
1042
  },
1019
- set: function(cssText) {
1043
+ set: function(value) {
1044
+ if (typeof value === "string") {
1045
+ this.__media.mediaText = value;
1046
+ } else {
1047
+ this.__media = value;
1048
+ }
1049
+ }
1050
+ });
1051
+
1052
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
1053
+ get: function() {
1054
+ return this.__layerName;
1055
+ }
1056
+ });
1057
+
1058
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
1059
+ get: function() {
1060
+ return this.__supportsText;
1061
+ }
1062
+ });
1063
+
1064
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
1065
+ get: function() {
1066
+ return this.__styleSheet;
1067
+ }
1068
+ });
1069
+
1070
+ /**
1071
+ * NON-STANDARD
1072
+ * Rule text parser.
1073
+ * @param {string} cssText
1074
+ */
1075
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "parse", {
1076
+ value: function(cssText) {
1020
1077
  var i = 0;
1021
1078
 
1022
1079
  /**
@@ -1195,71 +1252,57 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1195
1252
  }
1196
1253
  });
1197
1254
 
1198
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
1255
+
1256
+
1257
+
1258
+
1259
+
1260
+ /**
1261
+ * @constructor
1262
+ * @see https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface
1263
+ */
1264
+ CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
1265
+ CSSOM.CSSRule.call(this);
1266
+ this.__prefix = "";
1267
+ this.__namespaceURI = "";
1268
+ };
1269
+
1270
+ CSSOM.CSSNamespaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1271
+ CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
1272
+
1273
+ Object.setPrototypeOf(CSSOM.CSSNamespaceRule, CSSOM.CSSRule);
1274
+
1275
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
1276
+ value: 10,
1277
+ writable: false
1278
+ });
1279
+
1280
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1199
1281
  get: function() {
1200
- return this.__href;
1282
+ return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
1201
1283
  }
1202
1284
  });
1203
1285
 
1204
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
1286
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
1205
1287
  get: function() {
1206
- return this.__media;
1207
- },
1208
- set: function(value) {
1209
- if (typeof value === "string") {
1210
- this.__media.mediaText = value;
1211
- } else {
1212
- this.__media = value;
1213
- }
1214
- }
1215
- });
1216
-
1217
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
1218
- get: function() {
1219
- return this.__layerName;
1220
- }
1221
- });
1222
-
1223
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
1224
- get: function() {
1225
- return this.__supportsText;
1288
+ return this.__prefix;
1226
1289
  }
1227
1290
  });
1228
1291
 
1229
- Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
1292
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
1230
1293
  get: function() {
1231
- return this.__styleSheet;
1294
+ return this.__namespaceURI;
1232
1295
  }
1233
1296
  });
1234
1297
 
1235
1298
 
1236
-
1237
-
1238
-
1239
-
1240
1299
  /**
1241
- * @constructor
1242
- * @see https://drafts.csswg.org/cssom/#the-cssnamespacerule-interface
1300
+ * NON-STANDARD
1301
+ * Rule text parser.
1302
+ * @param {string} cssText
1243
1303
  */
1244
- CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
1245
- CSSOM.CSSRule.call(this);
1246
- this.__prefix = "";
1247
- this.__namespaceURI = "";
1248
- };
1249
-
1250
- CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
1251
- CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
1252
-
1253
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
1254
- value: 10,
1255
- writable: false
1256
- });
1257
-
1258
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1259
- get: function() {
1260
- return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
1261
- },
1262
- set: function(cssText) {
1304
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "parse", {
1305
+ value: function(cssText) {
1263
1306
  var newPrefix = "";
1264
1307
  var newNamespaceURI = "";
1265
1308
 
@@ -1307,19 +1350,6 @@ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1307
1350
  }
1308
1351
  });
1309
1352
 
1310
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
1311
- get: function() {
1312
- return this.__prefix;
1313
- }
1314
- });
1315
-
1316
- Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
1317
- get: function() {
1318
- return this.__namespaceURI;
1319
- }
1320
- });
1321
-
1322
-
1323
1353
 
1324
1354
 
1325
1355
 
@@ -1333,9 +1363,16 @@ CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
1333
1363
  this.__style.parentRule = this;
1334
1364
  };
1335
1365
 
1336
- CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule();
1366
+ CSSOM.CSSFontFaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1337
1367
  CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
1338
- CSSOM.CSSFontFaceRule.prototype.type = 5;
1368
+
1369
+ Object.setPrototypeOf(CSSOM.CSSFontFaceRule, CSSOM.CSSRule);
1370
+
1371
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "type", {
1372
+ value: 5,
1373
+ writable: false
1374
+ });
1375
+
1339
1376
  //FIXME
1340
1377
  //CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1341
1378
  //CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -1368,26 +1405,42 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
1368
1405
  /**
1369
1406
  * @constructor
1370
1407
  * @see http://www.w3.org/TR/shadow-dom/#host-at-rule
1408
+ * @see http://html5index.org/Shadow%20DOM%20-%20CSSHostRule.html
1409
+ * @deprecated This rule was part of early Shadow DOM drafts but was removed in favor of the more flexible :host and :host-context() pseudo-classes in modern CSS for Web Components.
1371
1410
  */
1372
1411
  CSSOM.CSSHostRule = function CSSHostRule() {
1373
1412
  CSSOM.CSSRule.call(this);
1374
1413
  this.cssRules = new CSSOM.CSSRuleList();
1375
1414
  };
1376
1415
 
1377
- CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule();
1416
+ CSSOM.CSSHostRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1378
1417
  CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
1379
- CSSOM.CSSHostRule.prototype.type = 1001;
1418
+
1419
+ Object.setPrototypeOf(CSSOM.CSSHostRule, CSSOM.CSSRule);
1420
+
1421
+ Object.defineProperty(CSSOM.CSSHostRule.prototype, "type", {
1422
+ value: 1001,
1423
+ writable: false
1424
+ });
1425
+
1380
1426
  //FIXME
1381
1427
  //CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1382
1428
  //CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
1383
1429
 
1384
1430
  Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", {
1385
1431
  get: function() {
1386
- var cssTexts = [];
1387
- for (var i=0, length=this.cssRules.length; i < length; i++) {
1388
- cssTexts.push(this.cssRules[i].cssText);
1432
+ var values = "";
1433
+ var valuesArr = [" {"];
1434
+ if (this.cssRules.length) {
1435
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
1436
+ if (rule.cssText !== "") {
1437
+ acc.push(rule.cssText);
1438
+ }
1439
+ return acc;
1440
+ }, []).join("\n "));
1389
1441
  }
1390
- return "@host {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
1442
+ values = valuesArr.join("\n ") + "\n}";
1443
+ return "@host" + values;
1391
1444
  }
1392
1445
  });
1393
1446
 
@@ -1404,20 +1457,34 @@ CSSOM.CSSStartingStyleRule = function CSSStartingStyleRule() {
1404
1457
  CSSOM.CSSGroupingRule.call(this);
1405
1458
  };
1406
1459
 
1407
- CSSOM.CSSStartingStyleRule.prototype = new CSSOM.CSSGroupingRule();
1460
+ CSSOM.CSSStartingStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
1408
1461
  CSSOM.CSSStartingStyleRule.prototype.constructor = CSSOM.CSSStartingStyleRule;
1409
- CSSOM.CSSStartingStyleRule.prototype.type = 1002;
1462
+
1463
+ Object.setPrototypeOf(CSSOM.CSSStartingStyleRule, CSSOM.CSSGroupingRule);
1464
+
1465
+ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "type", {
1466
+ value: 1002,
1467
+ writable: false
1468
+ });
1469
+
1410
1470
  //FIXME
1411
1471
  //CSSOM.CSSStartingStyleRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1412
1472
  //CSSOM.CSSStartingStyleRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
1413
1473
 
1414
1474
  Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
1415
1475
  get: function() {
1416
- var cssTexts = [];
1417
- for (var i=0, length=this.cssRules.length; i < length; i++) {
1418
- cssTexts.push(this.cssRules[i].cssText);
1476
+ var values = "";
1477
+ var valuesArr = [" {"];
1478
+ if (this.cssRules.length) {
1479
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
1480
+ if (rule.cssText !== "") {
1481
+ acc.push(rule.cssText);
1482
+ }
1483
+ return acc;
1484
+ }, []).join("\n "));
1419
1485
  }
1420
- return "@starting-style {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
1486
+ values = valuesArr.join("\n ") + "\n}";
1487
+ return "@starting-style" + values;
1421
1488
  }
1422
1489
  });
1423
1490
 
@@ -1427,15 +1494,38 @@ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
1427
1494
 
1428
1495
 
1429
1496
  /**
1430
- * @constructor
1431
1497
  * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
1432
1498
  */
1433
1499
  CSSOM.StyleSheet = function StyleSheet() {
1500
+ this.__href = null;
1501
+ this.__ownerNode = null;
1502
+ this.__title = null;
1434
1503
  this.__media = new CSSOM.MediaList();
1435
1504
  this.__parentStyleSheet = null;
1505
+ this.disabled = false;
1436
1506
  };
1437
1507
 
1438
1508
  Object.defineProperties(CSSOM.StyleSheet.prototype, {
1509
+ type: {
1510
+ get: function() {
1511
+ return "text/css";
1512
+ }
1513
+ },
1514
+ href: {
1515
+ get: function() {
1516
+ return this.__href;
1517
+ }
1518
+ },
1519
+ ownerNode: {
1520
+ get: function() {
1521
+ return this.__ownerNode;
1522
+ }
1523
+ },
1524
+ title: {
1525
+ get: function() {
1526
+ return this.__title;
1527
+ }
1528
+ },
1439
1529
  media: {
1440
1530
  get: function() {
1441
1531
  return this.__media;
@@ -1461,20 +1551,52 @@ Object.defineProperties(CSSOM.StyleSheet.prototype, {
1461
1551
 
1462
1552
  /**
1463
1553
  * @constructor
1554
+ * @param {CSSStyleSheetInit} [opts] - CSSStyleSheetInit options.
1555
+ * @param {string} [opts.baseURL] - The base URL of the stylesheet.
1556
+ * @param {boolean} [opts.disabled] - The disabled attribute of the stylesheet.
1557
+ * @param {MediaList | string} [opts.media] - The media attribute of the stylesheet.
1464
1558
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
1465
1559
  */
1466
- CSSOM.CSSStyleSheet = function CSSStyleSheet() {
1560
+ CSSOM.CSSStyleSheet = function CSSStyleSheet(opts) {
1467
1561
  CSSOM.StyleSheet.call(this);
1468
- this.cssRules = new CSSOM.CSSRuleList();
1562
+ this.__constructed = true;
1563
+ this.__cssRules = new CSSOM.CSSRuleList();
1564
+ this.__ownerRule = null;
1565
+
1566
+ if (opts && typeof opts === "object") {
1567
+ if (opts.baseURL && typeof opts.baseURL === "string") {
1568
+ this.__baseURL = opts.baseURL;
1569
+ }
1570
+ if (opts.media && typeof opts.media === "string") {
1571
+ this.media.mediaText = opts.media;
1572
+ }
1573
+ if (typeof opts.disabled === "boolean") {
1574
+ this.disabled = opts.disabled;
1575
+ }
1576
+ }
1469
1577
  };
1470
1578
 
1471
1579
 
1472
- CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
1580
+ CSSOM.CSSStyleSheet.prototype = Object.create(CSSOM.StyleSheet.prototype);
1473
1581
  CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
1474
1582
 
1583
+ Object.setPrototypeOf(CSSOM.CSSStyleSheet, CSSOM.StyleSheet);
1584
+
1585
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "cssRules", {
1586
+ get: function() {
1587
+ return this.__cssRules;
1588
+ }
1589
+ });
1590
+
1475
1591
  Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
1476
1592
  get: function() {
1477
- return this.cssRules;
1593
+ return this.__cssRules;
1594
+ }
1595
+ });
1596
+
1597
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "ownerRule", {
1598
+ get: function() {
1599
+ return this.__ownerRule;
1478
1600
  }
1479
1601
  });
1480
1602
 
@@ -1677,6 +1799,84 @@ CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
1677
1799
  this.deleteRule(index);
1678
1800
  };
1679
1801
 
1802
+
1803
+ /**
1804
+ * Replaces the rules of a {@link CSSStyleSheet}
1805
+ *
1806
+ * @returns a promise
1807
+ * @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replace
1808
+ */
1809
+ CSSOM.CSSStyleSheet.prototype.replace = function(text) {
1810
+ var _Promise;
1811
+ if (CSSOM.getGlobalObject() && CSSOM.getGlobalObject()['Promise']) {
1812
+ _Promise = CSSOM.getGlobalObject()['Promise'];
1813
+ } else {
1814
+ _Promise = Promise;
1815
+ }
1816
+ var sheet = this;
1817
+ return new _Promise(function (resolve, reject) {
1818
+ // If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
1819
+ if (!sheet.__constructed || sheet.__disallowModification) {
1820
+ reject(errorUtils.createError(sheet, 'DOMException',
1821
+ "Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
1822
+ 'NotAllowedError'));
1823
+ }
1824
+ // Set the disallow modification flag.
1825
+ sheet.__disallowModification = true;
1826
+
1827
+ // In parallel, do these steps:
1828
+ setTimeout(function() {
1829
+ // Let rules be the result of running parse a stylesheet's contents from text.
1830
+ var rules = new CSSOM.CSSRuleList();
1831
+ CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
1832
+ // If rules contains one or more @import rules, remove those rules from rules.
1833
+ var i = 0;
1834
+ while (i < rules.length) {
1835
+ if (rules[i].constructor.name === 'CSSImportRule') {
1836
+ rules.splice(i, 1);
1837
+ } else {
1838
+ i++;
1839
+ }
1840
+ }
1841
+ // Set sheet's CSS rules to rules.
1842
+ sheet.__cssRules = rules;
1843
+ // Unset sheet’s disallow modification flag.
1844
+ delete sheet.__disallowModification;
1845
+ // Resolve promise with sheet.
1846
+ resolve(sheet);
1847
+ })
1848
+ });
1849
+ }
1850
+
1851
+ /**
1852
+ * Synchronously replaces the rules of a {@link CSSStyleSheet}
1853
+ *
1854
+ * @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replacesync
1855
+ */
1856
+ CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
1857
+ var sheet = this;
1858
+ // If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
1859
+ if (!sheet.__constructed || sheet.__disallowModification) {
1860
+ errorUtils.throwError(sheet, 'DOMException',
1861
+ "Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
1862
+ 'NotAllowedError');
1863
+ }
1864
+ // Let rules be the result of running parse a stylesheet's contents from text.
1865
+ var rules = new CSSOM.CSSRuleList();
1866
+ CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
1867
+ // If rules contains one or more @import rules, remove those rules from rules.
1868
+ var i = 0;
1869
+ while (i < rules.length) {
1870
+ if (rules[i].constructor.name === 'CSSImportRule') {
1871
+ rules.splice(i, 1);
1872
+ } else {
1873
+ i++;
1874
+ }
1875
+ }
1876
+ // Set sheet's CSS rules to rules.
1877
+ sheet.__cssRules = rules;
1878
+ }
1879
+
1680
1880
  /**
1681
1881
  * NON-STANDARD
1682
1882
  * @return {string} serialize stylesheet
@@ -1736,20 +1936,33 @@ CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
1736
1936
  });
1737
1937
  };
1738
1938
 
1739
- CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule();
1939
+ CSSOM.CSSKeyframesRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1740
1940
  CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
1741
- CSSOM.CSSKeyframesRule.prototype.type = 7;
1941
+
1942
+ Object.setPrototypeOf(CSSOM.CSSKeyframesRule, CSSOM.CSSRule);
1943
+
1944
+ Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "type", {
1945
+ value: 7,
1946
+ writable: false
1947
+ });
1742
1948
 
1743
1949
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
1744
1950
  Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
1745
1951
  get: function() {
1746
- var cssTexts = [];
1747
- for (var i=0, length=this.cssRules.length; i < length; i++) {
1748
- cssTexts.push(this.cssRules[i].cssText);
1749
- }
1952
+ var values = "";
1953
+ var valuesArr = [" {"];
1954
+ if (this.cssRules.length) {
1955
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
1956
+ if (rule.cssText !== "") {
1957
+ acc.push(rule.cssText);
1958
+ }
1959
+ return acc;
1960
+ }, []).join("\n "));
1961
+ }
1962
+ values = valuesArr.join("\n ") + "\n}";
1750
1963
  var cssWideKeywords = ['initial', 'inherit', 'revert', 'revert-layer', 'unset', 'none'];
1751
1964
  var processedName = cssWideKeywords.includes(this.name) ? '"' + this.name + '"' : this.name;
1752
- return "@" + (this._vendorPrefix || '') + "keyframes " + processedName + (processedName && " ") + "{" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
1965
+ return "@" + (this._vendorPrefix || '') + "keyframes " + processedName + values;
1753
1966
  }
1754
1967
  });
1755
1968
 
@@ -1931,9 +2144,16 @@ CSSOM.CSSKeyframeRule = function CSSKeyframeRule() {
1931
2144
  this.__style.parentRule = this;
1932
2145
  };
1933
2146
 
1934
- CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule();
2147
+ CSSOM.CSSKeyframeRule.prototype = Object.create(CSSOM.CSSRule.prototype);
1935
2148
  CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule;
1936
- CSSOM.CSSKeyframeRule.prototype.type = 8;
2149
+
2150
+ Object.setPrototypeOf(CSSOM.CSSKeyframeRule, CSSOM.CSSRule);
2151
+
2152
+ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "type", {
2153
+ value: 8,
2154
+ writable: false
2155
+ });
2156
+
1937
2157
  //FIXME
1938
2158
  //CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
1939
2159
  //CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2024,6 +2244,7 @@ CSSOM.MatcherList.prototype = {
2024
2244
  /**
2025
2245
  * @constructor
2026
2246
  * @see https://developer.mozilla.org/en/CSS/@-moz-document
2247
+ * @deprecated This rule is a non-standard Mozilla-specific extension and is not part of any official CSS specification.
2027
2248
  */
2028
2249
  CSSOM.CSSDocumentRule = function CSSDocumentRule() {
2029
2250
  CSSOM.CSSRule.call(this);
@@ -2031,9 +2252,16 @@ CSSOM.CSSDocumentRule = function CSSDocumentRule() {
2031
2252
  this.cssRules = new CSSOM.CSSRuleList();
2032
2253
  };
2033
2254
 
2034
- CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule();
2255
+ CSSOM.CSSDocumentRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2035
2256
  CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule;
2036
- CSSOM.CSSDocumentRule.prototype.type = 10;
2257
+
2258
+ Object.setPrototypeOf(CSSOM.CSSDocumentRule, CSSOM.CSSRule);
2259
+
2260
+ Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "type", {
2261
+ value: 10,
2262
+ writable: false
2263
+ });
2264
+
2037
2265
  //FIXME
2038
2266
  //CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
2039
2267
  //CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
@@ -2102,9 +2330,11 @@ CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
2102
2330
  this._idx = idx;
2103
2331
  };
2104
2332
 
2105
- CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue();
2333
+ CSSOM.CSSValueExpression.prototype = Object.create(CSSOM.CSSValue.prototype);
2106
2334
  CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
2107
2335
 
2336
+ Object.setPrototypeOf(CSSOM.CSSValueExpression, CSSOM.CSSValue);
2337
+
2108
2338
  /**
2109
2339
  * parse css expression() value
2110
2340
  *
@@ -2439,9 +2669,10 @@ CSSOM.CSSScopeRule = function CSSScopeRule() {
2439
2669
  this.__end = null;
2440
2670
  };
2441
2671
 
2442
- CSSOM.CSSScopeRule.prototype = new CSSOM.CSSGroupingRule();
2672
+ CSSOM.CSSScopeRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2443
2673
  CSSOM.CSSScopeRule.prototype.constructor = CSSOM.CSSScopeRule;
2444
2674
 
2675
+ Object.setPrototypeOf(CSSOM.CSSScopeRule, CSSOM.CSSGroupingRule);
2445
2676
 
2446
2677
  Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
2447
2678
  type: {
@@ -2450,11 +2681,18 @@ Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
2450
2681
  },
2451
2682
  cssText: {
2452
2683
  get: function () {
2453
- var cssTexts = [];
2454
- for (var i = 0, length = this.cssRules.length; i < length; i++) {
2455
- cssTexts.push(this.cssRules[i].cssText);
2684
+ var values = "";
2685
+ var valuesArr = [" {"];
2686
+ if (this.cssRules.length) {
2687
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
2688
+ if (rule.cssText !== "") {
2689
+ acc.push(rule.cssText);
2690
+ }
2691
+ return acc;
2692
+ }, []).join("\n "));
2456
2693
  }
2457
- return "@scope " + (this.start ? "(" + this.start + ") " : "") + (this.end ? "to (" + this.end + ") " : "") + "{" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
2694
+ values = valuesArr.join("\n ") + "\n}";
2695
+ return "@scope" + (this.start ? " (" + this.start + ")" : "") + (this.end ? " to (" + this.end + ")" : "") + values;
2458
2696
  },
2459
2697
  configurable: true,
2460
2698
  enumerable: true,
@@ -2483,21 +2721,32 @@ CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
2483
2721
  this.name = "";
2484
2722
  };
2485
2723
 
2486
- CSSOM.CSSLayerBlockRule.prototype = new CSSOM.CSSGroupingRule();
2724
+ CSSOM.CSSLayerBlockRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2487
2725
  CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
2488
- CSSOM.CSSLayerBlockRule.prototype.type = 18;
2726
+
2727
+ Object.setPrototypeOf(CSSOM.CSSLayerBlockRule, CSSOM.CSSRule);
2728
+
2729
+ Object.defineProperty(CSSOM.CSSLayerBlockRule.prototype, "type", {
2730
+ value: 18,
2731
+ writable: false
2732
+ });
2489
2733
 
2490
2734
  Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
2491
2735
  cssText: {
2492
2736
  get: function () {
2493
- var cssTexts = [];
2494
- for (var i = 0, length = this.cssRules.length; i < length; i++) {
2495
- cssTexts.push(this.cssRules[i].cssText);
2737
+ var values = "";
2738
+ var valuesArr = [" {"];
2739
+ if (this.cssRules.length) {
2740
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
2741
+ if (rule.cssText !== "") {
2742
+ acc.push(rule.cssText);
2743
+ }
2744
+ return acc;
2745
+ }, []).join("\n "));
2496
2746
  }
2497
- return "@layer " + this.name + (this.name && " ") + "{" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
2498
- },
2499
- configurable: true,
2500
- enumerable: true,
2747
+ values = valuesArr.join("\n ") + "\n}";
2748
+ return "@layer" + (this.name ? " " + this.name : "") + values;
2749
+ }
2501
2750
  },
2502
2751
  });
2503
2752
 
@@ -2513,17 +2762,21 @@ CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
2513
2762
  this.nameList = [];
2514
2763
  };
2515
2764
 
2516
- CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
2765
+ CSSOM.CSSLayerStatementRule.prototype = Object.create(CSSOM.CSSRule.prototype);
2517
2766
  CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
2518
- CSSOM.CSSLayerStatementRule.prototype.type = 0;
2767
+
2768
+ Object.setPrototypeOf(CSSOM.CSSLayerStatementRule, CSSOM.CSSRule);
2769
+
2770
+ Object.defineProperty(CSSOM.CSSLayerStatementRule.prototype, "type", {
2771
+ value: 0,
2772
+ writable: false
2773
+ });
2519
2774
 
2520
2775
  Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2521
2776
  cssText: {
2522
2777
  get: function () {
2523
2778
  return "@layer " + this.nameList.join(", ") + ";";
2524
- },
2525
- configurable: true,
2526
- enumerable: true,
2779
+ }
2527
2780
  },
2528
2781
  });
2529
2782
 
@@ -2541,9 +2794,11 @@ CSSOM.CSSPageRule = function CSSPageRule() {
2541
2794
  this.__style.parentRule = this;
2542
2795
  };
2543
2796
 
2544
- CSSOM.CSSPageRule.prototype = new CSSOM.CSSGroupingRule();
2797
+ CSSOM.CSSPageRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
2545
2798
  CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
2546
2799
 
2800
+ Object.setPrototypeOf(CSSOM.CSSPageRule, CSSOM.CSSGroupingRule);
2801
+
2547
2802
  Object.defineProperty(CSSOM.CSSPageRule.prototype, "type", {
2548
2803
  value: 6,
2549
2804
  writable: false
@@ -2623,171 +2878,24 @@ Object.defineProperty(CSSOM.CSSPageRule.prototype, "style", {
2623
2878
 
2624
2879
  Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
2625
2880
  get: function() {
2626
- var values = ""
2881
+ var values = "";
2627
2882
  if (this.cssRules.length) {
2628
2883
  var valuesArr = [" {"];
2629
2884
  this.style.cssText && valuesArr.push(this.style.cssText);
2630
- valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
2631
- values = valuesArr.join("\n ") + "\n}"
2885
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
2886
+ if (rule.cssText !== "") {
2887
+ acc.push(rule.cssText);
2888
+ }
2889
+ return acc;
2890
+ }, []).join("\n "));
2891
+ values = valuesArr.join("\n ") + "\n}";
2632
2892
  } else {
2633
2893
  values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
2634
2894
  }
2635
2895
  return "@page" + (this.selectorText ? " " + this.selectorText : "") + values;
2636
- },
2637
- set: function(cssText) {
2638
- if (typeof value === "string") {
2639
- var rule = CSSOM.CSSPageRule.parse(cssText);
2640
- this.__style = rule.style;
2641
- this.selectorText = rule.selectorText;
2642
- }
2643
2896
  }
2644
2897
  });
2645
2898
 
2646
- /**
2647
- * NON-STANDARD
2648
- * lightweight version of parse.js.
2649
- * @param {string} ruleText
2650
- * @return CSSPageRule
2651
- */
2652
- CSSOM.CSSPageRule.parse = function(ruleText) {
2653
- var i = 0;
2654
- var state = "selector";
2655
- var index;
2656
- var j = i;
2657
- var buffer = "";
2658
-
2659
- var SIGNIFICANT_WHITESPACE = {
2660
- "selector": true,
2661
- "value": true
2662
- };
2663
-
2664
- var pageRule = new CSSOM.CSSPageRule();
2665
- var name, priority="";
2666
-
2667
- for (var character; (character = ruleText.charAt(i)); i++) {
2668
-
2669
- switch (character) {
2670
-
2671
- case " ":
2672
- case "\t":
2673
- case "\r":
2674
- case "\n":
2675
- case "\f":
2676
- if (SIGNIFICANT_WHITESPACE[state]) {
2677
- // Squash 2 or more white-spaces in the row into 1
2678
- switch (ruleText.charAt(i - 1)) {
2679
- case " ":
2680
- case "\t":
2681
- case "\r":
2682
- case "\n":
2683
- case "\f":
2684
- break;
2685
- default:
2686
- buffer += " ";
2687
- break;
2688
- }
2689
- }
2690
- break;
2691
-
2692
- // String
2693
- case '"':
2694
- j = i + 1;
2695
- index = ruleText.indexOf('"', j) + 1;
2696
- if (!index) {
2697
- throw '" is missing';
2698
- }
2699
- buffer += ruleText.slice(i, index);
2700
- i = index - 1;
2701
- break;
2702
-
2703
- case "'":
2704
- j = i + 1;
2705
- index = ruleText.indexOf("'", j) + 1;
2706
- if (!index) {
2707
- throw "' is missing";
2708
- }
2709
- buffer += ruleText.slice(i, index);
2710
- i = index - 1;
2711
- break;
2712
-
2713
- // Comment
2714
- case "/":
2715
- if (ruleText.charAt(i + 1) === "*") {
2716
- i += 2;
2717
- index = ruleText.indexOf("*/", i);
2718
- if (index === -1) {
2719
- throw new SyntaxError("Missing */");
2720
- } else {
2721
- i = index + 1;
2722
- }
2723
- } else {
2724
- buffer += character;
2725
- }
2726
- break;
2727
-
2728
- case "{":
2729
- if (state === "selector") {
2730
- pageRule.selectorText = buffer.trim();
2731
- buffer = "";
2732
- state = "name";
2733
- }
2734
- break;
2735
-
2736
- case ":":
2737
- if (state === "name") {
2738
- name = buffer.trim();
2739
- buffer = "";
2740
- state = "value";
2741
- } else {
2742
- buffer += character;
2743
- }
2744
- break;
2745
-
2746
- case "!":
2747
- if (state === "value" && ruleText.indexOf("!important", i) === i) {
2748
- priority = "important";
2749
- i += "important".length;
2750
- } else {
2751
- buffer += character;
2752
- }
2753
- break;
2754
-
2755
- case ";":
2756
- if (state === "value") {
2757
- pageRule.style.setProperty(name, buffer.trim(), priority);
2758
- priority = "";
2759
- buffer = "";
2760
- state = "name";
2761
- } else {
2762
- buffer += character;
2763
- }
2764
- break;
2765
-
2766
- case "}":
2767
- if (state === "value") {
2768
- pageRule.style.setProperty(name, buffer.trim(), priority);
2769
- priority = "";
2770
- buffer = "";
2771
- } else if (state === "name") {
2772
- break;
2773
- } else {
2774
- buffer += character;
2775
- }
2776
- state = "selector";
2777
- break;
2778
-
2779
- default:
2780
- buffer += character;
2781
- break;
2782
-
2783
- }
2784
- }
2785
-
2786
- return pageRule;
2787
-
2788
- };
2789
-
2790
-
2791
2899
 
2792
2900
 
2793
2901
 
@@ -2796,7 +2904,11 @@ CSSOM.CSSPageRule.parse = function(ruleText) {
2796
2904
  *
2797
2905
  * @param {string} token - The CSS string to parse.
2798
2906
  * @param {object} [opts] - Optional parsing options.
2799
- * @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
2907
+ * @param {object} [opts.globalObject] - @deprecated This property will be removed in the next release. Use CSSOM.setup({ globalObject }) instead. - An optional global object to override globals and window. Useful on jsdom webplatform tests.
2908
+ * @param {Element | ProcessingInstruction} [opts.ownerNode] - The owner node of the stylesheet.
2909
+ * @param {CSSRule} [opts.ownerRule] - The owner rule of the stylesheet.
2910
+ * @param {CSSOM.CSSStyleSheet} [opts.styleSheet] - Reuse a style sheet instead of creating a new one (e.g. as `parentStyleSheet`)
2911
+ * @param {CSSOM.CSSRuleList} [opts.cssRules] - Prepare all rules in this list instead of mutating the style sheet continually
2800
2912
  * @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
2801
2913
  * @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
2802
2914
  */
@@ -2843,14 +2955,35 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2843
2955
  "pageBlock": true
2844
2956
  };
2845
2957
 
2846
- var styleSheet = new CSSOM.CSSStyleSheet();
2958
+ var styleSheet;
2959
+ if (opts && opts.styleSheet) {
2960
+ styleSheet = opts.styleSheet;
2961
+ } else {
2962
+ styleSheet = new CSSOM.CSSStyleSheet()
2963
+ styleSheet.__constructed = false;
2964
+ }
2965
+
2966
+ var topScope;
2967
+ if (opts && opts.cssRules) {
2968
+ topScope = { cssRules: opts.cssRules };
2969
+ } else {
2970
+ topScope = styleSheet;
2971
+ }
2847
2972
 
2848
2973
  if (opts && opts.globalObject) {
2849
- styleSheet.__globalObject = opts.globalObject;
2974
+ CSSOM.setup({ globalObject: opts.globalObject });
2975
+ }
2976
+
2977
+ if (opts && opts.ownerNode) {
2978
+ styleSheet.__ownerNode = opts.ownerNode;
2979
+ }
2980
+
2981
+ if (opts && opts.ownerRule) {
2982
+ styleSheet.__ownerRule = opts.ownerRule;
2850
2983
  }
2851
2984
 
2852
2985
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
2853
- var currentScope = styleSheet;
2986
+ var currentScope = topScope;
2854
2987
 
2855
2988
  // @type CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
2856
2989
  var parentRule;
@@ -2858,7 +2991,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2858
2991
  var ancestorRules = [];
2859
2992
  var prevScope;
2860
2993
 
2861
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
2994
+ var name, priority = "", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
2862
2995
 
2863
2996
  // Track defined namespace prefixes for validation
2864
2997
  var definedNamespacePrefixes = {};
@@ -2956,14 +3089,14 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2956
3089
  var ruleClosingMatch = matchBalancedBlock(str, fromIndex);
2957
3090
  if (ruleClosingMatch) {
2958
3091
  var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
2959
- i+= ignoreRange;
3092
+ i += ignoreRange;
2960
3093
  if (token.charAt(i) === '}') {
2961
3094
  i -= 1;
2962
3095
  }
2963
3096
  } else {
2964
3097
  i += str.length;
2965
3098
  }
2966
- return i;
3099
+ return i;
2967
3100
  }
2968
3101
 
2969
3102
  /**
@@ -2973,29 +3106,29 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2973
3106
  */
2974
3107
  function parseScopePrelude(preludeContent) {
2975
3108
  var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
2976
-
3109
+
2977
3110
  // Restore the parentheses that were consumed by the split
2978
3111
  if (parts.length === 2) {
2979
3112
  parts[0] = parts[0] + ')';
2980
3113
  parts[1] = '(' + parts[1];
2981
3114
  }
2982
-
3115
+
2983
3116
  var hasStart = parts[0] &&
2984
3117
  parts[0].charAt(0) === '(' &&
2985
3118
  parts[0].charAt(parts[0].length - 1) === ')';
2986
3119
  var hasEnd = parts[1] &&
2987
3120
  parts[1].charAt(0) === '(' &&
2988
3121
  parts[1].charAt(parts[1].length - 1) === ')';
2989
-
3122
+
2990
3123
  // Handle case: @scope to (<end>)
2991
3124
  var hasOnlyEnd = !hasStart &&
2992
3125
  !hasEnd &&
2993
3126
  parts[0].indexOf('to (') === 0 &&
2994
3127
  parts[0].charAt(parts[0].length - 1) === ')';
2995
-
3128
+
2996
3129
  var startSelector = '';
2997
3130
  var endSelector = '';
2998
-
3131
+
2999
3132
  if (hasStart) {
3000
3133
  startSelector = parts[0].slice(1, -1).trim();
3001
3134
  }
@@ -3005,7 +3138,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3005
3138
  if (hasOnlyEnd) {
3006
3139
  endSelector = parts[0].slice(4, -1).trim();
3007
3140
  }
3008
-
3141
+
3009
3142
  return {
3010
3143
  startSelector: startSelector,
3011
3144
  endSelector: endSelector,
@@ -3043,11 +3176,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3043
3176
  var inDoubleQuote = false;
3044
3177
  var inAttr = false;
3045
3178
  var stack = useStack ? [] : null;
3046
-
3179
+
3047
3180
  for (var i = 0; i < selector.length; i++) {
3048
3181
  var char = selector[i];
3049
3182
  var prevChar = i > 0 ? selector[i - 1] : '';
3050
-
3183
+
3051
3184
  if (inSingleQuote) {
3052
3185
  if (char === "'" && prevChar !== "\\") {
3053
3186
  inSingleQuote = false;
@@ -3098,7 +3231,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3098
3231
  }
3099
3232
  }
3100
3233
  }
3101
-
3234
+
3102
3235
  // Check if everything is balanced
3103
3236
  if (useStack) {
3104
3237
  return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
@@ -3148,7 +3281,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3148
3281
  /(?:^|[\s>+~,\[])cue\s*\(/i,
3149
3282
  /(?:^|[\s>+~,\[])cue-region\s*\(/i
3150
3283
  ];
3151
-
3284
+
3152
3285
  for (var i = 0; i < invalidPatterns.length; i++) {
3153
3286
  if (invalidPatterns[i].test(selector)) {
3154
3287
  return true;
@@ -3179,7 +3312,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3179
3312
  var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
3180
3313
  var ruleSlice = token.slice(i);
3181
3314
  // Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
3182
- var shouldPerformCheck = cannotBeNested && currentScope !== styleSheet ? false : true;
3315
+ var shouldPerformCheck = cannotBeNested && currentScope !== topScope ? false : true;
3183
3316
  // First, check if there is no invalid characters just after the at-rule
3184
3317
  if (shouldPerformCheck && ruleSlice.search(ruleRegExp) === 0) {
3185
3318
  // Find the closest allowed character before the at-rule (a opening or closing brace, a semicolon or a comment ending)
@@ -3194,7 +3327,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3194
3327
  isValid = true;
3195
3328
  }
3196
3329
  }
3197
-
3330
+
3198
3331
  // Additional validation for @scope rule
3199
3332
  if (isValid && atRuleKey === "@scope") {
3200
3333
  var openBraceIndex = ruleSlice.indexOf('{');
@@ -3213,7 +3346,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3213
3346
  var hasStart = parsedScopePrelude.hasStart;
3214
3347
  var hasEnd = parsedScopePrelude.hasEnd;
3215
3348
  var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
3216
-
3349
+
3217
3350
  // Validation rules for @scope:
3218
3351
  // 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
3219
3352
  if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
@@ -3249,13 +3382,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3249
3382
  if (openBraceIndex !== -1) {
3250
3383
  // Extract the rule prelude (everything between the at-rule and {)
3251
3384
  var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
3252
-
3385
+
3253
3386
  // Skip past at-rule keyword and whitespace
3254
3387
  var preludeContent = rulePrelude.slice("@page".length).trim();
3255
3388
 
3256
3389
  if (preludeContent.length > 0) {
3257
3390
  var trimmedValue = preludeContent.trim();
3258
-
3391
+
3259
3392
  // Empty selector is valid for @page
3260
3393
  if (trimmedValue !== '') {
3261
3394
  // Parse @page selectorText for page name and pseudo-pages
@@ -3279,7 +3412,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3279
3412
 
3280
3413
  // Validate pseudo-pages if present
3281
3414
  if (pseudoPages) {
3282
- var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
3415
+ var pseudos = pseudoPages.split(':').filter(function (p) { return p; });
3283
3416
  var validPseudos = ['left', 'right', 'first', 'blank'];
3284
3417
  var allValid = true;
3285
3418
  for (var j = 0; j < pseudos.length; j++) {
@@ -3288,7 +3421,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3288
3421
  break;
3289
3422
  }
3290
3423
  }
3291
-
3424
+
3292
3425
  if (!allValid) {
3293
3426
  isValid = false;
3294
3427
  }
@@ -3297,21 +3430,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3297
3430
  isValid = false;
3298
3431
  }
3299
3432
  }
3300
-
3433
+
3301
3434
  }
3302
3435
  }
3303
3436
  }
3304
-
3437
+
3305
3438
  if (!isValid) {
3306
3439
  // If it's invalid the browser will simply ignore the entire invalid block
3307
3440
  // Use regex to find the closing brace of the invalid rule
3308
-
3441
+
3309
3442
  // Regex used above is not ES5 compliant. Using alternative.
3310
3443
  // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
3311
3444
  var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
3312
3445
 
3313
3446
  // If it's a statement inside a nested rule, ignore only the statement
3314
- if (ruleStatementMatch && currentScope !== styleSheet) {
3447
+ if (ruleStatementMatch && currentScope !== topScope) {
3315
3448
  var ignoreEnd = ruleStatementMatch[0].indexOf(";");
3316
3449
  i += ruleStatementMatch.index + ignoreEnd;
3317
3450
  return;
@@ -3320,7 +3453,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3320
3453
  // Check if there's a semicolon before the invalid at-rule and the first opening brace
3321
3454
  if (atRuleKey === "@layer") {
3322
3455
  var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
3323
- if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
3456
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";") {
3324
3457
  // Ignore the rule block until the semicolon
3325
3458
  i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
3326
3459
  state = "before-selector";
@@ -3336,6 +3469,255 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3336
3469
  }
3337
3470
  }
3338
3471
 
3472
+ // Helper functions for looseSelectorValidator
3473
+ // Defined outside to avoid recreation on every validation call
3474
+
3475
+ /**
3476
+ * Check if character is a valid identifier start
3477
+ * @param {string} c - Character to check
3478
+ * @returns {boolean}
3479
+ */
3480
+ function isIdentStart(c) {
3481
+ return /[a-zA-Z_\u00A0-\uFFFF]/.test(c);
3482
+ }
3483
+
3484
+ /**
3485
+ * Check if character is a valid identifier character
3486
+ * @param {string} c - Character to check
3487
+ * @returns {boolean}
3488
+ */
3489
+ function isIdentChar(c) {
3490
+ return /[a-zA-Z0-9_\u00A0-\uFFFF\-]/.test(c);
3491
+ }
3492
+
3493
+ /**
3494
+ * Helper function to validate CSS selector syntax without regex backtracking.
3495
+ * Iteratively parses the selector string to identify valid components.
3496
+ *
3497
+ * Supports:
3498
+ * - Escaped special characters (e.g., .class\!, #id\@name)
3499
+ * - Namespace selectors (ns|element, *|element, |element)
3500
+ * - All standard CSS selectors (class, ID, type, attribute, pseudo, etc.)
3501
+ * - Combinators (>, +, ~, whitespace)
3502
+ * - Nesting selector (&)
3503
+ *
3504
+ * This approach eliminates exponential backtracking by using explicit character-by-character
3505
+ * parsing instead of nested quantifiers in regex.
3506
+ *
3507
+ * @param {string} selector - The selector to validate
3508
+ * @returns {boolean} - True if valid selector syntax
3509
+ */
3510
+ function looseSelectorValidator(selector) {
3511
+ if (!selector || selector.length === 0) {
3512
+ return false;
3513
+ }
3514
+
3515
+ var i = 0;
3516
+ var len = selector.length;
3517
+ var hasMatchedComponent = false;
3518
+
3519
+ // Helper: Skip escaped character (backslash + any char)
3520
+ function skipEscape() {
3521
+ if (i < len && selector[i] === '\\') {
3522
+ i += 2; // Skip backslash and next character
3523
+ return true;
3524
+ }
3525
+ return false;
3526
+ }
3527
+
3528
+ // Helper: Parse identifier (with possible escapes)
3529
+ function parseIdentifier() {
3530
+ var start = i;
3531
+ while (i < len) {
3532
+ if (skipEscape()) {
3533
+ continue;
3534
+ } else if (isIdentChar(selector[i])) {
3535
+ i++;
3536
+ } else {
3537
+ break;
3538
+ }
3539
+ }
3540
+ return i > start;
3541
+ }
3542
+
3543
+ // Helper: Parse namespace prefix (optional)
3544
+ function parseNamespace() {
3545
+ var start = i;
3546
+
3547
+ // Match: *| or identifier| or |
3548
+ if (i < len && selector[i] === '*') {
3549
+ i++;
3550
+ } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
3551
+ parseIdentifier();
3552
+ }
3553
+
3554
+ if (i < len && selector[i] === '|') {
3555
+ i++;
3556
+ return true;
3557
+ }
3558
+
3559
+ // Rollback if no pipe found
3560
+ i = start;
3561
+ return false;
3562
+ }
3563
+
3564
+ // Helper: Parse pseudo-class/element arguments (with balanced parens)
3565
+ function parsePseudoArgs() {
3566
+ if (i >= len || selector[i] !== '(') {
3567
+ return false;
3568
+ }
3569
+
3570
+ i++; // Skip opening paren
3571
+ var depth = 1;
3572
+ var inString = false;
3573
+ var stringChar = '';
3574
+
3575
+ while (i < len && depth > 0) {
3576
+ var c = selector[i];
3577
+
3578
+ if (c === '\\' && i + 1 < len) {
3579
+ i += 2; // Skip escaped character
3580
+ } else if (!inString && (c === '"' || c === '\'')) {
3581
+ inString = true;
3582
+ stringChar = c;
3583
+ i++;
3584
+ } else if (inString && c === stringChar) {
3585
+ inString = false;
3586
+ i++;
3587
+ } else if (!inString && c === '(') {
3588
+ depth++;
3589
+ i++;
3590
+ } else if (!inString && c === ')') {
3591
+ depth--;
3592
+ i++;
3593
+ } else {
3594
+ i++;
3595
+ }
3596
+ }
3597
+
3598
+ return depth === 0;
3599
+ }
3600
+
3601
+ // Main parsing loop
3602
+ while (i < len) {
3603
+ var matched = false;
3604
+ var start = i;
3605
+
3606
+ // Skip whitespace
3607
+ while (i < len && /\s/.test(selector[i])) {
3608
+ i++;
3609
+ }
3610
+ if (i > start) {
3611
+ hasMatchedComponent = true;
3612
+ continue;
3613
+ }
3614
+
3615
+ // Match combinators: >, +, ~
3616
+ if (i < len && /[>+~]/.test(selector[i])) {
3617
+ i++;
3618
+ hasMatchedComponent = true;
3619
+ // Skip trailing whitespace
3620
+ while (i < len && /\s/.test(selector[i])) {
3621
+ i++;
3622
+ }
3623
+ continue;
3624
+ }
3625
+
3626
+ // Match nesting selector: &
3627
+ if (i < len && selector[i] === '&') {
3628
+ i++;
3629
+ hasMatchedComponent = true;
3630
+ matched = true;
3631
+ }
3632
+ // Match class selector: .identifier
3633
+ else if (i < len && selector[i] === '.') {
3634
+ i++;
3635
+ if (parseIdentifier()) {
3636
+ hasMatchedComponent = true;
3637
+ matched = true;
3638
+ }
3639
+ }
3640
+ // Match ID selector: #identifier
3641
+ else if (i < len && selector[i] === '#') {
3642
+ i++;
3643
+ if (parseIdentifier()) {
3644
+ hasMatchedComponent = true;
3645
+ matched = true;
3646
+ }
3647
+ }
3648
+ // Match pseudo-class/element: :identifier or ::identifier
3649
+ else if (i < len && selector[i] === ':') {
3650
+ i++;
3651
+ if (i < len && selector[i] === ':') {
3652
+ i++; // Pseudo-element
3653
+ }
3654
+ if (parseIdentifier()) {
3655
+ parsePseudoArgs(); // Optional arguments
3656
+ hasMatchedComponent = true;
3657
+ matched = true;
3658
+ }
3659
+ }
3660
+ // Match attribute selector: [...]
3661
+ else if (i < len && selector[i] === '[') {
3662
+ i++;
3663
+ var depth = 1;
3664
+ while (i < len && depth > 0) {
3665
+ if (selector[i] === '\\') {
3666
+ i += 2;
3667
+ } else if (selector[i] === '\'') {
3668
+ i++;
3669
+ while (i < len && selector[i] !== '\'') {
3670
+ if (selector[i] === '\\') i += 2;
3671
+ else i++;
3672
+ }
3673
+ if (i < len) i++; // Skip closing quote
3674
+ } else if (selector[i] === '"') {
3675
+ i++;
3676
+ while (i < len && selector[i] !== '"') {
3677
+ if (selector[i] === '\\') i += 2;
3678
+ else i++;
3679
+ }
3680
+ if (i < len) i++; // Skip closing quote
3681
+ } else if (selector[i] === '[') {
3682
+ depth++;
3683
+ i++;
3684
+ } else if (selector[i] === ']') {
3685
+ depth--;
3686
+ i++;
3687
+ } else {
3688
+ i++;
3689
+ }
3690
+ }
3691
+ if (depth === 0) {
3692
+ hasMatchedComponent = true;
3693
+ matched = true;
3694
+ }
3695
+ }
3696
+ // Match type selector with optional namespace: [namespace|]identifier
3697
+ else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|')) {
3698
+ parseNamespace(); // Optional namespace prefix
3699
+
3700
+ if (i < len && selector[i] === '*') {
3701
+ i++; // Universal selector
3702
+ hasMatchedComponent = true;
3703
+ matched = true;
3704
+ } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
3705
+ if (parseIdentifier()) {
3706
+ hasMatchedComponent = true;
3707
+ matched = true;
3708
+ }
3709
+ }
3710
+ }
3711
+
3712
+ // If no match found, invalid selector
3713
+ if (!matched && i === start) {
3714
+ return false;
3715
+ }
3716
+ }
3717
+
3718
+ return hasMatchedComponent;
3719
+ }
3720
+
3339
3721
  /**
3340
3722
  * Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
3341
3723
  * This function replaces the previous basicSelectorRegExp.
@@ -3360,6 +3742,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3360
3742
  * @returns {boolean}
3361
3743
  */
3362
3744
  function basicSelectorValidator(selector) {
3745
+ // Guard against extremely long selectors to prevent potential regex performance issues
3746
+ // Reasonable selectors are typically under 1000 characters
3747
+ if (selector.length > 10000) {
3748
+ return false;
3749
+ }
3750
+
3363
3751
  // Validate balanced syntax with attribute tracking and stack-based parentheses matching
3364
3752
  if (!validateBalancedSyntax(selector, true, true)) {
3365
3753
  return false;
@@ -3382,33 +3770,71 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3382
3770
 
3383
3771
  // Check for invalid pseudo-class usage with quoted strings
3384
3772
  // Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
3385
- var pseudoPattern = /::?([a-zA-Z][\w-]*)\(([^)]+)\)/g;
3386
- var pseudoMatch;
3387
- while ((pseudoMatch = pseudoPattern.exec(selector)) !== null) {
3388
- var pseudoName = pseudoMatch[1];
3389
- var pseudoContent = pseudoMatch[2];
3390
-
3391
- // List of pseudo-classes that should not accept quoted strings
3392
- // :lang() - accepts language codes: en, fr-CA
3393
- // :dir() - accepts direction: ltr, rtl
3394
- // :nth-*() - accepts An+B notation: 2n+1, odd, even
3395
- var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
3396
-
3397
- for (var i = 0; i < noQuotesPseudos.length; i++) {
3398
- if (pseudoName === noQuotesPseudos[i] && /['"]/.test(pseudoContent)) {
3399
- return false;
3773
+ // Using iterative parsing instead of regex to avoid exponential backtracking
3774
+ var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
3775
+
3776
+ for (var idx = 0; idx < selector.length; idx++) {
3777
+ // Look for pseudo-class/element start
3778
+ if (selector[idx] === ':') {
3779
+ var pseudoStart = idx;
3780
+ idx++;
3781
+
3782
+ // Skip second colon for pseudo-elements
3783
+ if (idx < selector.length && selector[idx] === ':') {
3784
+ idx++;
3785
+ }
3786
+
3787
+ // Extract pseudo name
3788
+ var nameStart = idx;
3789
+ while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
3790
+ idx++;
3791
+ }
3792
+
3793
+ if (idx === nameStart) {
3794
+ continue; // No name found
3795
+ }
3796
+
3797
+ var pseudoName = selector.substring(nameStart, idx).toLowerCase();
3798
+
3799
+ // Check if this pseudo has arguments
3800
+ if (idx < selector.length && selector[idx] === '(') {
3801
+ idx++;
3802
+ var contentStart = idx;
3803
+ var depth = 1;
3804
+
3805
+ // Find matching closing paren (handle nesting)
3806
+ while (idx < selector.length && depth > 0) {
3807
+ if (selector[idx] === '\\') {
3808
+ idx += 2; // Skip escaped character
3809
+ } else if (selector[idx] === '(') {
3810
+ depth++;
3811
+ idx++;
3812
+ } else if (selector[idx] === ')') {
3813
+ depth--;
3814
+ idx++;
3815
+ } else {
3816
+ idx++;
3817
+ }
3818
+ }
3819
+
3820
+ if (depth === 0) {
3821
+ var pseudoContent = selector.substring(contentStart, idx - 1);
3822
+
3823
+ // Check if this pseudo should not have quoted strings
3824
+ for (var j = 0; j < noQuotesPseudos.length; j++) {
3825
+ if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
3826
+ return false;
3827
+ }
3828
+ }
3829
+ }
3400
3830
  }
3401
3831
  }
3402
3832
  }
3403
3833
 
3404
- // Fallback to a loose regexp for the overall selector structure (without deep paren matching)
3405
- // This is similar to the original, but without nested paren limitations
3406
- // Modified to support namespace selectors: *|element, prefix|element, |element
3407
- // Fixed attribute selector regex to properly handle |=, ~=, ^=, $=, *= operators
3408
- var looseSelectorRegExp = /^((?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|(?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?\*|#[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\.[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\[(?:[^\[\]'"]|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")*(?:\s+[iI])?\]|::?[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+(?:\((.*)\))?|&|\s*[>+~]\s*|\s+)+$/;
3409
- return looseSelectorRegExp.test(selector);
3834
+ // Use the iterative validator to avoid regex backtracking issues
3835
+ return looseSelectorValidator(selector);
3410
3836
  }
3411
-
3837
+
3412
3838
  /**
3413
3839
  * Regular expression to match CSS pseudo-classes with arguments.
3414
3840
  *
@@ -3424,9 +3850,96 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3424
3850
  * - :nth-child(2n+1)
3425
3851
  * - :has(.sel:nth-child(3n))
3426
3852
  * - :not(".foo, .bar")
3427
- * @type {RegExp}
3853
+ *
3854
+ * REPLACED WITH FUNCTION to avoid exponential backtracking.
3428
3855
  */
3429
- var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\)/g;
3856
+
3857
+ /**
3858
+ * Extract pseudo-classes with arguments from a selector using iterative parsing.
3859
+ * Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
3860
+ *
3861
+ * Handles:
3862
+ * - Regular content without parentheses or quotes
3863
+ * - Single-quoted strings
3864
+ * - Double-quoted strings
3865
+ * - Nested parentheses (arbitrary depth)
3866
+ *
3867
+ * @param {string} selector - The CSS selector to parse
3868
+ * @returns {Array} Array of matches, each with: [fullMatch, pseudoName, pseudoArgs, startIndex]
3869
+ */
3870
+ function extractPseudoClasses(selector) {
3871
+ var matches = [];
3872
+
3873
+ for (var i = 0; i < selector.length; i++) {
3874
+ // Look for pseudo-class start (single or double colon)
3875
+ if (selector[i] === ':') {
3876
+ var pseudoStart = i;
3877
+ i++;
3878
+
3879
+ // Skip second colon for pseudo-elements (::)
3880
+ if (i < selector.length && selector[i] === ':') {
3881
+ i++;
3882
+ }
3883
+
3884
+ // Extract pseudo name
3885
+ var nameStart = i;
3886
+ while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
3887
+ i++;
3888
+ }
3889
+
3890
+ if (i === nameStart) {
3891
+ continue; // No name found
3892
+ }
3893
+
3894
+ var pseudoName = selector.substring(nameStart, i);
3895
+
3896
+ // Check if this pseudo has arguments
3897
+ if (i < selector.length && selector[i] === '(') {
3898
+ i++;
3899
+ var argsStart = i;
3900
+ var depth = 1;
3901
+ var inSingleQuote = false;
3902
+ var inDoubleQuote = false;
3903
+
3904
+ // Find matching closing paren (handle nesting and strings)
3905
+ while (i < selector.length && depth > 0) {
3906
+ var ch = selector[i];
3907
+
3908
+ if (ch === '\\') {
3909
+ i += 2; // Skip escaped character
3910
+ } else if (ch === "'" && !inDoubleQuote) {
3911
+ inSingleQuote = !inSingleQuote;
3912
+ i++;
3913
+ } else if (ch === '"' && !inSingleQuote) {
3914
+ inDoubleQuote = !inDoubleQuote;
3915
+ i++;
3916
+ } else if (ch === '(' && !inSingleQuote && !inDoubleQuote) {
3917
+ depth++;
3918
+ i++;
3919
+ } else if (ch === ')' && !inSingleQuote && !inDoubleQuote) {
3920
+ depth--;
3921
+ i++;
3922
+ } else {
3923
+ i++;
3924
+ }
3925
+ }
3926
+
3927
+ if (depth === 0) {
3928
+ var pseudoArgs = selector.substring(argsStart, i - 1);
3929
+ var fullMatch = selector.substring(pseudoStart, i);
3930
+
3931
+ // Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
3932
+ matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
3933
+ }
3934
+
3935
+ // Move back one since loop will increment
3936
+ i--;
3937
+ }
3938
+ }
3939
+ }
3940
+
3941
+ return matches;
3942
+ }
3430
3943
 
3431
3944
  /**
3432
3945
  * Parses a CSS selector string and splits it into parts, handling nested parentheses.
@@ -3521,18 +4034,30 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3521
4034
  return validatedSelectorsCache[selector];
3522
4035
  }
3523
4036
 
3524
- // Use a non-global regex to find all pseudo-classes with arguments
3525
- var pseudoClassMatches = [];
3526
- var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
3527
- var match;
3528
- while ((match = pseudoClassRegExp.exec(selector)) !== null) {
3529
- pseudoClassMatches.push(match);
3530
- }
4037
+ // Use function-based parsing to extract pseudo-classes (avoids backtracking)
4038
+ var pseudoClassMatches = extractPseudoClasses(selector);
3531
4039
 
3532
4040
  for (var j = 0; j < pseudoClassMatches.length; j++) {
3533
4041
  var pseudoClass = pseudoClassMatches[j][1];
3534
4042
  if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
3535
4043
  var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
4044
+
4045
+ // Check if ANY selector in the list contains & (nesting selector)
4046
+ // If so, skip validation for the entire selector list since & will be replaced at runtime
4047
+ var hasAmpersand = false;
4048
+ for (var k = 0; k < nestedSelectors.length; k++) {
4049
+ if (/&/.test(nestedSelectors[k])) {
4050
+ hasAmpersand = true;
4051
+ break;
4052
+ }
4053
+ }
4054
+
4055
+ // If any selector has &, skip validation for this entire pseudo-class
4056
+ if (hasAmpersand) {
4057
+ continue;
4058
+ }
4059
+
4060
+ // Otherwise, validate each selector normally
3536
4061
  for (var i = 0; i < nestedSelectors.length; i++) {
3537
4062
  var nestedSelector = nestedSelectors[i];
3538
4063
  if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
@@ -3569,10 +4094,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3569
4094
  var inAttr = false;
3570
4095
  var inSingleQuote = false;
3571
4096
  var inDoubleQuote = false;
3572
-
4097
+
3573
4098
  for (var i = 0; i < selector.length; i++) {
3574
4099
  var char = selector[i];
3575
-
4100
+
3576
4101
  if (inSingleQuote) {
3577
4102
  if (char === "'" && selector[i - 1] !== "\\") {
3578
4103
  inSingleQuote = false;
@@ -3599,18 +4124,18 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3599
4124
  }
3600
4125
  }
3601
4126
  }
3602
-
4127
+
3603
4128
  if (pipeIndex === -1) {
3604
4129
  return true; // No namespace, always valid
3605
4130
  }
3606
-
4131
+
3607
4132
  var namespacePrefix = selector.substring(0, pipeIndex);
3608
-
4133
+
3609
4134
  // Universal namespace (*|) and default namespace (|) are always valid
3610
4135
  if (namespacePrefix === '*' || namespacePrefix === '') {
3611
4136
  return true;
3612
4137
  }
3613
-
4138
+
3614
4139
  // Check if the custom namespace prefix is defined
3615
4140
  return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
3616
4141
  }
@@ -3619,22 +4144,92 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3619
4144
  * Processes a CSS selector text
3620
4145
  *
3621
4146
  * @param {string} selectorText - The CSS selector text to process
3622
- * @returns {string} The processed selector text with normalized whitespace
4147
+ * @returns {string} The processed selector text with normalized whitespace and invalid selectors removed
3623
4148
  */
3624
4149
  function processSelectorText(selectorText) {
3625
- // TODO: Remove invalid selectors that appears inside pseudo classes
3626
- // TODO: The same processing here needs to be reused in CSSStyleRule.selectorText setter
3627
- // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
3628
-
3629
- /**
3630
- * Normalizes whitespace and preserving quoted strings.
3631
- * Replaces all newline characters (CRLF, CR, or LF) with spaces while keeping quoted
3632
- * strings (single or double quotes) intact, including any escaped characters within them.
3633
- */
3634
- return selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
4150
+ // Normalize whitespace first
4151
+ var normalized = selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function (match, _, newline) {
3635
4152
  if (newline) return " ";
3636
4153
  return match;
3637
4154
  });
4155
+
4156
+ // Recursively process pseudo-classes to handle nesting
4157
+ return processNestedPseudoClasses(normalized);
4158
+ }
4159
+
4160
+ /**
4161
+ * Recursively processes pseudo-classes to filter invalid selectors
4162
+ *
4163
+ * @param {string} selectorText - The CSS selector text to process
4164
+ * @param {number} depth - Current recursion depth (to prevent infinite loops)
4165
+ * @returns {string} The processed selector text with invalid selectors removed
4166
+ */
4167
+ function processNestedPseudoClasses(selectorText, depth) {
4168
+ // Prevent infinite recursion
4169
+ if (typeof depth === 'undefined') {
4170
+ depth = 0;
4171
+ }
4172
+ if (depth > 10) {
4173
+ return selectorText;
4174
+ }
4175
+
4176
+ var pseudoClassMatches = extractPseudoClasses(selectorText);
4177
+
4178
+ // If no pseudo-classes found, return as-is
4179
+ if (pseudoClassMatches.length === 0) {
4180
+ return selectorText;
4181
+ }
4182
+
4183
+ // Build result by processing matches from right to left (to preserve positions)
4184
+ var result = selectorText;
4185
+
4186
+ for (var j = pseudoClassMatches.length - 1; j >= 0; j--) {
4187
+ var pseudoClass = pseudoClassMatches[j][1];
4188
+ if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
4189
+ var fullMatch = pseudoClassMatches[j][0];
4190
+ var pseudoArgs = pseudoClassMatches[j][2];
4191
+ var matchStart = pseudoClassMatches[j][3];
4192
+
4193
+ // Check if ANY selector contains & BEFORE processing
4194
+ var nestedSelectorsRaw = parseAndSplitNestedSelectors(pseudoArgs);
4195
+ var hasAmpersand = false;
4196
+ for (var k = 0; k < nestedSelectorsRaw.length; k++) {
4197
+ if (/&/.test(nestedSelectorsRaw[k])) {
4198
+ hasAmpersand = true;
4199
+ break;
4200
+ }
4201
+ }
4202
+
4203
+ // If & is present, skip all processing (keep everything unchanged)
4204
+ if (hasAmpersand) {
4205
+ continue;
4206
+ }
4207
+
4208
+ // Recursively process the arguments
4209
+ var processedArgs = processNestedPseudoClasses(pseudoArgs, depth + 1);
4210
+ var nestedSelectors = parseAndSplitNestedSelectors(processedArgs);
4211
+
4212
+ // Filter out invalid selectors
4213
+ var validSelectors = [];
4214
+ for (var i = 0; i < nestedSelectors.length; i++) {
4215
+ var nestedSelector = nestedSelectors[i];
4216
+ if (basicSelectorValidator(nestedSelector)) {
4217
+ validSelectors.push(nestedSelector);
4218
+ }
4219
+ }
4220
+
4221
+ // Reconstruct the pseudo-class with only valid selectors
4222
+ var newArgs = validSelectors.join(', ');
4223
+ var newPseudoClass = ':' + pseudoClass + '(' + newArgs + ')';
4224
+
4225
+ // Replace in the result string using position (processing right to left preserves positions)
4226
+ result = result.substring(0, matchStart) + newPseudoClass + result.substring(matchStart + fullMatch.length);
4227
+ }
4228
+ }
4229
+
4230
+ return result;
4231
+
4232
+ return normalized;
3638
4233
  }
3639
4234
 
3640
4235
  /**
@@ -3648,6 +4243,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3648
4243
  // TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
3649
4244
  // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
3650
4245
 
4246
+ // Check for empty selector lists in pseudo-classes (e.g., :is(), :not(), :where(), :has())
4247
+ // These are invalid after filtering out invalid selectors
4248
+ if (/:(?:is|not|where|has)\(\s*\)/.test(selectorText)) {
4249
+ return false;
4250
+ }
4251
+
3651
4252
  // Check for newlines inside single or double quotes using regex
3652
4253
  // This matches any quoted string (single or double) containing a newline
3653
4254
  var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
@@ -3668,6 +4269,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3668
4269
  return true;
3669
4270
  }
3670
4271
 
4272
+ function pushToAncestorRules(rule) {
4273
+ if (ancestorRules.indexOf(rule) === -1) {
4274
+ ancestorRules.push(rule);
4275
+ }
4276
+ }
4277
+
3671
4278
  function parseError(message, isNested) {
3672
4279
  var lines = token.substring(0, i).split('\n');
3673
4280
  var lineCount = lines.length;
@@ -3681,12 +4288,22 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3681
4288
  // Print the error but continue parsing the sheet
3682
4289
  try {
3683
4290
  throw error;
3684
- } catch(e) {
4291
+ } catch (e) {
3685
4292
  errorHandler && errorHandler(e);
3686
4293
  }
3687
4294
  };
3688
4295
 
4296
+ // Helper functions to check character types
4297
+ function isSelectorStartChar(char) {
4298
+ return '.:#&*['.indexOf(char) !== -1;
4299
+ }
4300
+
4301
+ function isWhitespaceChar(char) {
4302
+ return ' \t\n\r'.indexOf(char) !== -1;
4303
+ }
4304
+
3689
4305
  var endingIndex = token.length - 1;
4306
+ var initialEndingIndex = endingIndex;
3690
4307
 
3691
4308
  for (var character; (character = token.charAt(i)); i++) {
3692
4309
  if (i === endingIndex) {
@@ -3695,804 +4312,1031 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3695
4312
  case "namespaceRule":
3696
4313
  case "layerBlock":
3697
4314
  if (character !== ";") {
3698
- token += ";"
4315
+ token += ";";
4316
+ endingIndex += 1;
4317
+ }
4318
+ break;
4319
+ case "value":
4320
+ if (character !== "}") {
4321
+ if (character === ";") {
4322
+ token += "}"
4323
+ } else {
4324
+ token += ";";
4325
+ }
4326
+ endingIndex += 1;
4327
+ break;
4328
+ }
4329
+ case "name":
4330
+ case "before-name":
4331
+ if (character === "}") {
4332
+ token += " "
4333
+ } else {
4334
+ token += "}"
4335
+ }
4336
+ endingIndex += 1
4337
+ break;
4338
+ case "before-selector":
4339
+ if (character !== "}" && currentScope !== styleSheet) {
4340
+ token += "}"
4341
+ endingIndex += 1
4342
+ break;
3699
4343
  }
3700
4344
  }
3701
4345
  }
3702
-
3703
- switch (character) {
3704
4346
 
3705
- case " ":
3706
- case "\t":
3707
- case "\r":
3708
- case "\n":
3709
- case "\f":
3710
- if (SIGNIFICANT_WHITESPACE[state]) {
3711
- buffer += character;
3712
- }
3713
- break;
4347
+ // Handle escape sequences before processing special characters
4348
+ // If we encounter a backslash, add both the backslash and the next character to buffer
4349
+ // and skip the next iteration to prevent the escaped character from being interpreted
4350
+ if (character === '\\' && i + 1 < token.length) {
4351
+ buffer += character + token.charAt(i + 1);
4352
+ i++; // Skip the next character
4353
+ continue;
4354
+ }
4355
+
4356
+ switch (character) {
3714
4357
 
3715
- // String
3716
- case '"':
3717
- index = i + 1;
3718
- do {
3719
- index = token.indexOf('"', index) + 1;
3720
- if (!index) {
3721
- parseError('Unmatched "');
4358
+ case " ":
4359
+ case "\t":
4360
+ case "\r":
4361
+ case "\n":
4362
+ case "\f":
4363
+ if (SIGNIFICANT_WHITESPACE[state]) {
4364
+ buffer += character;
3722
4365
  }
3723
- } while (token[index - 2] === '\\');
3724
- if (index === 0) {
3725
4366
  break;
3726
- }
3727
- buffer += token.slice(i, index);
3728
- i = index - 1;
3729
- switch (state) {
3730
- case 'before-value':
3731
- state = 'value';
3732
- break;
3733
- case 'importRule-begin':
3734
- state = 'importRule';
3735
- if (i === endingIndex) {
3736
- token += ';'
4367
+
4368
+ // String
4369
+ case '"':
4370
+ index = i + 1;
4371
+ do {
4372
+ index = token.indexOf('"', index) + 1;
4373
+ if (!index) {
4374
+ parseError('Unmatched "');
3737
4375
  }
4376
+ } while (token[index - 2] === '\\');
4377
+ if (index === 0) {
3738
4378
  break;
3739
- case 'namespaceRule-begin':
3740
- state = 'namespaceRule';
3741
- if (i === endingIndex) {
3742
- token += ';'
4379
+ }
4380
+ buffer += token.slice(i, index);
4381
+ i = index - 1;
4382
+ switch (state) {
4383
+ case 'before-value':
4384
+ state = 'value';
4385
+ break;
4386
+ case 'importRule-begin':
4387
+ state = 'importRule';
4388
+ if (i === endingIndex) {
4389
+ token += ';'
4390
+ }
4391
+ break;
4392
+ case 'namespaceRule-begin':
4393
+ state = 'namespaceRule';
4394
+ if (i === endingIndex) {
4395
+ token += ';'
4396
+ }
4397
+ break;
4398
+ }
4399
+ break;
4400
+
4401
+ case "'":
4402
+ index = i + 1;
4403
+ do {
4404
+ index = token.indexOf("'", index) + 1;
4405
+ if (!index) {
4406
+ parseError("Unmatched '");
3743
4407
  }
4408
+ } while (token[index - 2] === '\\');
4409
+ if (index === 0) {
3744
4410
  break;
3745
- }
3746
- break;
4411
+ }
4412
+ buffer += token.slice(i, index);
4413
+ i = index - 1;
4414
+ switch (state) {
4415
+ case 'before-value':
4416
+ state = 'value';
4417
+ break;
4418
+ case 'importRule-begin':
4419
+ state = 'importRule';
4420
+ break;
4421
+ case 'namespaceRule-begin':
4422
+ state = 'namespaceRule';
4423
+ break;
4424
+ }
4425
+ break;
3747
4426
 
3748
- case "'":
3749
- index = i + 1;
3750
- do {
3751
- index = token.indexOf("'", index) + 1;
3752
- if (!index) {
3753
- parseError("Unmatched '");
4427
+ // Comment
4428
+ case "/":
4429
+ if (token.charAt(i + 1) === "*") {
4430
+ i += 2;
4431
+ index = token.indexOf("*/", i);
4432
+ if (index === -1) {
4433
+ i = token.length - 1;
4434
+ buffer = "";
4435
+ } else {
4436
+ i = index + 1;
4437
+ }
4438
+ } else {
4439
+ buffer += character;
4440
+ }
4441
+ if (state === "importRule-begin") {
4442
+ buffer += " ";
4443
+ state = "importRule";
4444
+ }
4445
+ if (state === "namespaceRule-begin") {
4446
+ buffer += " ";
4447
+ state = "namespaceRule";
3754
4448
  }
3755
- } while (token[index - 2] === '\\');
3756
- if (index === 0) {
3757
4449
  break;
3758
- }
3759
- buffer += token.slice(i, index);
3760
- i = index - 1;
3761
- switch (state) {
3762
- case 'before-value':
3763
- state = 'value';
4450
+
4451
+ // At-rule
4452
+ case "@":
4453
+ if (nestedSelectorRule) {
4454
+ if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
4455
+ currentScope.cssRules.push(styleRule);
4456
+ }
4457
+ if (nestedSelectorRule.parentRule && nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
4458
+ styleRule = nestedSelectorRule.parentRule;
4459
+ }
4460
+ // Don't reset nestedSelectorRule here - preserve it through @-rules
4461
+ }
4462
+ if (token.indexOf("@-moz-document", i) === i) {
4463
+ validateAtRule("@-moz-document", function () {
4464
+ state = "documentRule-begin";
4465
+ documentRule = new CSSOM.CSSDocumentRule();
4466
+ documentRule.__starts = i;
4467
+ i += "-moz-document".length;
4468
+ });
4469
+ buffer = "";
3764
4470
  break;
3765
- case 'importRule-begin':
3766
- state = 'importRule';
4471
+ } else if (token.indexOf("@media", i) === i) {
4472
+ validateAtRule("@media", function () {
4473
+ state = "atBlock";
4474
+ mediaRule = new CSSOM.CSSMediaRule();
4475
+ mediaRule.__starts = i;
4476
+ i += "media".length;
4477
+ });
4478
+ buffer = "";
3767
4479
  break;
3768
- case 'namespaceRule-begin':
3769
- state = 'namespaceRule';
4480
+ } else if (token.indexOf("@container", i) === i) {
4481
+ validateAtRule("@container", function () {
4482
+ state = "containerBlock";
4483
+ containerRule = new CSSOM.CSSContainerRule();
4484
+ containerRule.__starts = i;
4485
+ i += "container".length;
4486
+ });
4487
+ buffer = "";
3770
4488
  break;
3771
- }
3772
- break;
3773
-
3774
- // Comment
3775
- case "/":
3776
- if (token.charAt(i + 1) === "*") {
3777
- i += 2;
3778
- index = token.indexOf("*/", i);
3779
- if (index === -1) {
3780
- i = token.length - 1;
4489
+ } else if (token.indexOf("@counter-style", i) === i) {
4490
+ validateAtRule("@counter-style", function () {
4491
+ state = "counterStyleBlock"
4492
+ counterStyleRule = new CSSOM.CSSCounterStyleRule();
4493
+ counterStyleRule.__starts = i;
4494
+ i += "counter-style".length;
4495
+ }, true);
3781
4496
  buffer = "";
3782
- } else {
3783
- i = index + 1;
3784
- }
3785
- } else {
3786
- buffer += character;
3787
- }
3788
- if (state === "importRule-begin") {
3789
- buffer += " ";
3790
- state = "importRule";
3791
- }
3792
- if (state === "namespaceRule-begin") {
3793
- buffer += " ";
3794
- state = "namespaceRule";
3795
- }
3796
- break;
3797
-
3798
- // At-rule
3799
- case "@":
3800
- if (token.indexOf("@-moz-document", i) === i) {
3801
- validateAtRule("@-moz-document", function(){
3802
- state = "documentRule-begin";
3803
- documentRule = new CSSOM.CSSDocumentRule();
3804
- documentRule.__starts = i;
3805
- i += "-moz-document".length;
3806
- });
3807
- buffer = "";
3808
- break;
3809
- } else if (token.indexOf("@media", i) === i) {
3810
- validateAtRule("@media", function(){
3811
- state = "atBlock";
3812
- mediaRule = new CSSOM.CSSMediaRule();
3813
- mediaRule.__starts = i;
3814
- i += "media".length;
3815
- });
3816
- buffer = "";
3817
- break;
3818
- } else if (token.indexOf("@container", i) === i) {
3819
- validateAtRule("@container", function(){
3820
- state = "containerBlock";
3821
- containerRule = new CSSOM.CSSContainerRule();
3822
- containerRule.__starts = i;
3823
- i += "container".length;
3824
- });
3825
- buffer = "";
3826
- break;
3827
- } else if (token.indexOf("@counter-style", i) === i) {
3828
- validateAtRule("@counter-style", function(){
3829
- state = "counterStyleBlock"
3830
- counterStyleRule = new CSSOM.CSSCounterStyleRule();
3831
- counterStyleRule.__starts = i;
3832
- i += "counter-style".length;
3833
- }, true);
3834
- buffer = "";
3835
- break;
3836
- } else if (token.indexOf("@scope", i) === i) {
3837
- validateAtRule("@scope", function(){
3838
- state = "scopeBlock";
3839
- scopeRule = new CSSOM.CSSScopeRule();
3840
- scopeRule.__starts = i;
3841
- i += "scope".length;
3842
- });
3843
- buffer = "";
3844
- break;
3845
- } else if (token.indexOf("@layer", i) === i) {
3846
- validateAtRule("@layer", function(){
3847
- state = "layerBlock"
3848
- layerBlockRule = new CSSOM.CSSLayerBlockRule();
3849
- layerBlockRule.__starts = i;
3850
- i += "layer".length;
3851
- });
3852
- buffer = "";
3853
- break;
3854
- } else if (token.indexOf("@page", i) === i) {
3855
- validateAtRule("@page", function(){
3856
- state = "pageBlock"
3857
- pageRule = new CSSOM.CSSPageRule();
3858
- pageRule.__starts = i;
3859
- i += "page".length;
3860
- });
3861
- buffer = "";
3862
- break;
3863
- } else if (token.indexOf("@supports", i) === i) {
3864
- validateAtRule("@supports", function(){
3865
- state = "conditionBlock";
3866
- supportsRule = new CSSOM.CSSSupportsRule();
3867
- supportsRule.__starts = i;
3868
- i += "supports".length;
3869
- });
3870
- buffer = "";
3871
- break;
3872
- } else if (token.indexOf("@host", i) === i) {
3873
- validateAtRule("@host", function(){
3874
- state = "hostRule-begin";
3875
- i += "host".length;
3876
- hostRule = new CSSOM.CSSHostRule();
3877
- hostRule.__starts = i;
3878
- });
3879
- buffer = "";
3880
- break;
3881
- } else if (token.indexOf("@starting-style", i) === i) {
3882
- validateAtRule("@starting-style", function(){
3883
- state = "startingStyleRule-begin";
3884
- i += "starting-style".length;
3885
- startingStyleRule = new CSSOM.CSSStartingStyleRule();
3886
- startingStyleRule.__starts = i;
3887
- });
3888
- buffer = "";
3889
- break;
3890
- } else if (token.indexOf("@import", i) === i) {
3891
- buffer = "";
3892
- validateAtRule("@import", function(){
3893
- state = "importRule-begin";
3894
- i += "import".length;
3895
- buffer += "@import";
3896
- }, true);
3897
- break;
3898
- } else if (token.indexOf("@namespace", i) === i) {
3899
- buffer = "";
3900
- validateAtRule("@namespace", function(){
3901
- state = "namespaceRule-begin";
3902
- i += "namespace".length;
3903
- buffer += "@namespace";
3904
- }, true);
3905
- break;
3906
- } else if (token.indexOf("@font-face", i) === i) {
3907
- buffer = "";
3908
- validateAtRule("@font-face", function(){
3909
- state = "fontFaceRule-begin";
3910
- i += "font-face".length;
3911
- fontFaceRule = new CSSOM.CSSFontFaceRule();
3912
- fontFaceRule.__starts = i;
3913
- }, true);
3914
- break;
3915
- } else {
3916
- atKeyframesRegExp.lastIndex = i;
3917
- var matchKeyframes = atKeyframesRegExp.exec(token);
3918
- if (matchKeyframes && matchKeyframes.index === i) {
3919
- state = "keyframesRule-begin";
3920
- keyframesRule = new CSSOM.CSSKeyframesRule();
3921
- keyframesRule.__starts = i;
3922
- keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
3923
- i += matchKeyframes[0].length - 1;
4497
+ break;
4498
+ } else if (token.indexOf("@scope", i) === i) {
4499
+ validateAtRule("@scope", function () {
4500
+ state = "scopeBlock";
4501
+ scopeRule = new CSSOM.CSSScopeRule();
4502
+ scopeRule.__starts = i;
4503
+ i += "scope".length;
4504
+ });
3924
4505
  buffer = "";
3925
4506
  break;
3926
- } else if (state === "selector") {
3927
- state = "atRule";
3928
- }
3929
- }
3930
- buffer += character;
3931
- break;
4507
+ } else if (token.indexOf("@layer", i) === i) {
4508
+ validateAtRule("@layer", function () {
4509
+ state = "layerBlock"
4510
+ layerBlockRule = new CSSOM.CSSLayerBlockRule();
4511
+ layerBlockRule.__starts = i;
4512
+ i += "layer".length;
4513
+ });
4514
+ buffer = "";
4515
+ break;
4516
+ } else if (token.indexOf("@page", i) === i) {
4517
+ validateAtRule("@page", function () {
4518
+ state = "pageBlock"
4519
+ pageRule = new CSSOM.CSSPageRule();
4520
+ pageRule.__starts = i;
4521
+ i += "page".length;
4522
+ });
4523
+ buffer = "";
4524
+ break;
4525
+ } else if (token.indexOf("@supports", i) === i) {
4526
+ validateAtRule("@supports", function () {
4527
+ state = "conditionBlock";
4528
+ supportsRule = new CSSOM.CSSSupportsRule();
4529
+ supportsRule.__starts = i;
4530
+ i += "supports".length;
4531
+ });
4532
+ buffer = "";
4533
+ break;
4534
+ } else if (token.indexOf("@host", i) === i) {
4535
+ validateAtRule("@host", function () {
4536
+ state = "hostRule-begin";
4537
+ i += "host".length;
4538
+ hostRule = new CSSOM.CSSHostRule();
4539
+ hostRule.__starts = i;
4540
+ });
4541
+ buffer = "";
4542
+ break;
4543
+ } else if (token.indexOf("@starting-style", i) === i) {
4544
+ validateAtRule("@starting-style", function () {
4545
+ state = "startingStyleRule-begin";
4546
+ i += "starting-style".length;
4547
+ startingStyleRule = new CSSOM.CSSStartingStyleRule();
4548
+ startingStyleRule.__starts = i;
4549
+ });
4550
+ buffer = "";
4551
+ break;
4552
+ } else if (token.indexOf("@import", i) === i) {
4553
+ buffer = "";
4554
+ validateAtRule("@import", function () {
4555
+ state = "importRule-begin";
4556
+ i += "import".length;
4557
+ buffer += "@import";
4558
+ }, true);
4559
+ break;
4560
+ } else if (token.indexOf("@namespace", i) === i) {
4561
+ buffer = "";
4562
+ validateAtRule("@namespace", function () {
4563
+ state = "namespaceRule-begin";
4564
+ i += "namespace".length;
4565
+ buffer += "@namespace";
4566
+ }, true);
4567
+ break;
4568
+ } else if (token.indexOf("@font-face", i) === i) {
4569
+ buffer = "";
4570
+ // @font-face can be nested only inside CSSScopeRule or CSSConditionRule
4571
+ // and only if there's no CSSStyleRule in the parent chain
4572
+ var cannotBeNested = true;
4573
+ if (currentScope !== topScope) {
4574
+ var hasStyleRuleInChain = false;
4575
+ var hasValidParent = false;
4576
+
4577
+ // Check currentScope
4578
+ if (currentScope.constructor.name === 'CSSStyleRule') {
4579
+ hasStyleRuleInChain = true;
4580
+ } else if (currentScope instanceof CSSOM.CSSScopeRule || currentScope instanceof CSSOM.CSSConditionRule) {
4581
+ hasValidParent = true;
4582
+ }
3932
4583
 
3933
- case "{":
3934
- if (currentScope === styleSheet) {
3935
- nestedSelectorRule = null;
3936
- }
3937
- if (state === 'before-selector') {
3938
- parseError("Unexpected {");
3939
- i = ignoreBalancedBlock(i, token.slice(i));
3940
- break;
3941
- }
3942
- if (state === "selector" || state === "atRule") {
3943
- if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
3944
- var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
3945
- if (ruleClosingMatch) {
3946
- styleRule = null;
4584
+ // Check ancestorRules for CSSStyleRule
4585
+ if (!hasStyleRuleInChain) {
4586
+ for (var j = 0; j < ancestorRules.length; j++) {
4587
+ if (ancestorRules[j].constructor.name === 'CSSStyleRule') {
4588
+ hasStyleRuleInChain = true;
4589
+ break;
4590
+ }
4591
+ if (ancestorRules[j] instanceof CSSOM.CSSScopeRule || ancestorRules[j] instanceof CSSOM.CSSConditionRule) {
4592
+ hasValidParent = true;
4593
+ }
4594
+ }
4595
+ }
4596
+
4597
+ // Allow nesting if we have a valid parent and no style rule in the chain
4598
+ if (hasValidParent && !hasStyleRuleInChain) {
4599
+ cannotBeNested = false;
4600
+ }
4601
+ }
4602
+ validateAtRule("@font-face", function () {
4603
+ state = "fontFaceRule-begin";
4604
+ i += "font-face".length;
4605
+ fontFaceRule = new CSSOM.CSSFontFaceRule();
4606
+ fontFaceRule.__starts = i;
4607
+ }, cannotBeNested);
4608
+ break;
4609
+ } else {
4610
+ atKeyframesRegExp.lastIndex = i;
4611
+ var matchKeyframes = atKeyframesRegExp.exec(token);
4612
+ if (matchKeyframes && matchKeyframes.index === i) {
4613
+ state = "keyframesRule-begin";
4614
+ keyframesRule = new CSSOM.CSSKeyframesRule();
4615
+ keyframesRule.__starts = i;
4616
+ keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
4617
+ i += matchKeyframes[0].length - 1;
3947
4618
  buffer = "";
3948
- state = "before-selector";
3949
- i += ruleClosingMatch.index + ruleClosingMatch[0].length;
3950
4619
  break;
4620
+ } else if (state === "selector") {
4621
+ state = "atRule";
3951
4622
  }
3952
4623
  }
4624
+ buffer += character;
4625
+ break;
3953
4626
 
3954
- if (parentRule) {
3955
- styleRule.__parentRule = parentRule;
3956
- ancestorRules.push(parentRule);
3957
- }
3958
-
3959
- currentScope = parentRule = styleRule;
3960
- styleRule.selectorText = processSelectorText(buffer.trim());
3961
- styleRule.style.__starts = i;
3962
- styleRule.__parentStyleSheet = styleSheet;
3963
- buffer = "";
3964
- state = "before-name";
3965
- } else if (state === "atBlock") {
3966
- mediaRule.media.mediaText = buffer.trim();
3967
-
3968
- if (parentRule) {
3969
- mediaRule.__parentRule = parentRule;
3970
- ancestorRules.push(parentRule);
3971
- }
3972
-
3973
- currentScope = parentRule = mediaRule;
3974
- mediaRule.__parentStyleSheet = styleSheet;
3975
- buffer = "";
3976
- state = "before-selector";
3977
- } else if (state === "containerBlock") {
3978
- containerRule.__conditionText = buffer.trim();
3979
-
3980
- if (parentRule) {
3981
- containerRule.__parentRule = parentRule;
3982
- ancestorRules.push(parentRule);
3983
- }
3984
- currentScope = parentRule = containerRule;
3985
- containerRule.__parentStyleSheet = styleSheet;
3986
- buffer = "";
3987
- state = "before-selector";
3988
- } else if (state === "counterStyleBlock") {
3989
- // TODO: Validate counter-style name. At least that it cannot be empty nor multiple
3990
- counterStyleRule.name = buffer.trim().replace(/\n/g, "");
3991
- currentScope = parentRule = counterStyleRule;
3992
- counterStyleRule.__parentStyleSheet = styleSheet;
3993
- buffer = "";
3994
- } else if (state === "conditionBlock") {
3995
- supportsRule.__conditionText = buffer.trim();
3996
-
3997
- if (parentRule) {
3998
- supportsRule.__parentRule = parentRule;
3999
- ancestorRules.push(parentRule);
4000
- }
4001
-
4002
- currentScope = parentRule = supportsRule;
4003
- supportsRule.__parentStyleSheet = styleSheet;
4004
- buffer = "";
4005
- state = "before-selector";
4006
- } else if (state === "scopeBlock") {
4007
- var parsedScopePrelude = parseScopePrelude(buffer.trim());
4008
-
4009
- if (parsedScopePrelude.hasStart) {
4010
- scopeRule.__start = parsedScopePrelude.startSelector;
4627
+ case "{":
4628
+ if (currentScope === topScope) {
4629
+ nestedSelectorRule = null;
4011
4630
  }
4012
- if (parsedScopePrelude.hasEnd) {
4013
- scopeRule.__end = parsedScopePrelude.endSelector;
4014
- }
4015
- if (parsedScopePrelude.hasOnlyEnd) {
4016
- scopeRule.__end = parsedScopePrelude.endSelector;
4631
+ if (state === 'before-selector') {
4632
+ parseError("Unexpected {");
4633
+ i = ignoreBalancedBlock(i, token.slice(i));
4634
+ break;
4017
4635
  }
4636
+ if (state === "selector" || state === "atRule") {
4637
+ if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
4638
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4639
+ if (ruleClosingMatch) {
4640
+ styleRule = null;
4641
+ buffer = "";
4642
+ state = "before-selector";
4643
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length;
4644
+ break;
4645
+ }
4646
+ }
4018
4647
 
4019
- if (parentRule) {
4020
- scopeRule.__parentRule = parentRule;
4021
- ancestorRules.push(parentRule);
4022
- }
4023
- currentScope = parentRule = scopeRule;
4024
- scopeRule.__parentStyleSheet = styleSheet;
4025
- buffer = "";
4026
- state = "before-selector";
4027
- } else if (state === "layerBlock") {
4028
- layerBlockRule.name = buffer.trim();
4648
+ // Ensure styleRule exists before trying to set properties on it
4649
+ if (!styleRule) {
4650
+ styleRule = new CSSOM.CSSStyleRule();
4651
+ styleRule.__starts = i;
4652
+ }
4029
4653
 
4030
- var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4654
+ var originalParentRule = parentRule;
4031
4655
 
4032
- if (isValidName) {
4033
4656
  if (parentRule) {
4034
- layerBlockRule.__parentRule = parentRule;
4035
- ancestorRules.push(parentRule);
4657
+ styleRule.__parentRule = parentRule;
4658
+ pushToAncestorRules(parentRule);
4036
4659
  }
4037
-
4038
- currentScope = parentRule = layerBlockRule;
4039
- layerBlockRule.__parentStyleSheet = styleSheet;
4040
- }
4041
- buffer = "";
4042
- state = "before-selector";
4043
- } else if (state === "pageBlock") {
4044
- pageRule.selectorText = buffer.trim();
4045
-
4046
- if (parentRule) {
4047
- pageRule.__parentRule = parentRule;
4048
- ancestorRules.push(parentRule);
4049
- }
4050
-
4051
- currentScope = parentRule = pageRule;
4052
- pageRule.__parentStyleSheet = styleSheet;
4053
- styleRule = pageRule;
4054
- buffer = "";
4055
- state = "before-name";
4056
- } else if (state === "hostRule-begin") {
4057
- if (parentRule) {
4058
- ancestorRules.push(parentRule);
4059
- }
4060
-
4061
- currentScope = parentRule = hostRule;
4062
- hostRule.__parentStyleSheet = styleSheet;
4063
- buffer = "";
4064
- state = "before-selector";
4065
- } else if (state === "startingStyleRule-begin") {
4066
- if (parentRule) {
4067
- startingStyleRule.__parentRule = parentRule;
4068
- ancestorRules.push(parentRule);
4069
- }
4070
4660
 
4071
- currentScope = parentRule = startingStyleRule;
4072
- startingStyleRule.__parentStyleSheet = styleSheet;
4073
- buffer = "";
4074
- state = "before-selector";
4075
-
4076
- } else if (state === "fontFaceRule-begin") {
4077
- if (parentRule) {
4078
- fontFaceRule.__parentRule = parentRule;
4079
- }
4080
- fontFaceRule.__parentStyleSheet = styleSheet;
4081
- styleRule = fontFaceRule;
4082
- buffer = "";
4083
- state = "before-name";
4084
- } else if (state === "keyframesRule-begin") {
4085
- keyframesRule.name = buffer.trim();
4086
- if (parentRule) {
4087
- ancestorRules.push(parentRule);
4088
- keyframesRule.__parentRule = parentRule;
4089
- }
4090
- keyframesRule.__parentStyleSheet = styleSheet;
4091
- currentScope = parentRule = keyframesRule;
4092
- buffer = "";
4093
- state = "keyframeRule-begin";
4094
- } else if (state === "keyframeRule-begin") {
4095
- styleRule = new CSSOM.CSSKeyframeRule();
4096
- styleRule.keyText = buffer.trim();
4097
- styleRule.__starts = i;
4098
- buffer = "";
4099
- state = "before-name";
4100
- } else if (state === "documentRule-begin") {
4101
- // FIXME: what if this '{' is in the url text of the match function?
4102
- documentRule.matcher.matcherText = buffer.trim();
4103
- if (parentRule) {
4104
- ancestorRules.push(parentRule);
4105
- documentRule.__parentRule = parentRule;
4106
- }
4107
- currentScope = parentRule = documentRule;
4108
- documentRule.__parentStyleSheet = styleSheet;
4109
- buffer = "";
4110
- state = "before-selector";
4111
- } else if (state === "before-name" || state === "name") {
4112
- if (styleRule.constructor.name === "CSSNestedDeclarations") {
4113
- if (styleRule.style.length) {
4114
- parentRule.cssRules.push(styleRule);
4115
- styleRule.__parentRule = parentRule;
4116
- styleRule.__parentStyleSheet = styleSheet;
4117
- ancestorRules.push(parentRule);
4661
+ currentScope = parentRule = styleRule;
4662
+ var processedSelectorText = processSelectorText(buffer.trim());
4663
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4664
+ if (originalParentRule && originalParentRule.constructor.name === "CSSStyleRule") {
4665
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4666
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4667
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4668
+ }).join(', ');
4118
4669
  } else {
4119
- // If the styleRule is empty, we can assume that it's a nested selector
4120
- ancestorRules.push(parentRule);
4670
+ styleRule.selectorText = processedSelectorText;
4121
4671
  }
4122
- } else {
4123
- currentScope = parentRule = styleRule;
4124
- ancestorRules.push(parentRule);
4672
+ styleRule.style.__starts = i;
4125
4673
  styleRule.__parentStyleSheet = styleSheet;
4126
- }
4127
-
4128
- styleRule = new CSSOM.CSSStyleRule();
4129
- var processedSelectorText = processSelectorText(buffer.trim());
4130
- // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4131
- if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
4132
- styleRule.selectorText = processedSelectorText;
4133
- } else {
4134
- styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
4135
- // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4136
- return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4137
- }).join(', ');
4138
- }
4139
- styleRule.style.__starts = i - buffer.length;
4140
- styleRule.__parentRule = parentRule;
4141
- nestedSelectorRule = styleRule;
4142
-
4143
- buffer = "";
4144
- state = "before-name";
4145
- }
4146
- break;
4147
-
4148
- case ":":
4149
- if (state === "name") {
4150
- // It can be a nested selector, let's check
4151
- var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
4152
- var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
4153
- if (hasOpenBraceBefore) {
4154
- // Is a selector
4155
- buffer += character;
4156
- } else {
4157
- // Is a declaration
4158
- name = buffer.trim();
4159
4674
  buffer = "";
4160
- state = "before-value";
4161
- }
4162
- } else {
4163
- buffer += character;
4164
- }
4165
- break;
4166
-
4167
- case "(":
4168
- if (state === 'value') {
4169
- // ie css expression mode
4170
- if (buffer.trim() === 'expression') {
4171
- var info = (new CSSOM.CSSValueExpression(token, i)).parse();
4675
+ state = "before-name";
4676
+ } else if (state === "atBlock") {
4677
+ mediaRule.media.mediaText = buffer.trim();
4172
4678
 
4173
- if (info.error) {
4174
- parseError(info.error);
4175
- } else {
4176
- buffer += info.expression;
4177
- i = info.idx;
4679
+ if (parentRule) {
4680
+ mediaRule.__parentRule = parentRule;
4681
+ pushToAncestorRules(parentRule);
4682
+ // If entering @media from within a CSSStyleRule, set nestedSelectorRule
4683
+ // so that & selectors and declarations work correctly inside
4684
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4685
+ nestedSelectorRule = parentRule;
4686
+ }
4178
4687
  }
4179
- } else {
4180
- state = 'value-parenthesis';
4181
- //always ensure this is reset to 1 on transition
4182
- //from value to value-parenthesis
4183
- valueParenthesisDepth = 1;
4184
- buffer += character;
4185
- }
4186
- } else if (state === 'value-parenthesis') {
4187
- valueParenthesisDepth++;
4188
- buffer += character;
4189
- } else {
4190
- buffer += character;
4191
- }
4192
- break;
4193
4688
 
4194
- case ")":
4195
- if (state === 'value-parenthesis') {
4196
- valueParenthesisDepth--;
4197
- if (valueParenthesisDepth === 0) state = 'value';
4198
- }
4199
- buffer += character;
4200
- break;
4201
-
4202
- case "!":
4203
- if (state === "value" && token.indexOf("!important", i) === i) {
4204
- priority = "important";
4205
- i += "important".length;
4206
- } else {
4207
- buffer += character;
4208
- }
4209
- break;
4689
+ currentScope = parentRule = mediaRule;
4690
+ pushToAncestorRules(mediaRule);
4691
+ mediaRule.__parentStyleSheet = styleSheet;
4692
+ styleRule = null; // Reset styleRule when entering @-rule
4693
+ buffer = "";
4694
+ state = "before-selector";
4695
+ } else if (state === "containerBlock") {
4696
+ containerRule.__conditionText = buffer.trim();
4210
4697
 
4211
- case ";":
4212
- switch (state) {
4213
- case "before-value":
4214
- case "before-name":
4215
- parseError("Unexpected ;");
4698
+ if (parentRule) {
4699
+ containerRule.__parentRule = parentRule;
4700
+ pushToAncestorRules(parentRule);
4701
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4702
+ nestedSelectorRule = parentRule;
4703
+ }
4704
+ }
4705
+ currentScope = parentRule = containerRule;
4706
+ pushToAncestorRules(containerRule);
4707
+ containerRule.__parentStyleSheet = styleSheet;
4708
+ styleRule = null; // Reset styleRule when entering @-rule
4216
4709
  buffer = "";
4217
- state = "before-name";
4218
- break;
4219
- case "value":
4220
- styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4221
- priority = "";
4710
+ state = "before-selector";
4711
+ } else if (state === "counterStyleBlock") {
4712
+ var counterStyleName = buffer.trim().replace(/\n/g, "");
4713
+ // Validate: name cannot be empty, contain whitespace, or contain dots
4714
+ var isValidCounterStyleName = counterStyleName.length > 0 && !/[\s.]/.test(counterStyleName);
4715
+
4716
+ if (isValidCounterStyleName) {
4717
+ counterStyleRule.name = counterStyleName;
4718
+ currentScope = parentRule = counterStyleRule;
4719
+ counterStyleRule.__parentStyleSheet = styleSheet;
4720
+ }
4222
4721
  buffer = "";
4223
- state = "before-name";
4224
- break;
4225
- case "atRule":
4722
+ } else if (state === "conditionBlock") {
4723
+ supportsRule.__conditionText = buffer.trim();
4724
+
4725
+ if (parentRule) {
4726
+ supportsRule.__parentRule = parentRule;
4727
+ pushToAncestorRules(parentRule);
4728
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4729
+ nestedSelectorRule = parentRule;
4730
+ }
4731
+ }
4732
+
4733
+ currentScope = parentRule = supportsRule;
4734
+ pushToAncestorRules(supportsRule);
4735
+ supportsRule.__parentStyleSheet = styleSheet;
4736
+ styleRule = null; // Reset styleRule when entering @-rule
4226
4737
  buffer = "";
4227
4738
  state = "before-selector";
4228
- break;
4229
- case "importRule":
4230
- var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.some(function (rule) {
4231
- return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
4232
- });
4233
- if (isValid) {
4234
- importRule = new CSSOM.CSSImportRule();
4235
- importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
4236
- importRule.cssText = buffer + character;
4237
- styleSheet.cssRules.push(importRule);
4739
+ } else if (state === "scopeBlock") {
4740
+ var parsedScopePrelude = parseScopePrelude(buffer.trim());
4741
+
4742
+ if (parsedScopePrelude.hasStart) {
4743
+ scopeRule.__start = parsedScopePrelude.startSelector;
4238
4744
  }
4745
+ if (parsedScopePrelude.hasEnd) {
4746
+ scopeRule.__end = parsedScopePrelude.endSelector;
4747
+ }
4748
+ if (parsedScopePrelude.hasOnlyEnd) {
4749
+ scopeRule.__end = parsedScopePrelude.endSelector;
4750
+ }
4751
+
4752
+ if (parentRule) {
4753
+ scopeRule.__parentRule = parentRule;
4754
+ pushToAncestorRules(parentRule);
4755
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4756
+ nestedSelectorRule = parentRule;
4757
+ }
4758
+ }
4759
+ currentScope = parentRule = scopeRule;
4760
+ pushToAncestorRules(scopeRule);
4761
+ scopeRule.__parentStyleSheet = styleSheet;
4762
+ styleRule = null; // Reset styleRule when entering @-rule
4239
4763
  buffer = "";
4240
4764
  state = "before-selector";
4241
- break;
4242
- case "namespaceRule":
4243
- var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.every(function (rule) {
4244
- return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
4245
- });
4246
- if (isValid) {
4247
- try {
4248
- // Validate namespace syntax before creating the rule
4249
- var testNamespaceRule = new CSSOM.CSSNamespaceRule();
4250
- testNamespaceRule.cssText = buffer + character;
4251
-
4252
- namespaceRule = testNamespaceRule;
4253
- namespaceRule.__parentStyleSheet = styleSheet;
4254
- styleSheet.cssRules.push(namespaceRule);
4255
-
4256
- // Track the namespace prefix for validation
4257
- if (namespaceRule.prefix) {
4258
- definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
4765
+ } else if (state === "layerBlock") {
4766
+ layerBlockRule.name = buffer.trim();
4767
+
4768
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
4769
+
4770
+ if (isValidName) {
4771
+ if (parentRule) {
4772
+ layerBlockRule.__parentRule = parentRule;
4773
+ pushToAncestorRules(parentRule);
4774
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4775
+ nestedSelectorRule = parentRule;
4259
4776
  }
4260
- } catch(e) {
4261
- parseError(e.message);
4262
4777
  }
4778
+
4779
+ currentScope = parentRule = layerBlockRule;
4780
+ pushToAncestorRules(layerBlockRule);
4781
+ layerBlockRule.__parentStyleSheet = styleSheet;
4263
4782
  }
4783
+ styleRule = null; // Reset styleRule when entering @-rule
4264
4784
  buffer = "";
4265
4785
  state = "before-selector";
4266
- break;
4267
- case "layerBlock":
4268
- var nameListStr = buffer.trim().split(",").map(function (name) {
4269
- return name.trim();
4270
- });
4271
- var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
4272
- return name.trim().match(cssCustomIdentifierRegExp) === null;
4273
- });
4786
+ } else if (state === "pageBlock") {
4787
+ pageRule.selectorText = buffer.trim();
4274
4788
 
4275
- if (!isInvalid) {
4276
- layerStatementRule = new CSSOM.CSSLayerStatementRule();
4277
- layerStatementRule.__parentStyleSheet = styleSheet;
4278
- layerStatementRule.__starts = layerBlockRule.__starts;
4279
- layerStatementRule.__ends = i;
4280
- layerStatementRule.nameList = nameListStr;
4281
- styleSheet.cssRules.push(layerStatementRule);
4789
+ if (parentRule) {
4790
+ pageRule.__parentRule = parentRule;
4791
+ pushToAncestorRules(parentRule);
4282
4792
  }
4793
+
4794
+ currentScope = parentRule = pageRule;
4795
+ pageRule.__parentStyleSheet = styleSheet;
4796
+ styleRule = pageRule;
4797
+ buffer = "";
4798
+ state = "before-name";
4799
+ } else if (state === "hostRule-begin") {
4800
+ if (parentRule) {
4801
+ pushToAncestorRules(parentRule);
4802
+ }
4803
+
4804
+ currentScope = parentRule = hostRule;
4805
+ pushToAncestorRules(hostRule);
4806
+ hostRule.__parentStyleSheet = styleSheet;
4283
4807
  buffer = "";
4284
4808
  state = "before-selector";
4285
- break;
4286
- default:
4287
- buffer += character;
4288
- break;
4289
- }
4290
- break;
4809
+ } else if (state === "startingStyleRule-begin") {
4810
+ if (parentRule) {
4811
+ startingStyleRule.__parentRule = parentRule;
4812
+ pushToAncestorRules(parentRule);
4813
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
4814
+ nestedSelectorRule = parentRule;
4815
+ }
4816
+ }
4291
4817
 
4292
- case "}":
4293
- if (state === "counterStyleBlock") {
4294
- // FIXME : Implement cssText get setter that parses the real implementation
4295
- counterStyleRule.cssText = "@counter-style " + counterStyleRule.name + " { " + buffer.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function(match, quote) {
4296
- return quote ? match : ' ';
4297
- }) + " }";
4298
- buffer = "";
4299
- state = "before-selector";
4300
- }
4818
+ currentScope = parentRule = startingStyleRule;
4819
+ pushToAncestorRules(startingStyleRule);
4820
+ startingStyleRule.__parentStyleSheet = styleSheet;
4821
+ styleRule = null; // Reset styleRule when entering @-rule
4822
+ buffer = "";
4823
+ state = "before-selector";
4301
4824
 
4302
- switch (state) {
4303
- case "value":
4304
- styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4305
- priority = "";
4306
- /* falls through */
4307
- case "before-value":
4308
- case "before-name":
4309
- case "name":
4310
- styleRule.__ends = i + 1;
4311
-
4312
- if (parentRule === styleRule) {
4313
- parentRule = ancestorRules.pop()
4825
+ } else if (state === "fontFaceRule-begin") {
4826
+ if (parentRule) {
4827
+ fontFaceRule.__parentRule = parentRule;
4314
4828
  }
4315
-
4829
+ fontFaceRule.__parentStyleSheet = styleSheet;
4830
+ styleRule = fontFaceRule;
4831
+ buffer = "";
4832
+ state = "before-name";
4833
+ } else if (state === "keyframesRule-begin") {
4834
+ keyframesRule.name = buffer.trim();
4316
4835
  if (parentRule) {
4317
- styleRule.__parentRule = parentRule;
4836
+ pushToAncestorRules(parentRule);
4837
+ keyframesRule.__parentRule = parentRule;
4318
4838
  }
4319
- styleRule.__parentStyleSheet = styleSheet;
4320
-
4321
- if (currentScope === styleRule) {
4322
- currentScope = parentRule || styleSheet;
4839
+ keyframesRule.__parentStyleSheet = styleSheet;
4840
+ currentScope = parentRule = keyframesRule;
4841
+ buffer = "";
4842
+ state = "keyframeRule-begin";
4843
+ } else if (state === "keyframeRule-begin") {
4844
+ styleRule = new CSSOM.CSSKeyframeRule();
4845
+ styleRule.keyText = buffer.trim();
4846
+ styleRule.__starts = i;
4847
+ buffer = "";
4848
+ state = "before-name";
4849
+ } else if (state === "documentRule-begin") {
4850
+ // FIXME: what if this '{' is in the url text of the match function?
4851
+ documentRule.matcher.matcherText = buffer.trim();
4852
+ if (parentRule) {
4853
+ pushToAncestorRules(parentRule);
4854
+ documentRule.__parentRule = parentRule;
4855
+ }
4856
+ currentScope = parentRule = documentRule;
4857
+ pushToAncestorRules(documentRule);
4858
+ documentRule.__parentStyleSheet = styleSheet;
4859
+ buffer = "";
4860
+ state = "before-selector";
4861
+ } else if (state === "before-name" || state === "name") {
4862
+ // @font-face and similar rules don't support nested selectors
4863
+ // If we encounter a nested selector block inside them, skip it
4864
+ if (styleRule.constructor.name === "CSSFontFaceRule" ||
4865
+ styleRule.constructor.name === "CSSKeyframeRule" ||
4866
+ (styleRule.constructor.name === "CSSPageRule" && parentRule === styleRule)) {
4867
+ // Skip the nested block
4868
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
4869
+ if (ruleClosingMatch) {
4870
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
4871
+ buffer = "";
4872
+ state = "before-name";
4873
+ break;
4874
+ }
4323
4875
  }
4324
4876
 
4325
- if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
4326
- if (styleRule === nestedSelectorRule) {
4327
- nestedSelectorRule = null;
4877
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
4878
+ if (styleRule.style.length) {
4879
+ parentRule.cssRules.push(styleRule);
4880
+ styleRule.__parentRule = parentRule;
4881
+ styleRule.__parentStyleSheet = styleSheet;
4882
+ pushToAncestorRules(parentRule);
4883
+ } else {
4884
+ // If the styleRule is empty, we can assume that it's a nested selector
4885
+ pushToAncestorRules(parentRule);
4328
4886
  }
4329
- parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
4330
4887
  } else {
4331
- currentScope.cssRules.push(styleRule);
4888
+ currentScope = parentRule = styleRule;
4889
+ pushToAncestorRules(parentRule);
4890
+ styleRule.__parentStyleSheet = styleSheet;
4332
4891
  }
4333
- buffer = "";
4334
- if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
4335
- state = "keyframeRule-begin";
4892
+
4893
+ styleRule = new CSSOM.CSSStyleRule();
4894
+ var processedSelectorText = processSelectorText(buffer.trim());
4895
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
4896
+ if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
4897
+ styleRule.selectorText = processedSelectorText;
4336
4898
  } else {
4337
- state = "before-selector";
4899
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
4900
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
4901
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
4902
+ }).join(', ');
4903
+ }
4904
+ styleRule.style.__starts = i - buffer.length;
4905
+ styleRule.__parentRule = parentRule;
4906
+ // Only set nestedSelectorRule if we're directly inside a CSSStyleRule or CSSScopeRule,
4907
+ // not inside other grouping rules like @media/@supports
4908
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
4909
+ nestedSelectorRule = styleRule;
4338
4910
  }
4339
4911
 
4340
- if (styleRule.constructor.name === "CSSNestedDeclarations") {
4341
- if (currentScope !== styleSheet) {
4342
- nestedSelectorRule = currentScope;
4343
- }
4344
- styleRule = null;
4912
+ buffer = "";
4913
+ state = "before-name";
4914
+ }
4915
+ break;
4916
+
4917
+ case ":":
4918
+ if (state === "name") {
4919
+ // It can be a nested selector, let's check
4920
+ var openBraceBeforeMatch = token.slice(i).match(/[{;}]/);
4921
+ var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
4922
+ if (hasOpenBraceBefore) {
4923
+ // Is a selector
4924
+ buffer += character;
4345
4925
  } else {
4346
- styleRule = null;
4347
- break;
4926
+ // Is a declaration
4927
+ name = buffer.trim();
4928
+ buffer = "";
4929
+ state = "before-value";
4348
4930
  }
4349
- case "keyframeRule-begin":
4350
- case "before-selector":
4351
- case "selector":
4352
- // End of media/supports/document rule.
4353
- if (!parentRule) {
4354
- parseError("Unexpected }");
4355
-
4356
- var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
4357
- if (hasPreviousStyleRule) {
4358
- i = ignoreBalancedBlock(i, token.slice(i), 1);
4931
+ } else {
4932
+ buffer += character;
4933
+ }
4934
+ break;
4935
+
4936
+ case "(":
4937
+ if (state === 'value') {
4938
+ // ie css expression mode
4939
+ if (buffer.trim() === 'expression') {
4940
+ var info = (new CSSOM.CSSValueExpression(token, i)).parse();
4941
+
4942
+ if (info.error) {
4943
+ parseError(info.error);
4944
+ } else {
4945
+ buffer += info.expression;
4946
+ i = info.idx;
4359
4947
  }
4360
-
4361
- break;
4948
+ } else {
4949
+ state = 'value-parenthesis';
4950
+ //always ensure this is reset to 1 on transition
4951
+ //from value to value-parenthesis
4952
+ valueParenthesisDepth = 1;
4953
+ buffer += character;
4362
4954
  }
4955
+ } else if (state === 'value-parenthesis') {
4956
+ valueParenthesisDepth++;
4957
+ buffer += character;
4958
+ } else {
4959
+ buffer += character;
4960
+ }
4961
+ break;
4363
4962
 
4963
+ case ")":
4964
+ if (state === 'value-parenthesis') {
4965
+ valueParenthesisDepth--;
4966
+ if (valueParenthesisDepth === 0) state = 'value';
4967
+ }
4968
+ buffer += character;
4969
+ break;
4364
4970
 
4365
- while (ancestorRules.length > 0) {
4366
- parentRule = ancestorRules.pop();
4367
-
4368
- if (
4369
- parentRule.constructor.name === "CSSStyleRule"
4370
- || parentRule.constructor.name === "CSSMediaRule"
4371
- || parentRule.constructor.name === "CSSSupportsRule"
4372
- || parentRule.constructor.name === "CSSContainerRule"
4373
- || parentRule.constructor.name === "CSSScopeRule"
4374
- || parentRule.constructor.name === "CSSLayerBlockRule"
4375
- || parentRule.constructor.name === "CSSStartingStyleRule"
4376
- ) {
4377
- if (nestedSelectorRule) {
4378
- if (nestedSelectorRule.parentRule) {
4379
- prevScope = nestedSelectorRule;
4380
- currentScope = nestedSelectorRule.parentRule;
4381
- if (currentScope.cssRules.findIndex(function (rule) {
4382
- return rule === prevScope
4383
- }) === -1) {
4384
- currentScope.cssRules.push(prevScope);
4385
- }
4386
- nestedSelectorRule = currentScope;
4971
+ case "!":
4972
+ if (state === "value" && token.indexOf("!important", i) === i) {
4973
+ priority = "important";
4974
+ i += "important".length;
4975
+ } else {
4976
+ buffer += character;
4977
+ }
4978
+ break;
4979
+
4980
+ case ";":
4981
+ switch (state) {
4982
+ case "before-value":
4983
+ case "before-name":
4984
+ parseError("Unexpected ;");
4985
+ buffer = "";
4986
+ state = "before-name";
4987
+ break;
4988
+ case "value":
4989
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
4990
+ priority = "";
4991
+ buffer = "";
4992
+ state = "before-name";
4993
+ break;
4994
+ case "atRule":
4995
+ buffer = "";
4996
+ state = "before-selector";
4997
+ break;
4998
+ case "importRule":
4999
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
5000
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
5001
+ });
5002
+ if (isValid) {
5003
+ importRule = new CSSOM.CSSImportRule();
5004
+ importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
5005
+ importRule.parse(buffer + character);
5006
+ topScope.cssRules.push(importRule);
5007
+ }
5008
+ buffer = "";
5009
+ state = "before-selector";
5010
+ break;
5011
+ case "namespaceRule":
5012
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
5013
+ return ['CSSImportRule', 'CSSLayerStatementRule', 'CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
5014
+ });
5015
+ if (isValid) {
5016
+ try {
5017
+ // Validate namespace syntax before creating the rule
5018
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
5019
+ testNamespaceRule.parse(buffer + character);
5020
+
5021
+ namespaceRule = testNamespaceRule;
5022
+ namespaceRule.__parentStyleSheet = styleSheet;
5023
+ topScope.cssRules.push(namespaceRule);
5024
+
5025
+ // Track the namespace prefix for validation
5026
+ if (namespaceRule.prefix) {
5027
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
5028
+ }
5029
+ } catch (e) {
5030
+ parseError(e.message);
5031
+ }
5032
+ }
5033
+ buffer = "";
5034
+ state = "before-selector";
5035
+ break;
5036
+ case "layerBlock":
5037
+ var nameListStr = buffer.trim().split(",").map(function (name) {
5038
+ return name.trim();
5039
+ });
5040
+ var isInvalid = nameListStr.some(function (name) {
5041
+ return name.trim().match(cssCustomIdentifierRegExp) === null;
5042
+ });
5043
+
5044
+ // Check if there's a CSSStyleRule in the parent chain
5045
+ var hasStyleRuleParent = false;
5046
+ if (parentRule) {
5047
+ var checkParent = parentRule;
5048
+ while (checkParent) {
5049
+ if (checkParent.constructor.name === "CSSStyleRule") {
5050
+ hasStyleRuleParent = true;
5051
+ break;
4387
5052
  }
5053
+ checkParent = checkParent.__parentRule;
5054
+ }
5055
+ }
5056
+
5057
+ if (!isInvalid && !hasStyleRuleParent) {
5058
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
5059
+ layerStatementRule.__parentStyleSheet = styleSheet;
5060
+ layerStatementRule.__starts = layerBlockRule.__starts;
5061
+ layerStatementRule.__ends = i;
5062
+ layerStatementRule.nameList = nameListStr;
5063
+
5064
+ // Add to parent rule if nested, otherwise to top scope
5065
+ if (parentRule) {
5066
+ layerStatementRule.__parentRule = parentRule;
5067
+ parentRule.cssRules.push(layerStatementRule);
4388
5068
  } else {
4389
- prevScope = currentScope;
4390
- currentScope = parentRule;
4391
- currentScope !== prevScope && currentScope.cssRules.push(prevScope);
4392
- break;
5069
+ topScope.cssRules.push(layerStatementRule);
4393
5070
  }
4394
5071
  }
4395
- }
4396
-
4397
- if (currentScope.parentRule == null) {
4398
- currentScope.__ends = i + 1;
4399
- if (currentScope !== styleSheet && styleSheet.cssRules.findIndex(function (rule) {
4400
- return rule === currentScope
4401
- }) === -1) {
4402
- styleSheet.cssRules.push(currentScope);
5072
+ buffer = "";
5073
+ state = "before-selector";
5074
+ break;
5075
+ default:
5076
+ buffer += character;
5077
+ break;
5078
+ }
5079
+ break;
5080
+
5081
+ case "}":
5082
+ if (state === "counterStyleBlock") {
5083
+ // FIXME : Implement missing properties on CSSCounterStyleRule interface and update parse method
5084
+ // For now it's just assigning entire rule text
5085
+ counterStyleRule.parse("@counter-style " + counterStyleRule.name + " { " + buffer + " }");
5086
+ buffer = "";
5087
+ state = "before-selector";
5088
+ }
5089
+
5090
+ switch (state) {
5091
+ case "value":
5092
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
5093
+ priority = "";
5094
+ /* falls through */
5095
+ case "before-value":
5096
+ case "before-name":
5097
+ case "name":
5098
+ styleRule.__ends = i + 1;
5099
+
5100
+ if (parentRule === styleRule) {
5101
+ parentRule = ancestorRules.pop()
5102
+ }
5103
+
5104
+ if (parentRule) {
5105
+ styleRule.__parentRule = parentRule;
4403
5106
  }
4404
- currentScope = styleSheet;
4405
- if (nestedSelectorRule === parentRule) {
4406
- // Check if this selector is really starting inside another selector
4407
- var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
4408
- var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
4409
- var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
4410
- var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
4411
- var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
4412
-
4413
- if (openingBraceLen === closingBraceLen) {
4414
- // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
4415
- nestedSelectorRule.__ends = i + 1;
5107
+ styleRule.__parentStyleSheet = styleSheet;
5108
+
5109
+ if (currentScope === styleRule) {
5110
+ currentScope = parentRule || topScope;
5111
+ }
5112
+
5113
+ if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
5114
+ if (styleRule === nestedSelectorRule) {
4416
5115
  nestedSelectorRule = null;
4417
- parentRule = null;
4418
5116
  }
5117
+ parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
4419
5118
  } else {
4420
- parentRule = null;
5119
+ if (styleRule.parentRule) {
5120
+ styleRule.parentRule.cssRules.push(styleRule);
5121
+ } else {
5122
+ currentScope.cssRules.push(styleRule);
5123
+ }
5124
+ }
5125
+ buffer = "";
5126
+ if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
5127
+ state = "keyframeRule-begin";
5128
+ } else {
5129
+ state = "before-selector";
5130
+ }
4421
5131
 
5132
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
5133
+ if (currentScope !== topScope) {
5134
+ // Only set nestedSelectorRule if currentScope is CSSStyleRule or CSSScopeRule
5135
+ // Not for other grouping rules like @media/@supports
5136
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5137
+ nestedSelectorRule = currentScope;
5138
+ }
5139
+ }
5140
+ styleRule = null;
5141
+ } else {
5142
+ // Update nestedSelectorRule when closing a CSSStyleRule
5143
+ if (styleRule === nestedSelectorRule) {
5144
+ var selector = styleRule.selectorText && styleRule.selectorText.trim();
5145
+ // Check if this is proper nesting (&.class, &:pseudo) vs prepended & (& :is, & .class with space)
5146
+ // Prepended & has pattern "& X" where X starts with : or .
5147
+ var isPrependedAmpersand = selector && selector.match(/^&\s+[:\.]/);
5148
+
5149
+ // Check if parent is a grouping rule that can contain nested selectors
5150
+ var isGroupingRule = currentScope && currentScope instanceof CSSOM.CSSGroupingRule;
5151
+
5152
+ if (!isPrependedAmpersand && isGroupingRule) {
5153
+ // Proper nesting - set nestedSelectorRule to parent for more nested selectors
5154
+ // But only if it's a CSSStyleRule or CSSScopeRule, not other grouping rules like @media
5155
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
5156
+ nestedSelectorRule = currentScope;
5157
+ }
5158
+ // If currentScope is another type of grouping rule (like @media), keep nestedSelectorRule unchanged
5159
+ } else {
5160
+ // Prepended & or not nested in grouping rule - reset to prevent CSSNestedDeclarations
5161
+ nestedSelectorRule = null;
5162
+ }
5163
+ } else if (nestedSelectorRule && currentScope instanceof CSSOM.CSSGroupingRule) {
5164
+ // When closing a nested rule that's not the nestedSelectorRule itself,
5165
+ // maintain nestedSelectorRule if we're still inside a grouping rule
5166
+ // This ensures declarations after nested selectors inside @media/@supports etc. work correctly
5167
+ }
5168
+ styleRule = null;
5169
+ break;
4422
5170
  }
4423
- }
5171
+ case "keyframeRule-begin":
5172
+ case "before-selector":
5173
+ case "selector":
5174
+ // End of media/supports/document rule.
5175
+ if (!parentRule) {
5176
+ parseError("Unexpected }");
5177
+
5178
+ var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
5179
+ if (hasPreviousStyleRule) {
5180
+ i = ignoreBalancedBlock(i, token.slice(i), 1);
5181
+ }
4424
5182
 
4425
- buffer = "";
4426
- state = "before-selector";
4427
- break;
4428
- }
4429
- break;
5183
+ break;
5184
+ }
4430
5185
 
4431
- default:
4432
- switch (state) {
4433
- case "before-selector":
4434
- state = "selector";
4435
- if (styleRule && parentRule) {
4436
- // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
4437
- // If Declaration inside Nested Selector let's keep the same styleRule
4438
- if (
4439
- parentRule.constructor.name === "CSSStyleRule"
4440
- || parentRule.constructor.name === "CSSMediaRule"
4441
- || parentRule.constructor.name === "CSSSupportsRule"
4442
- || parentRule.constructor.name === "CSSContainerRule"
4443
- || parentRule.constructor.name === "CSSScopeRule"
4444
- || parentRule.constructor.name === "CSSLayerBlockRule"
4445
- || parentRule.constructor.name === "CSSStartingStyleRule"
4446
- ) {
4447
- // parentRule.__parentRule = styleRule;
4448
- state = "before-name";
4449
- if (styleRule !== parentRule) {
4450
- styleRule = new CSSOM.CSSNestedDeclarations();
4451
- styleRule.__starts = i;
5186
+ while (ancestorRules.length > 0) {
5187
+ parentRule = ancestorRules.pop();
5188
+
5189
+ if (parentRule instanceof CSSOM.CSSGroupingRule && (parentRule.constructor.name !== 'CSSStyleRule' || parentRule.__parentRule)) {
5190
+ if (nestedSelectorRule) {
5191
+ if (nestedSelectorRule.parentRule) {
5192
+ prevScope = nestedSelectorRule;
5193
+ currentScope = nestedSelectorRule.parentRule;
5194
+ if (currentScope.cssRules.findIndex(function (rule) {
5195
+ return rule === prevScope
5196
+ }) === -1) {
5197
+ currentScope.cssRules.push(prevScope);
5198
+ }
5199
+ nestedSelectorRule = currentScope;
5200
+ } else {
5201
+ // If nestedSelectorRule doesn't have a parentRule, we're closing a grouping rule
5202
+ // inside a top-level CSSStyleRule. We need to push currentScope to the parentRule.
5203
+ prevScope = currentScope;
5204
+ // Push to actual parent from ancestorRules if available
5205
+ var actualParent = ancestorRules.length > 0 ? ancestorRules[ancestorRules.length - 1] : nestedSelectorRule;
5206
+ actualParent !== prevScope && actualParent.cssRules.push(prevScope);
5207
+ // Update currentScope to the nestedSelectorRule before breaking
5208
+ currentScope = actualParent;
5209
+ parentRule = actualParent;
5210
+ break;
5211
+ }
5212
+ } else {
5213
+ prevScope = currentScope;
5214
+ parentRule !== prevScope && parentRule.cssRules.push(prevScope);
5215
+ break;
5216
+ }
4452
5217
  }
4453
5218
  }
4454
-
4455
- } else if (nestedSelectorRule && parentRule && (
4456
- parentRule.constructor.name === "CSSStyleRule"
4457
- || parentRule.constructor.name === "CSSMediaRule"
4458
- || parentRule.constructor.name === "CSSSupportsRule"
4459
- || parentRule.constructor.name === "CSSContainerRule"
4460
- || parentRule.constructor.name === "CSSLayerBlockRule"
4461
- || parentRule.constructor.name === "CSSStartingStyleRule"
4462
- )) {
4463
- state = "before-name";
4464
- if (parentRule.cssRules.length) {
4465
- currentScope = nestedSelectorRule = parentRule;
4466
- styleRule = new CSSOM.CSSNestedDeclarations();
4467
- styleRule.__starts = i;
4468
- } else {
4469
- if (parentRule.constructor.name === "CSSStyleRule") {
4470
- styleRule = parentRule;
5219
+
5220
+ // If currentScope has a __parentRule and wasn't added yet, add it
5221
+ if (ancestorRules.length === 0 && currentScope.__parentRule && currentScope.__parentRule.cssRules) {
5222
+ if (currentScope.__parentRule.cssRules.findIndex(function (rule) {
5223
+ return rule === currentScope
5224
+ }) === -1) {
5225
+ currentScope.__parentRule.cssRules.push(currentScope);
5226
+ }
5227
+ }
5228
+
5229
+ // Only handle top-level rule closing if we processed all ancestors
5230
+ if (ancestorRules.length === 0 && currentScope.parentRule == null) {
5231
+ currentScope.__ends = i + 1;
5232
+ if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
5233
+ return rule === currentScope
5234
+ }) === -1) {
5235
+ topScope.cssRules.push(currentScope);
5236
+ }
5237
+ currentScope = topScope;
5238
+ if (nestedSelectorRule === parentRule) {
5239
+ // Check if this selector is really starting inside another selector
5240
+ var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
5241
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
5242
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
5243
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
5244
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
5245
+
5246
+ if (openingBraceLen === closingBraceLen) {
5247
+ // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
5248
+ nestedSelectorRule.__ends = i + 1;
5249
+ nestedSelectorRule = null;
5250
+ parentRule = null;
5251
+ }
4471
5252
  } else {
5253
+ parentRule = null;
5254
+ }
5255
+ } else {
5256
+ currentScope = parentRule;
5257
+ }
5258
+
5259
+ buffer = "";
5260
+ state = "before-selector";
5261
+ break;
5262
+ }
5263
+ break;
5264
+
5265
+ default:
5266
+ switch (state) {
5267
+ case "before-selector":
5268
+ state = "selector";
5269
+ if ((styleRule || scopeRule) && parentRule) {
5270
+ // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
5271
+ // If Declaration inside Nested Selector let's keep the same styleRule
5272
+ if (!isSelectorStartChar(character) && !isWhitespaceChar(character) && parentRule instanceof CSSOM.CSSGroupingRule) {
5273
+ // parentRule.__parentRule = styleRule;
5274
+ state = "before-name";
5275
+ if (styleRule !== parentRule) {
5276
+ styleRule = new CSSOM.CSSNestedDeclarations();
5277
+ styleRule.__starts = i;
5278
+ }
5279
+ }
5280
+
5281
+ } else if (nestedSelectorRule && parentRule && parentRule instanceof CSSOM.CSSGroupingRule) {
5282
+ if (isSelectorStartChar(character)) {
5283
+ // If starting with a selector character, create CSSStyleRule instead of CSSNestedDeclarations
4472
5284
  styleRule = new CSSOM.CSSStyleRule();
4473
- styleRule.__starts = i;
5285
+ styleRule.__starts = i;
5286
+ } else if (!isWhitespaceChar(character)) {
5287
+ // Starting a declaration (not whitespace, not a selector)
5288
+ state = "before-name";
5289
+ // Check if we should create CSSNestedDeclarations
5290
+ // This happens if: parent has cssRules OR nestedSelectorRule exists (indicating CSSStyleRule in hierarchy)
5291
+ if (parentRule.cssRules.length || nestedSelectorRule) {
5292
+ currentScope = parentRule;
5293
+ // Only set nestedSelectorRule if parentRule is CSSStyleRule or CSSScopeRule
5294
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
5295
+ nestedSelectorRule = parentRule;
5296
+ }
5297
+ styleRule = new CSSOM.CSSNestedDeclarations();
5298
+ styleRule.__starts = i;
5299
+ } else {
5300
+ if (parentRule.constructor.name === "CSSStyleRule") {
5301
+ styleRule = parentRule;
5302
+ } else {
5303
+ styleRule = new CSSOM.CSSStyleRule();
5304
+ styleRule.__starts = i;
5305
+ }
5306
+ }
4474
5307
  }
4475
5308
  }
4476
- } else {
4477
- styleRule = new CSSOM.CSSStyleRule();
4478
- styleRule.__starts = i;
4479
- }
4480
- break;
4481
- case "before-name":
4482
- state = "name";
4483
- break;
4484
- case "before-value":
4485
- state = "value";
4486
- break;
4487
- case "importRule-begin":
4488
- state = "importRule";
4489
- break;
4490
- case "namespaceRule-begin":
4491
- state = "namespaceRule";
4492
- break;
5309
+ break;
5310
+ case "before-name":
5311
+ state = "name";
5312
+ break;
5313
+ case "before-value":
5314
+ state = "value";
5315
+ break;
5316
+ case "importRule-begin":
5317
+ state = "importRule";
5318
+ break;
5319
+ case "namespaceRule-begin":
5320
+ state = "namespaceRule";
5321
+ break;
5322
+ }
5323
+ buffer += character;
5324
+ break;
5325
+ }
5326
+
5327
+ // Auto-close all unclosed nested structures
5328
+ // Check AFTER processing the character, at the ORIGINAL ending index
5329
+ // Only add closing braces if CSS is incomplete (not at top scope)
5330
+ if (i === initialEndingIndex && (currentScope !== topScope || ancestorRules.length > 0)) {
5331
+ var needsClosing = ancestorRules.length;
5332
+ if (currentScope !== topScope && ancestorRules.indexOf(currentScope) === -1) {
5333
+ needsClosing += 1;
5334
+ }
5335
+ // Add closing braces for all unclosed structures
5336
+ for (var closeIdx = 0; closeIdx < needsClosing; closeIdx++) {
5337
+ token += "}";
5338
+ endingIndex += 1;
4493
5339
  }
4494
- buffer += character;
4495
- break;
4496
5340
  }
4497
5341
  }
4498
5342