@acemir/cssom 0.9.0 → 0.9.1

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
@@ -44,13 +44,24 @@ CSSOM.CSSStyleDeclaration.prototype = {
44
44
  this[this.length] = name;
45
45
  this.length++;
46
46
  }
47
+
48
+ // If the priority value of the incoming property is "important",
49
+ // or the value of the existing property is not "important",
50
+ // then remove the existing property and rewrite it.
51
+ if (priority || !this._importants[name]) {
52
+ this.removeProperty(name);
53
+ this[this.length] = name;
54
+ this.length++;
55
+ this[name] = value + '';
56
+ this._importants[name] = priority;
57
+ }
47
58
  } else {
48
59
  // New property.
49
60
  this[this.length] = name;
50
61
  this.length++;
62
+ this[name] = value + '';
63
+ this._importants[name] = priority;
51
64
  }
52
- this[name] = value + "";
53
- this._importants[name] = priority;
54
65
  },
55
66
 
56
67
  /**
@@ -632,6 +643,8 @@ CSSOM.CSSImportRule = function CSSImportRule() {
632
643
  CSSOM.CSSRule.call(this);
633
644
  this.href = "";
634
645
  this.media = new CSSOM.MediaList();
646
+ this.layerName = null;
647
+ this.supportsText = null;
635
648
  this.styleSheet = new CSSOM.CSSStyleSheet();
636
649
  };
637
650
 
@@ -642,7 +655,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
642
655
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
643
656
  get: function() {
644
657
  var mediaText = this.media.mediaText;
645
- return "@import url(" + this.href + ")" + (mediaText ? " " + mediaText : "") + ";";
658
+ return "@import url(" + this.href + ")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
646
659
  },
647
660
  set: function(cssText) {
648
661
  var i = 0;
@@ -658,6 +671,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
658
671
 
659
672
  var buffer = '';
660
673
  var index;
674
+
675
+ var layerRegExp = /layer\(([^)]*)\)/;
676
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
677
+ var supportsRegExp = /supports\(([^)]+)\)/;
678
+ var doubleOrMoreSpacesRegExp = /\s{2,}/g;
679
+
661
680
  for (var character; (character = cssText.charAt(i)); i++) {
662
681
 
663
682
  switch (character) {
@@ -682,6 +701,9 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
682
701
  break;
683
702
 
684
703
  case 'u':
704
+ if (state === 'media') {
705
+ buffer += character;
706
+ }
685
707
  if (state === 'url' && cssText.indexOf('url(', i) === i) {
686
708
  index = cssText.indexOf(')', i + 1);
687
709
  if (index === -1) {
@@ -701,7 +723,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
701
723
  break;
702
724
 
703
725
  case '"':
704
- if (state === 'url') {
726
+ if (state === 'after-import' || state === 'url') {
705
727
  index = cssText.indexOf('"', i + 1);
706
728
  if (!index) {
707
729
  throw i + ": '\"' not found";
@@ -713,7 +735,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
713
735
  break;
714
736
 
715
737
  case "'":
716
- if (state === 'url') {
738
+ if (state === 'after-import' || state === 'url') {
717
739
  index = cssText.indexOf("'", i + 1);
718
740
  if (!index) {
719
741
  throw i + ': "\'" not found';
@@ -727,7 +749,47 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
727
749
  case ';':
728
750
  if (state === 'media') {
729
751
  if (buffer) {
730
- this.media.mediaText = buffer.trim();
752
+ var bufferTrimmed = buffer.trim();
753
+
754
+ if (bufferTrimmed.indexOf('layer') === 0) {
755
+ var layerMatch = bufferTrimmed.match(layerRegExp);
756
+
757
+ if (layerMatch) {
758
+ var layerName = layerMatch[1].trim();
759
+ bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
760
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
761
+ .trim();
762
+
763
+ if (layerName.match(layerRuleNameRegExp) !== null) {
764
+ this.layerName = layerMatch[1].trim();
765
+ } else {
766
+ // REVIEW: In the browser, an empty layer() is not processed as a unamed layer
767
+ // and treats the rest of the string as mediaText, ignoring the parse of supports()
768
+ if (bufferTrimmed) {
769
+ this.media.mediaText = bufferTrimmed;
770
+ return;
771
+ }
772
+ }
773
+ } else {
774
+ this.layerName = "";
775
+ bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
776
+ }
777
+ }
778
+
779
+ var supportsMatch = bufferTrimmed.match(supportsRegExp);
780
+
781
+ if (supportsMatch && supportsMatch.index === 0) {
782
+ // REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
783
+ this.supportsText = supportsMatch[1].trim();
784
+ bufferTrimmed = bufferTrimmed.replace(supportsRegExp, '')
785
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
786
+ .trim();
787
+ }
788
+
789
+ // REVIEW: In the browser, any invalid media is replaced with 'not all'
790
+ if (bufferTrimmed) {
791
+ this.media.mediaText = bufferTrimmed;
792
+ }
731
793
  }
732
794
  }
733
795
  break;
@@ -862,11 +924,14 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
862
924
  * -> "img{border:none;}body{margin:0;}"
863
925
  *
864
926
  * @param {string} rule
865
- * @param {number} index
927
+ * @param {number} [index=0]
866
928
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
867
929
  * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
868
930
  */
869
931
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
932
+ if (index === void 0) {
933
+ index = 0;
934
+ }
870
935
  if (index < 0 || index > this.cssRules.length) {
871
936
  throw new RangeError("INDEX_SIZE_ERR");
872
937
  }
@@ -1431,7 +1496,7 @@ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
1431
1496
  */
1432
1497
  CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
1433
1498
  CSSOM.CSSGroupingRule.call(this);
1434
- this.layerName = "";
1499
+ this.name = "";
1435
1500
  this.cssRules = [];
1436
1501
  };
1437
1502
 
@@ -1440,23 +1505,37 @@ CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
1440
1505
  CSSOM.CSSLayerBlockRule.prototype.type = 18;
1441
1506
 
1442
1507
  Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
1443
- layerNameText: {
1508
+ cssText: {
1444
1509
  get: function () {
1445
- return this.layerName;
1446
- },
1447
- set: function (value) {
1448
- this.layerName = value;
1510
+ var cssTexts = [];
1511
+ for (var i = 0, length = this.cssRules.length; i < length; i++) {
1512
+ cssTexts.push(this.cssRules[i].cssText);
1513
+ }
1514
+ return "@layer " + this.name + (this.name && " ") + "{" + cssTexts.join("") + "}";
1449
1515
  },
1450
1516
  configurable: true,
1451
1517
  enumerable: true,
1452
1518
  },
1519
+ });
1520
+
1521
+
1522
+ /**
1523
+ * @constructor
1524
+ * @see https://drafts.csswg.org/css-cascade-5/#csslayerstatementrule
1525
+ */
1526
+ CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
1527
+ CSSOM.CSSRule.call(this);
1528
+ this.nameList = [];
1529
+ };
1530
+
1531
+ CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
1532
+ CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
1533
+ CSSOM.CSSLayerStatementRule.prototype.type = 0;
1534
+
1535
+ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
1453
1536
  cssText: {
1454
1537
  get: function () {
1455
- var cssTexts = [];
1456
- for (var i = 0, length = this.cssRules.length; i < length; i++) {
1457
- cssTexts.push(this.cssRules[i].cssText);
1458
- }
1459
- return "@layer " + this.layerNameText + " {" + cssTexts.join("") + "}";
1538
+ return "@layer " + this.nameList.join(", ") + ";";
1460
1539
  },
1461
1540
  configurable: true,
1462
1541
  enumerable: true,
@@ -1511,17 +1590,57 @@ CSSOM.parse = function parse(token) {
1511
1590
  var parentRule;
1512
1591
 
1513
1592
  var ancestorRules = [];
1514
- var hasAncestors = false;
1515
1593
  var prevScope;
1516
1594
 
1517
- var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, nestedSelectorRule;
1595
+ var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
1518
1596
 
1519
1597
  var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
1520
- var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
1598
+ // Regex above is not ES5 compliant
1599
+ // var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
1521
1600
  var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
1522
1601
  var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
1523
1602
  var forwardRuleValidationRegExp = /(?:\(|\s|\/\*)/; // Match that the rule is followed by any whitespace, a opening comment or a condition opening parenthesis
1603
+ var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
1524
1604
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
1605
+ var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
1606
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
1607
+
1608
+ /**
1609
+ * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
1610
+ * that is not inside a brace block within the given string. Mimics the behavior of a
1611
+ * regular expression match for such terminators, including any trailing whitespace.
1612
+ * @param {string} str - The string to search for at-rule statement terminators.
1613
+ * @returns {object | null} {0: string, index: number} or null if no match is found.
1614
+ */
1615
+ function atRulesStatemenRegExpES5Alternative(ruleSlice) {
1616
+ for (var i = 0; i < ruleSlice.length; i++) {
1617
+ var char = ruleSlice[i];
1618
+
1619
+ if (char === ';' || char === '}') {
1620
+ // Simulate negative lookbehind: check if there is a { before this position
1621
+ var sliceBefore = ruleSlice.substring(0, i);
1622
+ var openBraceIndex = sliceBefore.indexOf('{');
1623
+
1624
+ if (openBraceIndex === -1) {
1625
+ // No { found before, so we treat it as a valid match
1626
+ var match = char;
1627
+ var j = i + 1;
1628
+
1629
+ while (j < ruleSlice.length && /\s/.test(ruleSlice[j])) {
1630
+ match += ruleSlice[j];
1631
+ j++;
1632
+ }
1633
+
1634
+ var matchObj = [match];
1635
+ matchObj.index = i;
1636
+ matchObj.input = ruleSlice;
1637
+ return matchObj;
1638
+ }
1639
+ }
1640
+ }
1641
+
1642
+ return null;
1643
+ }
1525
1644
 
1526
1645
  /**
1527
1646
  * Finds the first balanced block (including nested braces) in the string, starting from fromIndex.
@@ -1530,17 +1649,18 @@ CSSOM.parse = function parse(token) {
1530
1649
  * @param {number} [fromIndex=0] - The index to start searching from.
1531
1650
  * @returns {object|null} - { 0: matchedString, index: startIndex, input: str } or null if not found.
1532
1651
  */
1533
- function matchBalancedBlock(str, fromIndex = 0) {
1534
- const openIndex = str.indexOf('{', fromIndex);
1652
+ function matchBalancedBlock(str, fromIndex) {
1653
+ fromIndex = fromIndex || 0;
1654
+ var openIndex = str.indexOf('{', fromIndex);
1535
1655
  if (openIndex === -1) return null;
1536
- let depth = 0;
1537
- for (let i = openIndex; i < str.length; i++) {
1656
+ var depth = 0;
1657
+ for (var i = openIndex; i < str.length; i++) {
1538
1658
  if (str[i] === '{') {
1539
1659
  depth++;
1540
1660
  } else if (str[i] === '}') {
1541
1661
  depth--;
1542
1662
  if (depth === 0) {
1543
- const matchedString = str.slice(openIndex, i + 1);
1663
+ var matchedString = str.slice(openIndex, i + 1);
1544
1664
  return {
1545
1665
  0: matchedString,
1546
1666
  index: openIndex,
@@ -1566,7 +1686,8 @@ CSSOM.parse = function parse(token) {
1566
1686
 
1567
1687
  var validateAtRule = function(atRuleKey, validCallback, cannotBeNested) {
1568
1688
  var isValid = false;
1569
- var ruleRegExp = new RegExp(atRuleKey + forwardRuleValidationRegExp.source, forwardRuleValidationRegExp.flags);
1689
+ var sourceRuleRegExp = atRuleKey === "@import" ? forwardImportRuleValidationRegExp : forwardRuleValidationRegExp;
1690
+ var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
1570
1691
  var ruleSlice = token.slice(i);
1571
1692
  // Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
1572
1693
  var shouldPerformCheck = cannotBeNested && currentScope !== styleSheet ? false : true;
@@ -1588,7 +1709,9 @@ CSSOM.parse = function parse(token) {
1588
1709
  // If it's invalid the browser will simply ignore the entire invalid block
1589
1710
  // Use regex to find the closing brace of the invalid rule
1590
1711
 
1591
- var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp);
1712
+ // Regex used above is not ES5 compliant. Using alternative.
1713
+ // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
1714
+ var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
1592
1715
 
1593
1716
  // If it's a statement inside a nested rule, ignore only the statement
1594
1717
  if (ruleStatementMatch && currentScope !== styleSheet) {
@@ -1597,10 +1720,21 @@ CSSOM.parse = function parse(token) {
1597
1720
  return;
1598
1721
  }
1599
1722
 
1723
+ // Check if there's a semicolon before the invalid at-rule and the first opening brace
1724
+ if (atRuleKey === "@layer") {
1725
+ var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
1726
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
1727
+ // Ignore the rule block until the semicolon
1728
+ i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
1729
+ state = "before-selector";
1730
+ return;
1731
+ }
1732
+ }
1733
+
1600
1734
  // Ignore the entire rule block (if it's a statement it should ignore the statement plus the next block)
1601
1735
  var ruleClosingMatch = matchBalancedBlock(ruleSlice);
1602
1736
  if (ruleClosingMatch) {
1603
- const ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
1737
+ var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
1604
1738
  i+= ignoreRange;
1605
1739
  if (token.charAt(i) === '}') {
1606
1740
  i -= 1;
@@ -1614,8 +1748,17 @@ CSSOM.parse = function parse(token) {
1614
1748
  }
1615
1749
  }
1616
1750
 
1617
- for (var character; (character = token.charAt(i)); i++) {
1751
+ var endingIndex = token.length - 1;
1618
1752
 
1753
+ for (var character; (character = token.charAt(i)); i++) {
1754
+ if (i === endingIndex) {
1755
+ switch (state) {
1756
+ case "importRule":
1757
+ case "layerBlock":
1758
+ token += ";"
1759
+ }
1760
+ }
1761
+
1619
1762
  switch (character) {
1620
1763
 
1621
1764
  case " ":
@@ -1645,6 +1788,9 @@ CSSOM.parse = function parse(token) {
1645
1788
  break;
1646
1789
  case 'importRule-begin':
1647
1790
  state = 'importRule';
1791
+ if (i === endingIndex) {
1792
+ token += ';'
1793
+ }
1648
1794
  break;
1649
1795
  }
1650
1796
  break;
@@ -1793,7 +1939,7 @@ CSSOM.parse = function parse(token) {
1793
1939
  nestedSelectorRule = null;
1794
1940
  }
1795
1941
  if (state === "selector" || state === "atRule") {
1796
- if (!nestedSelectorRule && buffer.includes(";")) {
1942
+ if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
1797
1943
  var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
1798
1944
  if (ruleClosingMatch) {
1799
1945
  styleRule = null;
@@ -1851,15 +1997,19 @@ CSSOM.parse = function parse(token) {
1851
1997
  buffer = "";
1852
1998
  state = "before-selector";
1853
1999
  } else if (state === "layerBlock") {
1854
- layerBlockRule.layerNameText = buffer.trim();
2000
+ layerBlockRule.name = buffer.trim();
1855
2001
 
1856
- if (parentRule) {
1857
- layerBlockRule.parentRule = parentRule;
1858
- ancestorRules.push(parentRule);
1859
- }
2002
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
1860
2003
 
1861
- currentScope = parentRule = layerBlockRule;
1862
- layerBlockRule.parentStyleSheet = styleSheet;
2004
+ if (isValidName) {
2005
+ if (parentRule) {
2006
+ layerBlockRule.parentRule = parentRule;
2007
+ ancestorRules.push(parentRule);
2008
+ }
2009
+
2010
+ currentScope = parentRule = layerBlockRule;
2011
+ layerBlockRule.parentStyleSheet = styleSheet;
2012
+ }
1863
2013
  buffer = "";
1864
2014
  state = "before-selector";
1865
2015
  } else if (state === "hostRule-begin") {
@@ -2022,10 +2172,34 @@ CSSOM.parse = function parse(token) {
2022
2172
  state = "before-selector";
2023
2173
  break;
2024
2174
  case "importRule":
2025
- importRule = new CSSOM.CSSImportRule();
2026
- importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
2027
- importRule.cssText = buffer + character;
2028
- styleSheet.cssRules.push(importRule);
2175
+ var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.some(function (rule) {
2176
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
2177
+ });
2178
+ if (isValid) {
2179
+ importRule = new CSSOM.CSSImportRule();
2180
+ importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
2181
+ importRule.cssText = buffer + character;
2182
+ styleSheet.cssRules.push(importRule);
2183
+ }
2184
+ buffer = "";
2185
+ state = "before-selector";
2186
+ break;
2187
+ case "layerBlock":
2188
+ var nameListStr = buffer.trim().split(",").map(function (name) {
2189
+ return name.trim();
2190
+ });
2191
+ var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
2192
+ return name.trim().match(layerRuleNameRegExp) === null;
2193
+ });
2194
+
2195
+ if (!isInvalid) {
2196
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
2197
+ layerStatementRule.parentStyleSheet = styleSheet;
2198
+ layerStatementRule.__starts = layerBlockRule.__starts;
2199
+ layerStatementRule.__ends = i;
2200
+ layerStatementRule.nameList = nameListStr;
2201
+ styleSheet.cssRules.push(layerStatementRule);
2202
+ }
2029
2203
  buffer = "";
2030
2204
  state = "before-selector";
2031
2205
  break;
@@ -2084,8 +2258,6 @@ CSSOM.parse = function parse(token) {
2084
2258
  //parseError("Unexpected }");
2085
2259
  }
2086
2260
 
2087
- // Handle rules nested in @media or @supports
2088
- hasAncestors = ancestorRules.length > 0;
2089
2261
 
2090
2262
  while (ancestorRules.length > 0) {
2091
2263
  parentRule = ancestorRules.pop();
@@ -2116,10 +2288,6 @@ CSSOM.parse = function parse(token) {
2116
2288
  break;
2117
2289
  }
2118
2290
  }
2119
-
2120
- if (ancestorRules.length === 0) {
2121
- hasAncestors = false;
2122
- }
2123
2291
  }
2124
2292
 
2125
2293
  if (currentScope.parentRule == null) {
@@ -2133,8 +2301,12 @@ CSSOM.parse = function parse(token) {
2133
2301
  if (nestedSelectorRule === parentRule) {
2134
2302
  // Check if this selector is really starting inside another selector
2135
2303
  var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
2136
-
2137
- if (nestedSelectorTokenToCurrentSelectorToken.match(/{/g)?.length === nestedSelectorTokenToCurrentSelectorToken.match(/}/g)?.length) {
2304
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
2305
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
2306
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
2307
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
2308
+
2309
+ if (openingBraceLen === closingBraceLen) {
2138
2310
  // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
2139
2311
  nestedSelectorRule.__ends = i + 1;
2140
2312
  nestedSelectorRule = null;
@@ -2263,6 +2435,10 @@ CSSOM.clone = function clone(stylesheet) {
2263
2435
  ruleClone.mediaText = rule.mediaText;
2264
2436
  }
2265
2437
 
2438
+ if (rule.hasOwnProperty('supportsText')) {
2439
+ ruleClone.supports = rule.supports;
2440
+ }
2441
+
2266
2442
  if (rule.hasOwnProperty('conditionText')) {
2267
2443
  ruleClone.conditionText = rule.conditionText;
2268
2444
  }
@@ -2271,6 +2447,18 @@ CSSOM.clone = function clone(stylesheet) {
2271
2447
  ruleClone.layerName = rule.layerName;
2272
2448
  }
2273
2449
 
2450
+ if (rule.hasOwnProperty('href')) {
2451
+ ruleClone.href = rule.href;
2452
+ }
2453
+
2454
+ if (rule.hasOwnProperty('name')) {
2455
+ ruleClone.name = rule.name;
2456
+ }
2457
+
2458
+ if (rule.hasOwnProperty('nameList')) {
2459
+ ruleClone.nameList = rule.nameList;
2460
+ }
2461
+
2274
2462
  if (rule.hasOwnProperty('cssRules')) {
2275
2463
  ruleClone.cssRules = clone(rule).cssRules;
2276
2464
  }
@@ -16,6 +16,8 @@ CSSOM.CSSImportRule = function CSSImportRule() {
16
16
  CSSOM.CSSRule.call(this);
17
17
  this.href = "";
18
18
  this.media = new CSSOM.MediaList();
19
+ this.layerName = null;
20
+ this.supportsText = null;
19
21
  this.styleSheet = new CSSOM.CSSStyleSheet();
20
22
  };
21
23
 
@@ -26,7 +28,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
26
28
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
27
29
  get: function() {
28
30
  var mediaText = this.media.mediaText;
29
- return "@import url(" + this.href + ")" + (mediaText ? " " + mediaText : "") + ";";
31
+ return "@import url(" + this.href + ")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
30
32
  },
31
33
  set: function(cssText) {
32
34
  var i = 0;
@@ -42,6 +44,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
42
44
 
43
45
  var buffer = '';
44
46
  var index;
47
+
48
+ var layerRegExp = /layer\(([^)]*)\)/;
49
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
50
+ var supportsRegExp = /supports\(([^)]+)\)/;
51
+ var doubleOrMoreSpacesRegExp = /\s{2,}/g;
52
+
45
53
  for (var character; (character = cssText.charAt(i)); i++) {
46
54
 
47
55
  switch (character) {
@@ -66,6 +74,9 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
66
74
  break;
67
75
 
68
76
  case 'u':
77
+ if (state === 'media') {
78
+ buffer += character;
79
+ }
69
80
  if (state === 'url' && cssText.indexOf('url(', i) === i) {
70
81
  index = cssText.indexOf(')', i + 1);
71
82
  if (index === -1) {
@@ -85,7 +96,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
85
96
  break;
86
97
 
87
98
  case '"':
88
- if (state === 'url') {
99
+ if (state === 'after-import' || state === 'url') {
89
100
  index = cssText.indexOf('"', i + 1);
90
101
  if (!index) {
91
102
  throw i + ": '\"' not found";
@@ -97,7 +108,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
97
108
  break;
98
109
 
99
110
  case "'":
100
- if (state === 'url') {
111
+ if (state === 'after-import' || state === 'url') {
101
112
  index = cssText.indexOf("'", i + 1);
102
113
  if (!index) {
103
114
  throw i + ': "\'" not found';
@@ -111,7 +122,47 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
111
122
  case ';':
112
123
  if (state === 'media') {
113
124
  if (buffer) {
114
- this.media.mediaText = buffer.trim();
125
+ var bufferTrimmed = buffer.trim();
126
+
127
+ if (bufferTrimmed.indexOf('layer') === 0) {
128
+ var layerMatch = bufferTrimmed.match(layerRegExp);
129
+
130
+ if (layerMatch) {
131
+ var layerName = layerMatch[1].trim();
132
+ bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
133
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
134
+ .trim();
135
+
136
+ if (layerName.match(layerRuleNameRegExp) !== null) {
137
+ this.layerName = layerMatch[1].trim();
138
+ } else {
139
+ // REVIEW: In the browser, an empty layer() is not processed as a unamed layer
140
+ // and treats the rest of the string as mediaText, ignoring the parse of supports()
141
+ if (bufferTrimmed) {
142
+ this.media.mediaText = bufferTrimmed;
143
+ return;
144
+ }
145
+ }
146
+ } else {
147
+ this.layerName = "";
148
+ bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
149
+ }
150
+ }
151
+
152
+ var supportsMatch = bufferTrimmed.match(supportsRegExp);
153
+
154
+ if (supportsMatch && supportsMatch.index === 0) {
155
+ // REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
156
+ this.supportsText = supportsMatch[1].trim();
157
+ bufferTrimmed = bufferTrimmed.replace(supportsRegExp, '')
158
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
159
+ .trim();
160
+ }
161
+
162
+ // REVIEW: In the browser, any invalid media is replaced with 'not all'
163
+ if (bufferTrimmed) {
164
+ this.media.mediaText = bufferTrimmed;
165
+ }
115
166
  }
116
167
  }
117
168
  break;
@@ -11,7 +11,7 @@ var CSSOM = {
11
11
  */
12
12
  CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
13
13
  CSSOM.CSSGroupingRule.call(this);
14
- this.layerName = "";
14
+ this.name = "";
15
15
  this.cssRules = [];
16
16
  };
17
17
 
@@ -20,23 +20,13 @@ CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
20
20
  CSSOM.CSSLayerBlockRule.prototype.type = 18;
21
21
 
22
22
  Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
23
- layerNameText: {
24
- get: function () {
25
- return this.layerName;
26
- },
27
- set: function (value) {
28
- this.layerName = value;
29
- },
30
- configurable: true,
31
- enumerable: true,
32
- },
33
23
  cssText: {
34
24
  get: function () {
35
25
  var cssTexts = [];
36
26
  for (var i = 0, length = this.cssRules.length; i < length; i++) {
37
27
  cssTexts.push(this.cssRules[i].cssText);
38
28
  }
39
- return "@layer " + this.layerNameText + " {" + cssTexts.join("") + "}";
29
+ return "@layer " + this.name + (this.name && " ") + "{" + cssTexts.join("") + "}";
40
30
  },
41
31
  configurable: true,
42
32
  enumerable: true,
@@ -0,0 +1,32 @@
1
+ //.CommonJS
2
+ var CSSOM = {
3
+ CSSRule: require("./CSSRule").CSSRule,
4
+ };
5
+ ///CommonJS
6
+
7
+ /**
8
+ * @constructor
9
+ * @see https://drafts.csswg.org/css-cascade-5/#csslayerstatementrule
10
+ */
11
+ CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
12
+ CSSOM.CSSRule.call(this);
13
+ this.nameList = [];
14
+ };
15
+
16
+ CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
17
+ CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
18
+ CSSOM.CSSLayerStatementRule.prototype.type = 0;
19
+
20
+ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
21
+ cssText: {
22
+ get: function () {
23
+ return "@layer " + this.nameList.join(", ") + ";";
24
+ },
25
+ configurable: true,
26
+ enumerable: true,
27
+ },
28
+ });
29
+
30
+ //.CommonJS
31
+ exports.CSSLayerStatementRule = CSSOM.CSSLayerStatementRule;
32
+ ///CommonJS
@@ -46,13 +46,24 @@ CSSOM.CSSStyleDeclaration.prototype = {
46
46
  this[this.length] = name;
47
47
  this.length++;
48
48
  }
49
+
50
+ // If the priority value of the incoming property is "important",
51
+ // or the value of the existing property is not "important",
52
+ // then remove the existing property and rewrite it.
53
+ if (priority || !this._importants[name]) {
54
+ this.removeProperty(name);
55
+ this[this.length] = name;
56
+ this.length++;
57
+ this[name] = value + '';
58
+ this._importants[name] = priority;
59
+ }
49
60
  } else {
50
61
  // New property.
51
62
  this[this.length] = name;
52
63
  this.length++;
64
+ this[name] = value + '';
65
+ this._importants[name] = priority;
53
66
  }
54
- this[name] = value + "";
55
- this._importants[name] = priority;
56
67
  },
57
68
 
58
69
  /**
@@ -32,11 +32,14 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
32
32
  * -> "img{border:none;}body{margin:0;}"
33
33
  *
34
34
  * @param {string} rule
35
- * @param {number} index
35
+ * @param {number} [index=0]
36
36
  * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
37
37
  * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
38
38
  */
39
39
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
40
+ if (index === void 0) {
41
+ index = 0;
42
+ }
40
43
  if (index < 0 || index > this.cssRules.length) {
41
44
  throw new RangeError("INDEX_SIZE_ERR");
42
45
  }
package/lib/clone.js CHANGED
@@ -12,7 +12,8 @@ var CSSOM = {
12
12
  CSSStyleDeclaration: require("./CSSStyleDeclaration").CSSStyleDeclaration,
13
13
  CSSKeyframeRule: require('./CSSKeyframeRule').CSSKeyframeRule,
14
14
  CSSKeyframesRule: require('./CSSKeyframesRule').CSSKeyframesRule,
15
- CSSLayerBlockRule: require('./CSSLayerBlockRule').CSSLayerBlockRule
15
+ CSSLayerBlockRule: require('./CSSLayerBlockRule').CSSLayerBlockRule,
16
+ CSSLayerStatementRule: require('./CSSLayerStatementRule').CSSLayerStatementRule
16
17
  };
17
18
  ///CommonJS
18
19
 
@@ -59,6 +60,10 @@ CSSOM.clone = function clone(stylesheet) {
59
60
  ruleClone.mediaText = rule.mediaText;
60
61
  }
61
62
 
63
+ if (rule.hasOwnProperty('supportsText')) {
64
+ ruleClone.supports = rule.supports;
65
+ }
66
+
62
67
  if (rule.hasOwnProperty('conditionText')) {
63
68
  ruleClone.conditionText = rule.conditionText;
64
69
  }
@@ -67,6 +72,18 @@ CSSOM.clone = function clone(stylesheet) {
67
72
  ruleClone.layerName = rule.layerName;
68
73
  }
69
74
 
75
+ if (rule.hasOwnProperty('href')) {
76
+ ruleClone.href = rule.href;
77
+ }
78
+
79
+ if (rule.hasOwnProperty('name')) {
80
+ ruleClone.name = rule.name;
81
+ }
82
+
83
+ if (rule.hasOwnProperty('nameList')) {
84
+ ruleClone.nameList = rule.nameList;
85
+ }
86
+
70
87
  if (rule.hasOwnProperty('cssRules')) {
71
88
  ruleClone.cssRules = clone(rule).cssRules;
72
89
  }
package/lib/index.js CHANGED
@@ -23,5 +23,6 @@ exports.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
23
23
  exports.CSSValue = require('./CSSValue').CSSValue;
24
24
  exports.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
25
25
  exports.CSSLayerBlockRule = require('./CSSLayerBlockRule').CSSLayerBlockRule;
26
+ exports.CSSLayerStatementRule = require('./CSSLayerStatementRule').CSSLayerStatementRule;
26
27
  exports.parse = require('./parse').parse;
27
28
  exports.clone = require('./clone').clone;
package/lib/parse.js CHANGED
@@ -50,17 +50,57 @@ CSSOM.parse = function parse(token) {
50
50
  var parentRule;
51
51
 
52
52
  var ancestorRules = [];
53
- var hasAncestors = false;
54
53
  var prevScope;
55
54
 
56
- var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, nestedSelectorRule;
55
+ var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
57
56
 
58
57
  var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
59
- var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
58
+ // Regex above is not ES5 compliant
59
+ // var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
60
60
  var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
61
61
  var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
62
62
  var forwardRuleValidationRegExp = /(?:\(|\s|\/\*)/; // Match that the rule is followed by any whitespace, a opening comment or a condition opening parenthesis
63
+ var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
63
64
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
65
+ var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
66
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
67
+
68
+ /**
69
+ * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
70
+ * that is not inside a brace block within the given string. Mimics the behavior of a
71
+ * regular expression match for such terminators, including any trailing whitespace.
72
+ * @param {string} str - The string to search for at-rule statement terminators.
73
+ * @returns {object | null} {0: string, index: number} or null if no match is found.
74
+ */
75
+ function atRulesStatemenRegExpES5Alternative(ruleSlice) {
76
+ for (var i = 0; i < ruleSlice.length; i++) {
77
+ var char = ruleSlice[i];
78
+
79
+ if (char === ';' || char === '}') {
80
+ // Simulate negative lookbehind: check if there is a { before this position
81
+ var sliceBefore = ruleSlice.substring(0, i);
82
+ var openBraceIndex = sliceBefore.indexOf('{');
83
+
84
+ if (openBraceIndex === -1) {
85
+ // No { found before, so we treat it as a valid match
86
+ var match = char;
87
+ var j = i + 1;
88
+
89
+ while (j < ruleSlice.length && /\s/.test(ruleSlice[j])) {
90
+ match += ruleSlice[j];
91
+ j++;
92
+ }
93
+
94
+ var matchObj = [match];
95
+ matchObj.index = i;
96
+ matchObj.input = ruleSlice;
97
+ return matchObj;
98
+ }
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
64
104
 
65
105
  /**
66
106
  * Finds the first balanced block (including nested braces) in the string, starting from fromIndex.
@@ -69,17 +109,18 @@ CSSOM.parse = function parse(token) {
69
109
  * @param {number} [fromIndex=0] - The index to start searching from.
70
110
  * @returns {object|null} - { 0: matchedString, index: startIndex, input: str } or null if not found.
71
111
  */
72
- function matchBalancedBlock(str, fromIndex = 0) {
73
- const openIndex = str.indexOf('{', fromIndex);
112
+ function matchBalancedBlock(str, fromIndex) {
113
+ fromIndex = fromIndex || 0;
114
+ var openIndex = str.indexOf('{', fromIndex);
74
115
  if (openIndex === -1) return null;
75
- let depth = 0;
76
- for (let i = openIndex; i < str.length; i++) {
116
+ var depth = 0;
117
+ for (var i = openIndex; i < str.length; i++) {
77
118
  if (str[i] === '{') {
78
119
  depth++;
79
120
  } else if (str[i] === '}') {
80
121
  depth--;
81
122
  if (depth === 0) {
82
- const matchedString = str.slice(openIndex, i + 1);
123
+ var matchedString = str.slice(openIndex, i + 1);
83
124
  return {
84
125
  0: matchedString,
85
126
  index: openIndex,
@@ -105,7 +146,8 @@ CSSOM.parse = function parse(token) {
105
146
 
106
147
  var validateAtRule = function(atRuleKey, validCallback, cannotBeNested) {
107
148
  var isValid = false;
108
- var ruleRegExp = new RegExp(atRuleKey + forwardRuleValidationRegExp.source, forwardRuleValidationRegExp.flags);
149
+ var sourceRuleRegExp = atRuleKey === "@import" ? forwardImportRuleValidationRegExp : forwardRuleValidationRegExp;
150
+ var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
109
151
  var ruleSlice = token.slice(i);
110
152
  // Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
111
153
  var shouldPerformCheck = cannotBeNested && currentScope !== styleSheet ? false : true;
@@ -127,7 +169,9 @@ CSSOM.parse = function parse(token) {
127
169
  // If it's invalid the browser will simply ignore the entire invalid block
128
170
  // Use regex to find the closing brace of the invalid rule
129
171
 
130
- var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp);
172
+ // Regex used above is not ES5 compliant. Using alternative.
173
+ // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
174
+ var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
131
175
 
132
176
  // If it's a statement inside a nested rule, ignore only the statement
133
177
  if (ruleStatementMatch && currentScope !== styleSheet) {
@@ -136,10 +180,21 @@ CSSOM.parse = function parse(token) {
136
180
  return;
137
181
  }
138
182
 
183
+ // Check if there's a semicolon before the invalid at-rule and the first opening brace
184
+ if (atRuleKey === "@layer") {
185
+ var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
186
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
187
+ // Ignore the rule block until the semicolon
188
+ i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
189
+ state = "before-selector";
190
+ return;
191
+ }
192
+ }
193
+
139
194
  // Ignore the entire rule block (if it's a statement it should ignore the statement plus the next block)
140
195
  var ruleClosingMatch = matchBalancedBlock(ruleSlice);
141
196
  if (ruleClosingMatch) {
142
- const ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
197
+ var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
143
198
  i+= ignoreRange;
144
199
  if (token.charAt(i) === '}') {
145
200
  i -= 1;
@@ -153,8 +208,17 @@ CSSOM.parse = function parse(token) {
153
208
  }
154
209
  }
155
210
 
156
- for (var character; (character = token.charAt(i)); i++) {
211
+ var endingIndex = token.length - 1;
157
212
 
213
+ for (var character; (character = token.charAt(i)); i++) {
214
+ if (i === endingIndex) {
215
+ switch (state) {
216
+ case "importRule":
217
+ case "layerBlock":
218
+ token += ";"
219
+ }
220
+ }
221
+
158
222
  switch (character) {
159
223
 
160
224
  case " ":
@@ -184,6 +248,9 @@ CSSOM.parse = function parse(token) {
184
248
  break;
185
249
  case 'importRule-begin':
186
250
  state = 'importRule';
251
+ if (i === endingIndex) {
252
+ token += ';'
253
+ }
187
254
  break;
188
255
  }
189
256
  break;
@@ -332,7 +399,7 @@ CSSOM.parse = function parse(token) {
332
399
  nestedSelectorRule = null;
333
400
  }
334
401
  if (state === "selector" || state === "atRule") {
335
- if (!nestedSelectorRule && buffer.includes(";")) {
402
+ if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
336
403
  var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
337
404
  if (ruleClosingMatch) {
338
405
  styleRule = null;
@@ -390,15 +457,19 @@ CSSOM.parse = function parse(token) {
390
457
  buffer = "";
391
458
  state = "before-selector";
392
459
  } else if (state === "layerBlock") {
393
- layerBlockRule.layerNameText = buffer.trim();
460
+ layerBlockRule.name = buffer.trim();
394
461
 
395
- if (parentRule) {
396
- layerBlockRule.parentRule = parentRule;
397
- ancestorRules.push(parentRule);
398
- }
462
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
399
463
 
400
- currentScope = parentRule = layerBlockRule;
401
- layerBlockRule.parentStyleSheet = styleSheet;
464
+ if (isValidName) {
465
+ if (parentRule) {
466
+ layerBlockRule.parentRule = parentRule;
467
+ ancestorRules.push(parentRule);
468
+ }
469
+
470
+ currentScope = parentRule = layerBlockRule;
471
+ layerBlockRule.parentStyleSheet = styleSheet;
472
+ }
402
473
  buffer = "";
403
474
  state = "before-selector";
404
475
  } else if (state === "hostRule-begin") {
@@ -561,10 +632,34 @@ CSSOM.parse = function parse(token) {
561
632
  state = "before-selector";
562
633
  break;
563
634
  case "importRule":
564
- importRule = new CSSOM.CSSImportRule();
565
- importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
566
- importRule.cssText = buffer + character;
567
- styleSheet.cssRules.push(importRule);
635
+ var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.some(function (rule) {
636
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
637
+ });
638
+ if (isValid) {
639
+ importRule = new CSSOM.CSSImportRule();
640
+ importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
641
+ importRule.cssText = buffer + character;
642
+ styleSheet.cssRules.push(importRule);
643
+ }
644
+ buffer = "";
645
+ state = "before-selector";
646
+ break;
647
+ case "layerBlock":
648
+ var nameListStr = buffer.trim().split(",").map(function (name) {
649
+ return name.trim();
650
+ });
651
+ var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
652
+ return name.trim().match(layerRuleNameRegExp) === null;
653
+ });
654
+
655
+ if (!isInvalid) {
656
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
657
+ layerStatementRule.parentStyleSheet = styleSheet;
658
+ layerStatementRule.__starts = layerBlockRule.__starts;
659
+ layerStatementRule.__ends = i;
660
+ layerStatementRule.nameList = nameListStr;
661
+ styleSheet.cssRules.push(layerStatementRule);
662
+ }
568
663
  buffer = "";
569
664
  state = "before-selector";
570
665
  break;
@@ -623,8 +718,6 @@ CSSOM.parse = function parse(token) {
623
718
  //parseError("Unexpected }");
624
719
  }
625
720
 
626
- // Handle rules nested in @media or @supports
627
- hasAncestors = ancestorRules.length > 0;
628
721
 
629
722
  while (ancestorRules.length > 0) {
630
723
  parentRule = ancestorRules.pop();
@@ -655,10 +748,6 @@ CSSOM.parse = function parse(token) {
655
748
  break;
656
749
  }
657
750
  }
658
-
659
- if (ancestorRules.length === 0) {
660
- hasAncestors = false;
661
- }
662
751
  }
663
752
 
664
753
  if (currentScope.parentRule == null) {
@@ -672,8 +761,12 @@ CSSOM.parse = function parse(token) {
672
761
  if (nestedSelectorRule === parentRule) {
673
762
  // Check if this selector is really starting inside another selector
674
763
  var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
675
-
676
- if (nestedSelectorTokenToCurrentSelectorToken.match(/{/g)?.length === nestedSelectorTokenToCurrentSelectorToken.match(/}/g)?.length) {
764
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
765
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
766
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
767
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
768
+
769
+ if (openingBraceLen === closingBraceLen) {
677
770
  // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
678
771
  nestedSelectorRule.__ends = i + 1;
679
772
  nestedSelectorRule = null;
@@ -780,4 +873,5 @@ CSSOM.CSSKeyframesRule = require('./CSSKeyframesRule').CSSKeyframesRule;
780
873
  CSSOM.CSSValueExpression = require('./CSSValueExpression').CSSValueExpression;
781
874
  CSSOM.CSSDocumentRule = require('./CSSDocumentRule').CSSDocumentRule;
782
875
  CSSOM.CSSLayerBlockRule = require("./CSSLayerBlockRule").CSSLayerBlockRule;
876
+ CSSOM.CSSLayerStatementRule = require("./CSSLayerStatementRule").CSSLayerStatementRule;
783
877
  ///CommonJS
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "parser",
8
8
  "styleSheet"
9
9
  ],
10
- "version": "0.9.0",
10
+ "version": "0.9.1",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"