@acemir/cssom 0.9.0 → 0.9.2
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 +485 -70
- package/lib/CSSImportRule.js +55 -4
- package/lib/CSSLayerBlockRule.js +2 -12
- package/lib/CSSLayerStatementRule.js +32 -0
- package/lib/CSSStyleDeclaration.js +41 -4
- package/lib/CSSStyleSheet.js +4 -1
- package/lib/clone.js +18 -1
- package/lib/index.js +1 -0
- package/lib/parse.js +344 -50
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
var CSSOM = {};
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
// NOTE: Check viability to add a validation for css values or use a dependency like csstree-validator
|
|
5
|
+
/**
|
|
6
|
+
* Regular expression to detect invalid characters in the value portion of a CSS style declaration.
|
|
7
|
+
*
|
|
8
|
+
* This regex matches a colon (:) that is not inside parentheses and not inside single or double quotes.
|
|
9
|
+
* It is used to ensure that the value part of a CSS property does not contain unexpected colons,
|
|
10
|
+
* which would indicate a malformed declaration (e.g., "color: foo:bar;" is invalid).
|
|
11
|
+
*
|
|
12
|
+
* The negative lookahead `(?![^(]*\))` ensures that the colon is not followed by a closing
|
|
13
|
+
* parenthesis without encountering an opening parenthesis, effectively ignoring colons inside
|
|
14
|
+
* function-like values (e.g., `url(data:image/png;base64,...)`).
|
|
15
|
+
*
|
|
16
|
+
* The lookahead `(?=(?:[^'"]|'[^']*'|"[^"]*")*$)` ensures that the colon is not inside single or double quotes,
|
|
17
|
+
* allowing colons within quoted strings (e.g., `content: ":";` or `background: url("foo:bar.png");`).
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
* "color: red;" // valid, does not match
|
|
21
|
+
* "background: url(data:image/png;base64,...);" // valid, does not match
|
|
22
|
+
* "content: ':';" // valid, does not match
|
|
23
|
+
* "color: foo:bar;" // invalid, matches
|
|
24
|
+
*/
|
|
25
|
+
var basicStylePropertyValueValidationRegExp = /:(?![^(]*\))(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/;
|
|
26
|
+
|
|
4
27
|
/**
|
|
5
28
|
* @constructor
|
|
6
29
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
|
|
@@ -36,21 +59,36 @@ CSSOM.CSSStyleDeclaration.prototype = {
|
|
|
36
59
|
* @param {string} [priority=null] "important" or null
|
|
37
60
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
|
|
38
61
|
*/
|
|
39
|
-
setProperty: function(name, value, priority)
|
|
40
|
-
|
|
62
|
+
setProperty: function(name, value, priority, parseErrorHandler)
|
|
63
|
+
{
|
|
64
|
+
// NOTE: Check viability to add a validation for css values or use a dependency like csstree-validator
|
|
65
|
+
if (basicStylePropertyValueValidationRegExp.test(value)) {
|
|
66
|
+
parseErrorHandler && parseErrorHandler('Invalid CSSStyleDeclaration property value');
|
|
67
|
+
} else if (this[name]) {
|
|
41
68
|
// Property already exist. Overwrite it.
|
|
42
69
|
var index = Array.prototype.indexOf.call(this, name);
|
|
43
70
|
if (index < 0) {
|
|
44
71
|
this[this.length] = name;
|
|
45
72
|
this.length++;
|
|
46
73
|
}
|
|
74
|
+
|
|
75
|
+
// If the priority value of the incoming property is "important",
|
|
76
|
+
// or the value of the existing property is not "important",
|
|
77
|
+
// then remove the existing property and rewrite it.
|
|
78
|
+
if (priority || !this._importants[name]) {
|
|
79
|
+
this.removeProperty(name);
|
|
80
|
+
this[this.length] = name;
|
|
81
|
+
this.length++;
|
|
82
|
+
this[name] = value + '';
|
|
83
|
+
this._importants[name] = priority;
|
|
84
|
+
}
|
|
47
85
|
} else {
|
|
48
86
|
// New property.
|
|
49
87
|
this[this.length] = name;
|
|
50
88
|
this.length++;
|
|
89
|
+
this[name] = value + '';
|
|
90
|
+
this._importants[name] = priority;
|
|
51
91
|
}
|
|
52
|
-
this[name] = value + "";
|
|
53
|
-
this._importants[name] = priority;
|
|
54
92
|
},
|
|
55
93
|
|
|
56
94
|
/**
|
|
@@ -632,6 +670,8 @@ CSSOM.CSSImportRule = function CSSImportRule() {
|
|
|
632
670
|
CSSOM.CSSRule.call(this);
|
|
633
671
|
this.href = "";
|
|
634
672
|
this.media = new CSSOM.MediaList();
|
|
673
|
+
this.layerName = null;
|
|
674
|
+
this.supportsText = null;
|
|
635
675
|
this.styleSheet = new CSSOM.CSSStyleSheet();
|
|
636
676
|
};
|
|
637
677
|
|
|
@@ -642,7 +682,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
|
|
|
642
682
|
Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
643
683
|
get: function() {
|
|
644
684
|
var mediaText = this.media.mediaText;
|
|
645
|
-
return "@import url(" + this.href + ")" + (mediaText ? " " + mediaText : "") + ";";
|
|
685
|
+
return "@import url(" + this.href + ")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
|
|
646
686
|
},
|
|
647
687
|
set: function(cssText) {
|
|
648
688
|
var i = 0;
|
|
@@ -658,6 +698,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
658
698
|
|
|
659
699
|
var buffer = '';
|
|
660
700
|
var index;
|
|
701
|
+
|
|
702
|
+
var layerRegExp = /layer\(([^)]*)\)/;
|
|
703
|
+
var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
|
|
704
|
+
var supportsRegExp = /supports\(([^)]+)\)/;
|
|
705
|
+
var doubleOrMoreSpacesRegExp = /\s{2,}/g;
|
|
706
|
+
|
|
661
707
|
for (var character; (character = cssText.charAt(i)); i++) {
|
|
662
708
|
|
|
663
709
|
switch (character) {
|
|
@@ -682,6 +728,9 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
682
728
|
break;
|
|
683
729
|
|
|
684
730
|
case 'u':
|
|
731
|
+
if (state === 'media') {
|
|
732
|
+
buffer += character;
|
|
733
|
+
}
|
|
685
734
|
if (state === 'url' && cssText.indexOf('url(', i) === i) {
|
|
686
735
|
index = cssText.indexOf(')', i + 1);
|
|
687
736
|
if (index === -1) {
|
|
@@ -701,7 +750,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
701
750
|
break;
|
|
702
751
|
|
|
703
752
|
case '"':
|
|
704
|
-
if (state === 'url') {
|
|
753
|
+
if (state === 'after-import' || state === 'url') {
|
|
705
754
|
index = cssText.indexOf('"', i + 1);
|
|
706
755
|
if (!index) {
|
|
707
756
|
throw i + ": '\"' not found";
|
|
@@ -713,7 +762,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
713
762
|
break;
|
|
714
763
|
|
|
715
764
|
case "'":
|
|
716
|
-
if (state === 'url') {
|
|
765
|
+
if (state === 'after-import' || state === 'url') {
|
|
717
766
|
index = cssText.indexOf("'", i + 1);
|
|
718
767
|
if (!index) {
|
|
719
768
|
throw i + ': "\'" not found';
|
|
@@ -727,7 +776,47 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
727
776
|
case ';':
|
|
728
777
|
if (state === 'media') {
|
|
729
778
|
if (buffer) {
|
|
730
|
-
|
|
779
|
+
var bufferTrimmed = buffer.trim();
|
|
780
|
+
|
|
781
|
+
if (bufferTrimmed.indexOf('layer') === 0) {
|
|
782
|
+
var layerMatch = bufferTrimmed.match(layerRegExp);
|
|
783
|
+
|
|
784
|
+
if (layerMatch) {
|
|
785
|
+
var layerName = layerMatch[1].trim();
|
|
786
|
+
bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
|
|
787
|
+
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
|
|
788
|
+
.trim();
|
|
789
|
+
|
|
790
|
+
if (layerName.match(layerRuleNameRegExp) !== null) {
|
|
791
|
+
this.layerName = layerMatch[1].trim();
|
|
792
|
+
} else {
|
|
793
|
+
// REVIEW: In the browser, an empty layer() is not processed as a unamed layer
|
|
794
|
+
// and treats the rest of the string as mediaText, ignoring the parse of supports()
|
|
795
|
+
if (bufferTrimmed) {
|
|
796
|
+
this.media.mediaText = bufferTrimmed;
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
this.layerName = "";
|
|
802
|
+
bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
var supportsMatch = bufferTrimmed.match(supportsRegExp);
|
|
807
|
+
|
|
808
|
+
if (supportsMatch && supportsMatch.index === 0) {
|
|
809
|
+
// REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
|
|
810
|
+
this.supportsText = supportsMatch[1].trim();
|
|
811
|
+
bufferTrimmed = bufferTrimmed.replace(supportsRegExp, '')
|
|
812
|
+
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
|
|
813
|
+
.trim();
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// REVIEW: In the browser, any invalid media is replaced with 'not all'
|
|
817
|
+
if (bufferTrimmed) {
|
|
818
|
+
this.media.mediaText = bufferTrimmed;
|
|
819
|
+
}
|
|
731
820
|
}
|
|
732
821
|
}
|
|
733
822
|
break;
|
|
@@ -862,11 +951,14 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
|
|
|
862
951
|
* -> "img{border:none;}body{margin:0;}"
|
|
863
952
|
*
|
|
864
953
|
* @param {string} rule
|
|
865
|
-
* @param {number} index
|
|
954
|
+
* @param {number} [index=0]
|
|
866
955
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
|
|
867
956
|
* @return {number} The index within the style sheet's rule collection of the newly inserted rule.
|
|
868
957
|
*/
|
|
869
958
|
CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
|
|
959
|
+
if (index === void 0) {
|
|
960
|
+
index = 0;
|
|
961
|
+
}
|
|
870
962
|
if (index < 0 || index > this.cssRules.length) {
|
|
871
963
|
throw new RangeError("INDEX_SIZE_ERR");
|
|
872
964
|
}
|
|
@@ -1431,7 +1523,7 @@ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
|
|
|
1431
1523
|
*/
|
|
1432
1524
|
CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
|
|
1433
1525
|
CSSOM.CSSGroupingRule.call(this);
|
|
1434
|
-
this.
|
|
1526
|
+
this.name = "";
|
|
1435
1527
|
this.cssRules = [];
|
|
1436
1528
|
};
|
|
1437
1529
|
|
|
@@ -1440,23 +1532,37 @@ CSSOM.CSSLayerBlockRule.prototype.constructor = CSSOM.CSSLayerBlockRule;
|
|
|
1440
1532
|
CSSOM.CSSLayerBlockRule.prototype.type = 18;
|
|
1441
1533
|
|
|
1442
1534
|
Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
|
|
1443
|
-
|
|
1535
|
+
cssText: {
|
|
1444
1536
|
get: function () {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1537
|
+
var cssTexts = [];
|
|
1538
|
+
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
1539
|
+
cssTexts.push(this.cssRules[i].cssText);
|
|
1540
|
+
}
|
|
1541
|
+
return "@layer " + this.name + (this.name && " ") + "{" + cssTexts.join("") + "}";
|
|
1449
1542
|
},
|
|
1450
1543
|
configurable: true,
|
|
1451
1544
|
enumerable: true,
|
|
1452
1545
|
},
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* @constructor
|
|
1551
|
+
* @see https://drafts.csswg.org/css-cascade-5/#csslayerstatementrule
|
|
1552
|
+
*/
|
|
1553
|
+
CSSOM.CSSLayerStatementRule = function CSSLayerStatementRule() {
|
|
1554
|
+
CSSOM.CSSRule.call(this);
|
|
1555
|
+
this.nameList = [];
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
CSSOM.CSSLayerStatementRule.prototype = new CSSOM.CSSRule();
|
|
1559
|
+
CSSOM.CSSLayerStatementRule.prototype.constructor = CSSOM.CSSLayerStatementRule;
|
|
1560
|
+
CSSOM.CSSLayerStatementRule.prototype.type = 0;
|
|
1561
|
+
|
|
1562
|
+
Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
|
|
1453
1563
|
cssText: {
|
|
1454
1564
|
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("") + "}";
|
|
1565
|
+
return "@layer " + this.nameList.join(", ") + ";";
|
|
1460
1566
|
},
|
|
1461
1567
|
configurable: true,
|
|
1462
1568
|
enumerable: true,
|
|
@@ -1467,7 +1573,8 @@ Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
|
|
|
1467
1573
|
/**
|
|
1468
1574
|
* @param {string} token
|
|
1469
1575
|
*/
|
|
1470
|
-
CSSOM.parse = function parse(token) {
|
|
1576
|
+
CSSOM.parse = function parse(token, errorHandler) {
|
|
1577
|
+
errorHandler = errorHandler === undefined && (console && console.error);
|
|
1471
1578
|
|
|
1472
1579
|
var i = 0;
|
|
1473
1580
|
|
|
@@ -1489,6 +1596,8 @@ CSSOM.parse = function parse(token) {
|
|
|
1489
1596
|
var valueParenthesisDepth = 0;
|
|
1490
1597
|
|
|
1491
1598
|
var SIGNIFICANT_WHITESPACE = {
|
|
1599
|
+
"name": true,
|
|
1600
|
+
"before-name": true,
|
|
1492
1601
|
"selector": true,
|
|
1493
1602
|
"value": true,
|
|
1494
1603
|
"value-parenthesis": true,
|
|
@@ -1511,17 +1620,57 @@ CSSOM.parse = function parse(token) {
|
|
|
1511
1620
|
var parentRule;
|
|
1512
1621
|
|
|
1513
1622
|
var ancestorRules = [];
|
|
1514
|
-
var hasAncestors = false;
|
|
1515
1623
|
var prevScope;
|
|
1516
1624
|
|
|
1517
|
-
var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, nestedSelectorRule;
|
|
1625
|
+
var name, priority="", styleRule, mediaRule, containerRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule;
|
|
1518
1626
|
|
|
1519
1627
|
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
|
|
1520
|
-
|
|
1628
|
+
// Regex above is not ES5 compliant
|
|
1629
|
+
// var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
|
|
1521
1630
|
var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
|
|
1522
1631
|
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
1632
|
var forwardRuleValidationRegExp = /(?:\(|\s|\/\*)/; // Match that the rule is followed by any whitespace, a opening comment or a condition opening parenthesis
|
|
1633
|
+
var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
|
|
1524
1634
|
var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
|
|
1635
|
+
var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
|
|
1636
|
+
var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
|
|
1637
|
+
|
|
1638
|
+
/**
|
|
1639
|
+
* Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
|
|
1640
|
+
* that is not inside a brace block within the given string. Mimics the behavior of a
|
|
1641
|
+
* regular expression match for such terminators, including any trailing whitespace.
|
|
1642
|
+
* @param {string} str - The string to search for at-rule statement terminators.
|
|
1643
|
+
* @returns {object | null} {0: string, index: number} or null if no match is found.
|
|
1644
|
+
*/
|
|
1645
|
+
function atRulesStatemenRegExpES5Alternative(ruleSlice) {
|
|
1646
|
+
for (var i = 0; i < ruleSlice.length; i++) {
|
|
1647
|
+
var char = ruleSlice[i];
|
|
1648
|
+
|
|
1649
|
+
if (char === ';' || char === '}') {
|
|
1650
|
+
// Simulate negative lookbehind: check if there is a { before this position
|
|
1651
|
+
var sliceBefore = ruleSlice.substring(0, i);
|
|
1652
|
+
var openBraceIndex = sliceBefore.indexOf('{');
|
|
1653
|
+
|
|
1654
|
+
if (openBraceIndex === -1) {
|
|
1655
|
+
// No { found before, so we treat it as a valid match
|
|
1656
|
+
var match = char;
|
|
1657
|
+
var j = i + 1;
|
|
1658
|
+
|
|
1659
|
+
while (j < ruleSlice.length && /\s/.test(ruleSlice[j])) {
|
|
1660
|
+
match += ruleSlice[j];
|
|
1661
|
+
j++;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
var matchObj = [match];
|
|
1665
|
+
matchObj.index = i;
|
|
1666
|
+
matchObj.input = ruleSlice;
|
|
1667
|
+
return matchObj;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1525
1674
|
|
|
1526
1675
|
/**
|
|
1527
1676
|
* Finds the first balanced block (including nested braces) in the string, starting from fromIndex.
|
|
@@ -1530,17 +1679,18 @@ CSSOM.parse = function parse(token) {
|
|
|
1530
1679
|
* @param {number} [fromIndex=0] - The index to start searching from.
|
|
1531
1680
|
* @returns {object|null} - { 0: matchedString, index: startIndex, input: str } or null if not found.
|
|
1532
1681
|
*/
|
|
1533
|
-
function matchBalancedBlock(str, fromIndex
|
|
1534
|
-
|
|
1682
|
+
function matchBalancedBlock(str, fromIndex) {
|
|
1683
|
+
fromIndex = fromIndex || 0;
|
|
1684
|
+
var openIndex = str.indexOf('{', fromIndex);
|
|
1535
1685
|
if (openIndex === -1) return null;
|
|
1536
|
-
|
|
1537
|
-
for (
|
|
1686
|
+
var depth = 0;
|
|
1687
|
+
for (var i = openIndex; i < str.length; i++) {
|
|
1538
1688
|
if (str[i] === '{') {
|
|
1539
1689
|
depth++;
|
|
1540
1690
|
} else if (str[i] === '}') {
|
|
1541
1691
|
depth--;
|
|
1542
1692
|
if (depth === 0) {
|
|
1543
|
-
|
|
1693
|
+
var matchedString = str.slice(openIndex, i + 1);
|
|
1544
1694
|
return {
|
|
1545
1695
|
0: matchedString,
|
|
1546
1696
|
index: openIndex,
|
|
@@ -1552,6 +1702,29 @@ CSSOM.parse = function parse(token) {
|
|
|
1552
1702
|
return null;
|
|
1553
1703
|
}
|
|
1554
1704
|
|
|
1705
|
+
/**
|
|
1706
|
+
* Advances the index `i` to skip over a balanced block of curly braces in the given string.
|
|
1707
|
+
* This is typically used to ignore the contents of a CSS rule block.
|
|
1708
|
+
*
|
|
1709
|
+
* @param {number} i - The current index in the string to start searching from.
|
|
1710
|
+
* @param {string} str - The string containing the CSS code.
|
|
1711
|
+
* @param {number} fromIndex - The index in the string where the balanced block search should begin.
|
|
1712
|
+
* @returns {number} The updated index after skipping the balanced block.
|
|
1713
|
+
*/
|
|
1714
|
+
function ignoreBalancedBlock(i, str, fromIndex) {
|
|
1715
|
+
var ruleClosingMatch = matchBalancedBlock(str, fromIndex);
|
|
1716
|
+
if (ruleClosingMatch) {
|
|
1717
|
+
var ignoreRange = ruleClosingMatch.index + ruleClosingMatch[0].length;
|
|
1718
|
+
i+= ignoreRange;
|
|
1719
|
+
if (token.charAt(i) === '}') {
|
|
1720
|
+
i -= 1;
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
i += str.length;
|
|
1724
|
+
}
|
|
1725
|
+
return i;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1555
1728
|
var parseError = function(message) {
|
|
1556
1729
|
var lines = token.substring(0, i).split('\n');
|
|
1557
1730
|
var lineCount = lines.length;
|
|
@@ -1561,12 +1734,18 @@ CSSOM.parse = function parse(token) {
|
|
|
1561
1734
|
/* jshint sub : true */
|
|
1562
1735
|
error['char'] = charCount;
|
|
1563
1736
|
error.styleSheet = styleSheet;
|
|
1564
|
-
|
|
1737
|
+
// Print the error but continue parsing the sheet
|
|
1738
|
+
try {
|
|
1739
|
+
throw error;
|
|
1740
|
+
} catch(e) {
|
|
1741
|
+
errorHandler && errorHandler(e);
|
|
1742
|
+
}
|
|
1565
1743
|
};
|
|
1566
1744
|
|
|
1567
1745
|
var validateAtRule = function(atRuleKey, validCallback, cannotBeNested) {
|
|
1568
1746
|
var isValid = false;
|
|
1569
|
-
var
|
|
1747
|
+
var sourceRuleRegExp = atRuleKey === "@import" ? forwardImportRuleValidationRegExp : forwardRuleValidationRegExp;
|
|
1748
|
+
var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
|
|
1570
1749
|
var ruleSlice = token.slice(i);
|
|
1571
1750
|
// Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
|
|
1572
1751
|
var shouldPerformCheck = cannotBeNested && currentScope !== styleSheet ? false : true;
|
|
@@ -1588,7 +1767,9 @@ CSSOM.parse = function parse(token) {
|
|
|
1588
1767
|
// If it's invalid the browser will simply ignore the entire invalid block
|
|
1589
1768
|
// Use regex to find the closing brace of the invalid rule
|
|
1590
1769
|
|
|
1591
|
-
|
|
1770
|
+
// Regex used above is not ES5 compliant. Using alternative.
|
|
1771
|
+
// var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
|
|
1772
|
+
var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
|
|
1592
1773
|
|
|
1593
1774
|
// If it's a statement inside a nested rule, ignore only the statement
|
|
1594
1775
|
if (ruleStatementMatch && currentScope !== styleSheet) {
|
|
@@ -1597,25 +1778,179 @@ CSSOM.parse = function parse(token) {
|
|
|
1597
1778
|
return;
|
|
1598
1779
|
}
|
|
1599
1780
|
|
|
1600
|
-
//
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1781
|
+
// Check if there's a semicolon before the invalid at-rule and the first opening brace
|
|
1782
|
+
if (atRuleKey === "@layer") {
|
|
1783
|
+
var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
|
|
1784
|
+
if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";" ) {
|
|
1785
|
+
// Ignore the rule block until the semicolon
|
|
1786
|
+
i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
|
|
1787
|
+
state = "before-selector";
|
|
1788
|
+
return;
|
|
1607
1789
|
}
|
|
1608
|
-
} else {
|
|
1609
|
-
i += ruleSlice.length;
|
|
1610
1790
|
}
|
|
1791
|
+
|
|
1792
|
+
// Ignore the entire rule block (if it's a statement it should ignore the statement plus the next block)
|
|
1793
|
+
i = ignoreBalancedBlock(i, ruleSlice);
|
|
1611
1794
|
state = "before-selector";
|
|
1612
1795
|
} else {
|
|
1613
1796
|
validCallback.call(this);
|
|
1614
1797
|
}
|
|
1615
1798
|
}
|
|
1616
1799
|
|
|
1617
|
-
|
|
1800
|
+
/**
|
|
1801
|
+
* Regular expression to match a basic CSS selector.
|
|
1802
|
+
*
|
|
1803
|
+
* This regex matches the following selector components:
|
|
1804
|
+
* - Type selectors (e.g., `div`, `span`)
|
|
1805
|
+
* - Universal selector (`*`)
|
|
1806
|
+
* - ID selectors (e.g., `#header`)
|
|
1807
|
+
* - Class selectors (e.g., `.container`)
|
|
1808
|
+
* - Attribute selectors (e.g., `[type="text"]`)
|
|
1809
|
+
* - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
|
|
1810
|
+
* - The parent selector (`&`)
|
|
1811
|
+
* - Combinators (`>`, `+`, `~`) with optional whitespace
|
|
1812
|
+
* - Whitespace (descendant combinator)
|
|
1813
|
+
*
|
|
1814
|
+
* The pattern ensures that a string consists only of valid basic selector components,
|
|
1815
|
+
* possibly repeated and combined, but does not match full CSS selector groups separated by commas.
|
|
1816
|
+
*
|
|
1817
|
+
* @type {RegExp}
|
|
1818
|
+
*/
|
|
1819
|
+
var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*\]|::?[a-zA-Z0-9_-]+(?:\([^\(\)]*\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Regular expression to match CSS pseudo-classes with arguments.
|
|
1823
|
+
*
|
|
1824
|
+
* Matches patterns like `:pseudo-class(argument)`, capturing the pseudo-class name and its argument.
|
|
1825
|
+
*
|
|
1826
|
+
* Capture groups:
|
|
1827
|
+
* 1. The pseudo-class name (letters and hyphens).
|
|
1828
|
+
* 2. The argument inside the parentheses (any characters except a closing parenthesis).
|
|
1829
|
+
*
|
|
1830
|
+
* Global flag (`g`) is used to find all matches in the input string.
|
|
1831
|
+
*
|
|
1832
|
+
* Example match: `:nth-child(2n+1)`
|
|
1833
|
+
* - Group 1: "nth-child"
|
|
1834
|
+
* - Group 2: "2n+1"
|
|
1835
|
+
* @type {RegExp}
|
|
1836
|
+
*/
|
|
1837
|
+
var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(([^)]*)\)/g;
|
|
1838
|
+
|
|
1839
|
+
/**
|
|
1840
|
+
* Parses a CSS selector string and splits it into parts, handling nested parentheses.
|
|
1841
|
+
*
|
|
1842
|
+
* This function is useful for splitting selectors that may contain nested function-like
|
|
1843
|
+
* syntax (e.g., :not(.foo, .bar)), ensuring that commas inside parentheses do not split
|
|
1844
|
+
* the selector.
|
|
1845
|
+
*
|
|
1846
|
+
* @param {string} selector - The CSS selector string to parse.
|
|
1847
|
+
* @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
|
|
1848
|
+
*/
|
|
1849
|
+
function parseNestedSelectors(selector) {
|
|
1850
|
+
var depth = 0;
|
|
1851
|
+
var buffer = "";
|
|
1852
|
+
var parts = [];
|
|
1853
|
+
var i, char;
|
|
1854
|
+
|
|
1855
|
+
for (i = 0; i < selector.length; i++) {
|
|
1856
|
+
char = selector.charAt(i);
|
|
1618
1857
|
|
|
1858
|
+
if (char === '(') {
|
|
1859
|
+
depth++;
|
|
1860
|
+
buffer += char;
|
|
1861
|
+
} else if (char === ')') {
|
|
1862
|
+
depth--;
|
|
1863
|
+
buffer += char;
|
|
1864
|
+
if (depth === 0) {
|
|
1865
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1866
|
+
buffer = "";
|
|
1867
|
+
}
|
|
1868
|
+
} else if (char === ',' && depth === 0) {
|
|
1869
|
+
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
1870
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1871
|
+
}
|
|
1872
|
+
buffer = "";
|
|
1873
|
+
} else {
|
|
1874
|
+
buffer += char;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (buffer.replace(/^\s+|\s+$/g, "")) {
|
|
1879
|
+
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
return parts;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
/**
|
|
1886
|
+
* Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
|
|
1887
|
+
*
|
|
1888
|
+
* This function checks if the provided selector is valid according to the rules defined by
|
|
1889
|
+
* `basicSelectorRegExp`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
|
|
1890
|
+
* it recursively validates each nested selector using the same validation logic.
|
|
1891
|
+
*
|
|
1892
|
+
* @param {string} selector - The CSS selector string to validate.
|
|
1893
|
+
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
1894
|
+
*/
|
|
1895
|
+
function validateSelector(selector) {
|
|
1896
|
+
var match, nestedSelectors, i;
|
|
1897
|
+
|
|
1898
|
+
// Only pseudo-classes that accept selector lists should recurse
|
|
1899
|
+
var selectorListPseudoClasses = {
|
|
1900
|
+
'not': true,
|
|
1901
|
+
'is': true,
|
|
1902
|
+
'has': true,
|
|
1903
|
+
'where': true
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1906
|
+
// Reset regex lastIndex for global regex in ES5 loop
|
|
1907
|
+
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
1908
|
+
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
1909
|
+
var pseudoClass = match[1];
|
|
1910
|
+
if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
|
|
1911
|
+
nestedSelectors = parseNestedSelectors(match[2]);
|
|
1912
|
+
// Validate each nested selector
|
|
1913
|
+
for (i = 0; i < nestedSelectors.length; i++) {
|
|
1914
|
+
if (!validateSelector(nestedSelectors[i])) {
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// Allow "&" anywhere in the selector for nested selectors
|
|
1922
|
+
return basicSelectorRegExp.test(selector);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* Checks if a given CSS selector text is valid by splitting it by commas
|
|
1927
|
+
* and validating each individual selector using the `validateSelector` function.
|
|
1928
|
+
*
|
|
1929
|
+
* @param {string} selectorText - The CSS selector text to validate. Can contain multiple selectors separated by commas.
|
|
1930
|
+
* @returns {boolean} Returns true if all selectors are valid, otherwise false.
|
|
1931
|
+
*/
|
|
1932
|
+
function isValidSelectorText(selectorText) {
|
|
1933
|
+
// Split selectorText by commas and validate each part
|
|
1934
|
+
var selectors = selectorText.split(',');
|
|
1935
|
+
for (var i = 0; i < selectors.length; i++) {
|
|
1936
|
+
if (!validateSelector(selectors[i].replace(/^\s+|\s+$/g, ""))) {
|
|
1937
|
+
return false;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return true;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
var endingIndex = token.length - 1;
|
|
1944
|
+
|
|
1945
|
+
for (var character; (character = token.charAt(i)); i++) {
|
|
1946
|
+
if (i === endingIndex) {
|
|
1947
|
+
switch (state) {
|
|
1948
|
+
case "importRule":
|
|
1949
|
+
case "layerBlock":
|
|
1950
|
+
token += ";"
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1619
1954
|
switch (character) {
|
|
1620
1955
|
|
|
1621
1956
|
case " ":
|
|
@@ -1637,6 +1972,9 @@ CSSOM.parse = function parse(token) {
|
|
|
1637
1972
|
parseError('Unmatched "');
|
|
1638
1973
|
}
|
|
1639
1974
|
} while (token[index - 2] === '\\');
|
|
1975
|
+
if (index === 0) {
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1640
1978
|
buffer += token.slice(i, index);
|
|
1641
1979
|
i = index - 1;
|
|
1642
1980
|
switch (state) {
|
|
@@ -1645,6 +1983,9 @@ CSSOM.parse = function parse(token) {
|
|
|
1645
1983
|
break;
|
|
1646
1984
|
case 'importRule-begin':
|
|
1647
1985
|
state = 'importRule';
|
|
1986
|
+
if (i === endingIndex) {
|
|
1987
|
+
token += ';'
|
|
1988
|
+
}
|
|
1648
1989
|
break;
|
|
1649
1990
|
}
|
|
1650
1991
|
break;
|
|
@@ -1657,6 +1998,9 @@ CSSOM.parse = function parse(token) {
|
|
|
1657
1998
|
parseError("Unmatched '");
|
|
1658
1999
|
}
|
|
1659
2000
|
} while (token[index - 2] === '\\');
|
|
2001
|
+
if (index === 0) {
|
|
2002
|
+
break;
|
|
2003
|
+
}
|
|
1660
2004
|
buffer += token.slice(i, index);
|
|
1661
2005
|
i = index - 1;
|
|
1662
2006
|
switch (state) {
|
|
@@ -1792,8 +2136,13 @@ CSSOM.parse = function parse(token) {
|
|
|
1792
2136
|
if (currentScope === styleSheet) {
|
|
1793
2137
|
nestedSelectorRule = null;
|
|
1794
2138
|
}
|
|
2139
|
+
if (state === 'before-selector') {
|
|
2140
|
+
parseError("Unexpected {");
|
|
2141
|
+
i = ignoreBalancedBlock(i, token.slice(i));
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
1795
2144
|
if (state === "selector" || state === "atRule") {
|
|
1796
|
-
if (!nestedSelectorRule && buffer.
|
|
2145
|
+
if (!nestedSelectorRule && buffer.indexOf(";") !== -1) {
|
|
1797
2146
|
var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
|
|
1798
2147
|
if (ruleClosingMatch) {
|
|
1799
2148
|
styleRule = null;
|
|
@@ -1810,6 +2159,7 @@ CSSOM.parse = function parse(token) {
|
|
|
1810
2159
|
}
|
|
1811
2160
|
|
|
1812
2161
|
currentScope = parentRule = styleRule;
|
|
2162
|
+
console.log('sel out', buffer);
|
|
1813
2163
|
styleRule.selectorText = buffer.trim();
|
|
1814
2164
|
styleRule.style.__starts = i;
|
|
1815
2165
|
styleRule.parentStyleSheet = styleSheet;
|
|
@@ -1851,15 +2201,19 @@ CSSOM.parse = function parse(token) {
|
|
|
1851
2201
|
buffer = "";
|
|
1852
2202
|
state = "before-selector";
|
|
1853
2203
|
} else if (state === "layerBlock") {
|
|
1854
|
-
layerBlockRule.
|
|
2204
|
+
layerBlockRule.name = buffer.trim();
|
|
1855
2205
|
|
|
1856
|
-
|
|
1857
|
-
layerBlockRule.parentRule = parentRule;
|
|
1858
|
-
ancestorRules.push(parentRule);
|
|
1859
|
-
}
|
|
2206
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
|
|
1860
2207
|
|
|
1861
|
-
|
|
1862
|
-
|
|
2208
|
+
if (isValidName) {
|
|
2209
|
+
if (parentRule) {
|
|
2210
|
+
layerBlockRule.parentRule = parentRule;
|
|
2211
|
+
ancestorRules.push(parentRule);
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
currentScope = parentRule = layerBlockRule;
|
|
2215
|
+
layerBlockRule.parentStyleSheet = styleSheet;
|
|
2216
|
+
}
|
|
1863
2217
|
buffer = "";
|
|
1864
2218
|
state = "before-selector";
|
|
1865
2219
|
} else if (state === "hostRule-begin") {
|
|
@@ -1917,7 +2271,7 @@ CSSOM.parse = function parse(token) {
|
|
|
1917
2271
|
documentRule.parentStyleSheet = styleSheet;
|
|
1918
2272
|
buffer = "";
|
|
1919
2273
|
state = "before-selector";
|
|
1920
|
-
} else if (state === "name") {
|
|
2274
|
+
} else if (state === "before-name" || state === "name") {
|
|
1921
2275
|
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
1922
2276
|
if (styleRule.style.length) {
|
|
1923
2277
|
parentRule.cssRules.push(styleRule);
|
|
@@ -1934,9 +2288,12 @@ CSSOM.parse = function parse(token) {
|
|
|
1934
2288
|
styleRule.parentStyleSheet = styleSheet;
|
|
1935
2289
|
}
|
|
1936
2290
|
|
|
1937
|
-
|
|
1938
2291
|
styleRule = new CSSOM.CSSStyleRule();
|
|
1939
|
-
|
|
2292
|
+
console.log('sel in', buffer);
|
|
2293
|
+
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
2294
|
+
styleRule.selectorText = parseNestedSelectors(buffer.trim()).map(function(sel) {
|
|
2295
|
+
return sel.indexOf('&') === -1 ? '& ' + sel : sel;
|
|
2296
|
+
}).join(', ');
|
|
1940
2297
|
styleRule.style.__starts = i - buffer.length;
|
|
1941
2298
|
styleRule.parentRule = parentRule;
|
|
1942
2299
|
nestedSelectorRule = styleRule;
|
|
@@ -2011,8 +2368,14 @@ CSSOM.parse = function parse(token) {
|
|
|
2011
2368
|
|
|
2012
2369
|
case ";":
|
|
2013
2370
|
switch (state) {
|
|
2371
|
+
case "before-value":
|
|
2372
|
+
case "before-name":
|
|
2373
|
+
parseError("Unexpected ;");
|
|
2374
|
+
buffer = "";
|
|
2375
|
+
state = "before-name";
|
|
2376
|
+
break;
|
|
2014
2377
|
case "value":
|
|
2015
|
-
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
2378
|
+
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
2016
2379
|
priority = "";
|
|
2017
2380
|
buffer = "";
|
|
2018
2381
|
state = "before-name";
|
|
@@ -2022,10 +2385,34 @@ CSSOM.parse = function parse(token) {
|
|
|
2022
2385
|
state = "before-selector";
|
|
2023
2386
|
break;
|
|
2024
2387
|
case "importRule":
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2388
|
+
var isValid = styleSheet.cssRules.length === 0 || styleSheet.cssRules.some(function (rule) {
|
|
2389
|
+
return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
|
|
2390
|
+
});
|
|
2391
|
+
if (isValid) {
|
|
2392
|
+
importRule = new CSSOM.CSSImportRule();
|
|
2393
|
+
importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
|
|
2394
|
+
importRule.cssText = buffer + character;
|
|
2395
|
+
styleSheet.cssRules.push(importRule);
|
|
2396
|
+
}
|
|
2397
|
+
buffer = "";
|
|
2398
|
+
state = "before-selector";
|
|
2399
|
+
break;
|
|
2400
|
+
case "layerBlock":
|
|
2401
|
+
var nameListStr = buffer.trim().split(",").map(function (name) {
|
|
2402
|
+
return name.trim();
|
|
2403
|
+
});
|
|
2404
|
+
var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
|
|
2405
|
+
return name.trim().match(layerRuleNameRegExp) === null;
|
|
2406
|
+
});
|
|
2407
|
+
|
|
2408
|
+
if (!isInvalid) {
|
|
2409
|
+
layerStatementRule = new CSSOM.CSSLayerStatementRule();
|
|
2410
|
+
layerStatementRule.parentStyleSheet = styleSheet;
|
|
2411
|
+
layerStatementRule.__starts = layerBlockRule.__starts;
|
|
2412
|
+
layerStatementRule.__ends = i;
|
|
2413
|
+
layerStatementRule.nameList = nameListStr;
|
|
2414
|
+
styleSheet.cssRules.push(layerStatementRule);
|
|
2415
|
+
}
|
|
2029
2416
|
buffer = "";
|
|
2030
2417
|
state = "before-selector";
|
|
2031
2418
|
break;
|
|
@@ -2038,9 +2425,10 @@ CSSOM.parse = function parse(token) {
|
|
|
2038
2425
|
case "}":
|
|
2039
2426
|
switch (state) {
|
|
2040
2427
|
case "value":
|
|
2041
|
-
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
2428
|
+
styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
|
|
2042
2429
|
priority = "";
|
|
2043
2430
|
/* falls through */
|
|
2431
|
+
case "before-value":
|
|
2044
2432
|
case "before-name":
|
|
2045
2433
|
case "name":
|
|
2046
2434
|
styleRule.__ends = i + 1;
|
|
@@ -2058,7 +2446,14 @@ CSSOM.parse = function parse(token) {
|
|
|
2058
2446
|
currentScope = parentRule || styleSheet;
|
|
2059
2447
|
}
|
|
2060
2448
|
|
|
2061
|
-
|
|
2449
|
+
if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
|
|
2450
|
+
if (styleRule === nestedSelectorRule) {
|
|
2451
|
+
nestedSelectorRule = null;
|
|
2452
|
+
}
|
|
2453
|
+
parseError('Invalid CSSStyleRule.selectorText');
|
|
2454
|
+
} else {
|
|
2455
|
+
currentScope.cssRules.push(styleRule);
|
|
2456
|
+
}
|
|
2062
2457
|
buffer = "";
|
|
2063
2458
|
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
2064
2459
|
state = "keyframeRule-begin";
|
|
@@ -2080,12 +2475,16 @@ CSSOM.parse = function parse(token) {
|
|
|
2080
2475
|
case "selector":
|
|
2081
2476
|
// End of media/supports/document rule.
|
|
2082
2477
|
if (!parentRule) {
|
|
2478
|
+
parseError("Unexpected }");
|
|
2479
|
+
|
|
2480
|
+
var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
|
|
2481
|
+
if (hasPreviousStyleRule) {
|
|
2482
|
+
i = ignoreBalancedBlock(i, token.slice(i), 1);
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2083
2485
|
break;
|
|
2084
|
-
//parseError("Unexpected }");
|
|
2085
2486
|
}
|
|
2086
2487
|
|
|
2087
|
-
// Handle rules nested in @media or @supports
|
|
2088
|
-
hasAncestors = ancestorRules.length > 0;
|
|
2089
2488
|
|
|
2090
2489
|
while (ancestorRules.length > 0) {
|
|
2091
2490
|
parentRule = ancestorRules.pop();
|
|
@@ -2112,14 +2511,10 @@ CSSOM.parse = function parse(token) {
|
|
|
2112
2511
|
} else {
|
|
2113
2512
|
prevScope = currentScope;
|
|
2114
2513
|
currentScope = parentRule;
|
|
2115
|
-
currentScope.cssRules.push(prevScope);
|
|
2514
|
+
currentScope !== prevScope && currentScope.cssRules.push(prevScope);
|
|
2116
2515
|
break;
|
|
2117
2516
|
}
|
|
2118
2517
|
}
|
|
2119
|
-
|
|
2120
|
-
if (ancestorRules.length === 0) {
|
|
2121
|
-
hasAncestors = false;
|
|
2122
|
-
}
|
|
2123
2518
|
}
|
|
2124
2519
|
|
|
2125
2520
|
if (currentScope.parentRule == null) {
|
|
@@ -2133,8 +2528,12 @@ CSSOM.parse = function parse(token) {
|
|
|
2133
2528
|
if (nestedSelectorRule === parentRule) {
|
|
2134
2529
|
// Check if this selector is really starting inside another selector
|
|
2135
2530
|
var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
|
|
2136
|
-
|
|
2137
|
-
|
|
2531
|
+
var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/{/g);
|
|
2532
|
+
var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(/}/g);
|
|
2533
|
+
var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
|
|
2534
|
+
var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
|
|
2535
|
+
|
|
2536
|
+
if (openingBraceLen === closingBraceLen) {
|
|
2138
2537
|
// If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
|
|
2139
2538
|
nestedSelectorRule.__ends = i + 1;
|
|
2140
2539
|
nestedSelectorRule = null;
|
|
@@ -2263,6 +2662,10 @@ CSSOM.clone = function clone(stylesheet) {
|
|
|
2263
2662
|
ruleClone.mediaText = rule.mediaText;
|
|
2264
2663
|
}
|
|
2265
2664
|
|
|
2665
|
+
if (rule.hasOwnProperty('supportsText')) {
|
|
2666
|
+
ruleClone.supports = rule.supports;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2266
2669
|
if (rule.hasOwnProperty('conditionText')) {
|
|
2267
2670
|
ruleClone.conditionText = rule.conditionText;
|
|
2268
2671
|
}
|
|
@@ -2271,6 +2674,18 @@ CSSOM.clone = function clone(stylesheet) {
|
|
|
2271
2674
|
ruleClone.layerName = rule.layerName;
|
|
2272
2675
|
}
|
|
2273
2676
|
|
|
2677
|
+
if (rule.hasOwnProperty('href')) {
|
|
2678
|
+
ruleClone.href = rule.href;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
if (rule.hasOwnProperty('name')) {
|
|
2682
|
+
ruleClone.name = rule.name;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
if (rule.hasOwnProperty('nameList')) {
|
|
2686
|
+
ruleClone.nameList = rule.nameList;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2274
2689
|
if (rule.hasOwnProperty('cssRules')) {
|
|
2275
2690
|
ruleClone.cssRules = clone(rule).cssRules;
|
|
2276
2691
|
}
|