@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 +238 -50
- package/lib/CSSImportRule.js +55 -4
- package/lib/CSSLayerBlockRule.js +2 -12
- package/lib/CSSLayerStatementRule.js +32 -0
- package/lib/CSSStyleDeclaration.js +13 -2
- package/lib/CSSStyleSheet.js +4 -1
- package/lib/clone.js +18 -1
- package/lib/index.js +1 -0
- package/lib/parse.js +126 -32
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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
|
-
|
|
1508
|
+
cssText: {
|
|
1444
1509
|
get: function () {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1534
|
-
|
|
1652
|
+
function matchBalancedBlock(str, fromIndex) {
|
|
1653
|
+
fromIndex = fromIndex || 0;
|
|
1654
|
+
var openIndex = str.indexOf('{', fromIndex);
|
|
1535
1655
|
if (openIndex === -1) return null;
|
|
1536
|
-
|
|
1537
|
-
for (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
2000
|
+
layerBlockRule.name = buffer.trim();
|
|
1855
2001
|
|
|
1856
|
-
|
|
1857
|
-
layerBlockRule.parentRule = parentRule;
|
|
1858
|
-
ancestorRules.push(parentRule);
|
|
1859
|
-
}
|
|
2002
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
|
|
1860
2003
|
|
|
1861
|
-
|
|
1862
|
-
|
|
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
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/CSSImportRule.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/lib/CSSLayerBlockRule.js
CHANGED
|
@@ -11,7 +11,7 @@ var CSSOM = {
|
|
|
11
11
|
*/
|
|
12
12
|
CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
|
|
13
13
|
CSSOM.CSSGroupingRule.call(this);
|
|
14
|
-
this.
|
|
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.
|
|
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
|
/**
|
package/lib/CSSStyleSheet.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
73
|
-
|
|
112
|
+
function matchBalancedBlock(str, fromIndex) {
|
|
113
|
+
fromIndex = fromIndex || 0;
|
|
114
|
+
var openIndex = str.indexOf('{', fromIndex);
|
|
74
115
|
if (openIndex === -1) return null;
|
|
75
|
-
|
|
76
|
-
for (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
460
|
+
layerBlockRule.name = buffer.trim();
|
|
394
461
|
|
|
395
|
-
|
|
396
|
-
layerBlockRule.parentRule = parentRule;
|
|
397
|
-
ancestorRules.push(parentRule);
|
|
398
|
-
}
|
|
462
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
|
|
399
463
|
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
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
|