@acemir/cssom 0.9.22 → 0.9.24
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 +716 -111
- package/lib/CSSContainerRule.js +11 -4
- package/lib/CSSDocumentRule.js +1 -0
- package/lib/CSSHostRule.js +13 -4
- package/lib/CSSKeyframesRule.js +12 -5
- package/lib/CSSLayerBlockRule.js +11 -4
- package/lib/CSSMediaRule.js +11 -4
- package/lib/CSSPageRule.js +8 -3
- package/lib/CSSScopeRule.js +11 -4
- package/lib/CSSStartingStyleRule.js +11 -4
- package/lib/CSSStyleRule.js +8 -3
- package/lib/CSSStyleSheet.js +79 -0
- package/lib/CSSSupportsRule.js +11 -6
- package/lib/MediaList.js +3 -1
- package/lib/errorUtils.js +15 -3
- package/lib/parse.js +511 -66
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -34,6 +34,19 @@ function getErrorConstructor(context, errorType) {
|
|
|
34
34
|
eval(errorType);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Creates an appropriate error with context-aware constructor.
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
|
|
41
|
+
* @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
|
|
42
|
+
* @param {string} message - The error message
|
|
43
|
+
* @param {string} [name] - Optional name for DOMException
|
|
44
|
+
*/
|
|
45
|
+
function createError(context, errorType, message, name) {
|
|
46
|
+
var ErrorConstructor = getErrorConstructor(context, errorType);
|
|
47
|
+
return new ErrorConstructor(message, name);
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* Creates and throws an appropriate error with context-aware constructor.
|
|
39
52
|
*
|
|
@@ -43,9 +56,7 @@ function getErrorConstructor(context, errorType) {
|
|
|
43
56
|
* @param {string} [name] - Optional name for DOMException
|
|
44
57
|
*/
|
|
45
58
|
function throwError(context, errorType, message, name) {
|
|
46
|
-
|
|
47
|
-
var error = new ErrorConstructor(message, name);
|
|
48
|
-
throw error;
|
|
59
|
+
throw createError(context, errorType, message, name);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
/**
|
|
@@ -98,6 +109,7 @@ function throwIndexError(context, methodName, objectName, index, maxIndex, name)
|
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
var errorUtils = {
|
|
112
|
+
createError: createError,
|
|
101
113
|
getErrorConstructor: getErrorConstructor,
|
|
102
114
|
throwError: throwError,
|
|
103
115
|
throwMissingArguments: throwMissingArguments,
|
|
@@ -628,12 +640,17 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
|
|
|
628
640
|
get: function() {
|
|
629
641
|
var text;
|
|
630
642
|
if (this.selectorText) {
|
|
631
|
-
var values = ""
|
|
643
|
+
var values = "";
|
|
632
644
|
if (this.cssRules.length) {
|
|
633
645
|
var valuesArr = [" {"];
|
|
634
646
|
this.style.cssText && valuesArr.push(this.style.cssText);
|
|
635
|
-
valuesArr.push(this.cssRules.
|
|
636
|
-
|
|
647
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
648
|
+
if (rule.cssText !== "") {
|
|
649
|
+
acc.push(rule.cssText);
|
|
650
|
+
}
|
|
651
|
+
return acc;
|
|
652
|
+
}, []).join("\n "));
|
|
653
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
637
654
|
} else {
|
|
638
655
|
values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
|
|
639
656
|
}
|
|
@@ -825,7 +842,9 @@ CSSOM.MediaList.prototype = {
|
|
|
825
842
|
* @param {string} value
|
|
826
843
|
*/
|
|
827
844
|
set mediaText(value) {
|
|
828
|
-
var values = value.split(",")
|
|
845
|
+
var values = value.split(",").filter(function(text){
|
|
846
|
+
return !!text;
|
|
847
|
+
});
|
|
829
848
|
var length = this.length = values.length;
|
|
830
849
|
for (var i=0; i<length; i++) {
|
|
831
850
|
this[i] = values[i].trim();
|
|
@@ -895,11 +914,18 @@ Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
|
|
|
895
914
|
},
|
|
896
915
|
"cssText": {
|
|
897
916
|
get: function() {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
917
|
+
var values = "";
|
|
918
|
+
var valuesArr = [" {"];
|
|
919
|
+
if (this.cssRules.length) {
|
|
920
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
921
|
+
if (rule.cssText !== "") {
|
|
922
|
+
acc.push(rule.cssText);
|
|
923
|
+
}
|
|
924
|
+
return acc;
|
|
925
|
+
}, []).join("\n "));
|
|
901
926
|
}
|
|
902
|
-
|
|
927
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
928
|
+
return "@media " + this.media.mediaText + values;
|
|
903
929
|
},
|
|
904
930
|
configurable: true,
|
|
905
931
|
enumerable: true
|
|
@@ -927,11 +953,18 @@ CSSOM.CSSContainerRule.prototype.type = 17;
|
|
|
927
953
|
Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
|
|
928
954
|
"cssText": {
|
|
929
955
|
get: function() {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
956
|
+
var values = "";
|
|
957
|
+
var valuesArr = [" {"];
|
|
958
|
+
if (this.cssRules.length) {
|
|
959
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
960
|
+
if (rule.cssText !== "") {
|
|
961
|
+
acc.push(rule.cssText);
|
|
962
|
+
}
|
|
963
|
+
return acc;
|
|
964
|
+
}, []).join("\n "));
|
|
933
965
|
}
|
|
934
|
-
|
|
966
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
967
|
+
return "@container " + this.conditionText + values;
|
|
935
968
|
},
|
|
936
969
|
configurable: true,
|
|
937
970
|
enumerable: true
|
|
@@ -975,13 +1008,18 @@ CSSOM.CSSSupportsRule.prototype.type = 12;
|
|
|
975
1008
|
|
|
976
1009
|
Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
|
|
977
1010
|
get: function() {
|
|
978
|
-
var
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1011
|
+
var values = "";
|
|
1012
|
+
var valuesArr = [" {"];
|
|
1013
|
+
if (this.cssRules.length) {
|
|
1014
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
1015
|
+
if (rule.cssText !== "") {
|
|
1016
|
+
acc.push(rule.cssText);
|
|
1017
|
+
}
|
|
1018
|
+
return acc;
|
|
1019
|
+
}, []).join("\n "));
|
|
982
1020
|
}
|
|
983
|
-
|
|
984
|
-
return "@supports " + this.conditionText +
|
|
1021
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
1022
|
+
return "@supports " + this.conditionText + values;
|
|
985
1023
|
}
|
|
986
1024
|
});
|
|
987
1025
|
|
|
@@ -1368,6 +1406,8 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
|
|
|
1368
1406
|
/**
|
|
1369
1407
|
* @constructor
|
|
1370
1408
|
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
|
|
1409
|
+
* @see http://html5index.org/Shadow%20DOM%20-%20CSSHostRule.html
|
|
1410
|
+
* @deprecated This rule was part of early Shadow DOM drafts but was removed in favor of the more flexible :host and :host-context() pseudo-classes in modern CSS for Web Components.
|
|
1371
1411
|
*/
|
|
1372
1412
|
CSSOM.CSSHostRule = function CSSHostRule() {
|
|
1373
1413
|
CSSOM.CSSRule.call(this);
|
|
@@ -1383,11 +1423,18 @@ CSSOM.CSSHostRule.prototype.type = 1001;
|
|
|
1383
1423
|
|
|
1384
1424
|
Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", {
|
|
1385
1425
|
get: function() {
|
|
1386
|
-
var
|
|
1387
|
-
|
|
1388
|
-
|
|
1426
|
+
var values = "";
|
|
1427
|
+
var valuesArr = [" {"];
|
|
1428
|
+
if (this.cssRules.length) {
|
|
1429
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
1430
|
+
if (rule.cssText !== "") {
|
|
1431
|
+
acc.push(rule.cssText);
|
|
1432
|
+
}
|
|
1433
|
+
return acc;
|
|
1434
|
+
}, []).join("\n "));
|
|
1389
1435
|
}
|
|
1390
|
-
|
|
1436
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
1437
|
+
return "@host" + values;
|
|
1391
1438
|
}
|
|
1392
1439
|
});
|
|
1393
1440
|
|
|
@@ -1413,11 +1460,18 @@ CSSOM.CSSStartingStyleRule.prototype.type = 1002;
|
|
|
1413
1460
|
|
|
1414
1461
|
Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
|
|
1415
1462
|
get: function() {
|
|
1416
|
-
var
|
|
1417
|
-
|
|
1418
|
-
|
|
1463
|
+
var values = "";
|
|
1464
|
+
var valuesArr = [" {"];
|
|
1465
|
+
if (this.cssRules.length) {
|
|
1466
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
1467
|
+
if (rule.cssText !== "") {
|
|
1468
|
+
acc.push(rule.cssText);
|
|
1469
|
+
}
|
|
1470
|
+
return acc;
|
|
1471
|
+
}, []).join("\n "));
|
|
1419
1472
|
}
|
|
1420
|
-
|
|
1473
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
1474
|
+
return "@starting-style" + values;
|
|
1421
1475
|
}
|
|
1422
1476
|
});
|
|
1423
1477
|
|
|
@@ -1465,6 +1519,7 @@ Object.defineProperties(CSSOM.StyleSheet.prototype, {
|
|
|
1465
1519
|
*/
|
|
1466
1520
|
CSSOM.CSSStyleSheet = function CSSStyleSheet() {
|
|
1467
1521
|
CSSOM.StyleSheet.call(this);
|
|
1522
|
+
this.__constructed = true;
|
|
1468
1523
|
this.cssRules = new CSSOM.CSSRuleList();
|
|
1469
1524
|
};
|
|
1470
1525
|
|
|
@@ -1677,6 +1732,84 @@ CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
|
|
|
1677
1732
|
this.deleteRule(index);
|
|
1678
1733
|
};
|
|
1679
1734
|
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* Replaces the rules of a {@link CSSStyleSheet}
|
|
1738
|
+
*
|
|
1739
|
+
* @returns a promise
|
|
1740
|
+
* @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replace
|
|
1741
|
+
*/
|
|
1742
|
+
CSSOM.CSSStyleSheet.prototype.replace = function(text) {
|
|
1743
|
+
var _Promise;
|
|
1744
|
+
if (this.__globalObject) {
|
|
1745
|
+
_Promise = this.__globalObject['Promise'];
|
|
1746
|
+
} else {
|
|
1747
|
+
_Promise = Promise;
|
|
1748
|
+
}
|
|
1749
|
+
var sheet = this;
|
|
1750
|
+
return new _Promise(function (resolve, reject) {
|
|
1751
|
+
// If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
|
|
1752
|
+
if (!sheet.__constructed || sheet.__disallowModification) {
|
|
1753
|
+
reject(errorUtils.createError(sheet, 'DOMException',
|
|
1754
|
+
"Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
|
|
1755
|
+
'NotAllowedError'));
|
|
1756
|
+
}
|
|
1757
|
+
// Set the disallow modification flag.
|
|
1758
|
+
sheet.__disallowModification = true;
|
|
1759
|
+
|
|
1760
|
+
// In parallel, do these steps:
|
|
1761
|
+
setTimeout(function() {
|
|
1762
|
+
// Let rules be the result of running parse a stylesheet's contents from text.
|
|
1763
|
+
var rules = new CSSOM.CSSRuleList();
|
|
1764
|
+
CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
|
|
1765
|
+
// If rules contains one or more @import rules, remove those rules from rules.
|
|
1766
|
+
var i = 0;
|
|
1767
|
+
while (i < rules.length) {
|
|
1768
|
+
if (rules[i].constructor.name === 'CSSImportRule') {
|
|
1769
|
+
rules.splice(i, 1);
|
|
1770
|
+
} else {
|
|
1771
|
+
i++;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
// Set sheet's CSS rules to rules.
|
|
1775
|
+
sheet.cssRules = rules;
|
|
1776
|
+
// Unset sheet’s disallow modification flag.
|
|
1777
|
+
delete sheet.__disallowModification;
|
|
1778
|
+
// Resolve promise with sheet.
|
|
1779
|
+
resolve(sheet);
|
|
1780
|
+
})
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* Synchronously replaces the rules of a {@link CSSStyleSheet}
|
|
1786
|
+
*
|
|
1787
|
+
* @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replacesync
|
|
1788
|
+
*/
|
|
1789
|
+
CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
|
|
1790
|
+
var sheet = this;
|
|
1791
|
+
// If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
|
|
1792
|
+
if (!sheet.__constructed || sheet.__disallowModification) {
|
|
1793
|
+
errorUtils.throwError(sheet, 'DOMException',
|
|
1794
|
+
"Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
|
|
1795
|
+
'NotAllowedError');
|
|
1796
|
+
}
|
|
1797
|
+
// Let rules be the result of running parse a stylesheet's contents from text.
|
|
1798
|
+
var rules = new CSSOM.CSSRuleList();
|
|
1799
|
+
CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
|
|
1800
|
+
// If rules contains one or more @import rules, remove those rules from rules.
|
|
1801
|
+
var i = 0;
|
|
1802
|
+
while (i < rules.length) {
|
|
1803
|
+
if (rules[i].constructor.name === 'CSSImportRule') {
|
|
1804
|
+
rules.splice(i, 1);
|
|
1805
|
+
} else {
|
|
1806
|
+
i++;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
// Set sheet's CSS rules to rules.
|
|
1810
|
+
sheet.cssRules = rules;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1680
1813
|
/**
|
|
1681
1814
|
* NON-STANDARD
|
|
1682
1815
|
* @return {string} serialize stylesheet
|
|
@@ -1743,13 +1876,20 @@ CSSOM.CSSKeyframesRule.prototype.type = 7;
|
|
|
1743
1876
|
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
|
|
1744
1877
|
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
|
|
1745
1878
|
get: function() {
|
|
1746
|
-
var
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1879
|
+
var values = "";
|
|
1880
|
+
var valuesArr = [" {"];
|
|
1881
|
+
if (this.cssRules.length) {
|
|
1882
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
1883
|
+
if (rule.cssText !== "") {
|
|
1884
|
+
acc.push(rule.cssText);
|
|
1885
|
+
}
|
|
1886
|
+
return acc;
|
|
1887
|
+
}, []).join("\n "));
|
|
1888
|
+
}
|
|
1889
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
1750
1890
|
var cssWideKeywords = ['initial', 'inherit', 'revert', 'revert-layer', 'unset', 'none'];
|
|
1751
1891
|
var processedName = cssWideKeywords.includes(this.name) ? '"' + this.name + '"' : this.name;
|
|
1752
|
-
return "@" + (this._vendorPrefix || '') + "keyframes " + processedName +
|
|
1892
|
+
return "@" + (this._vendorPrefix || '') + "keyframes " + processedName + values;
|
|
1753
1893
|
}
|
|
1754
1894
|
});
|
|
1755
1895
|
|
|
@@ -2024,6 +2164,7 @@ CSSOM.MatcherList.prototype = {
|
|
|
2024
2164
|
/**
|
|
2025
2165
|
* @constructor
|
|
2026
2166
|
* @see https://developer.mozilla.org/en/CSS/@-moz-document
|
|
2167
|
+
* @deprecated This rule is a non-standard Mozilla-specific extension and is not part of any official CSS specification.
|
|
2027
2168
|
*/
|
|
2028
2169
|
CSSOM.CSSDocumentRule = function CSSDocumentRule() {
|
|
2029
2170
|
CSSOM.CSSRule.call(this);
|
|
@@ -2450,11 +2591,18 @@ Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
|
|
|
2450
2591
|
},
|
|
2451
2592
|
cssText: {
|
|
2452
2593
|
get: function () {
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2594
|
+
var values = "";
|
|
2595
|
+
var valuesArr = [" {"];
|
|
2596
|
+
if (this.cssRules.length) {
|
|
2597
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
2598
|
+
if (rule.cssText !== "") {
|
|
2599
|
+
acc.push(rule.cssText);
|
|
2600
|
+
}
|
|
2601
|
+
return acc;
|
|
2602
|
+
}, []).join("\n "));
|
|
2456
2603
|
}
|
|
2457
|
-
|
|
2604
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
2605
|
+
return "@scope" + (this.start ? " (" + this.start + ")" : "") + (this.end ? " to (" + this.end + ")" : "") + values;
|
|
2458
2606
|
},
|
|
2459
2607
|
configurable: true,
|
|
2460
2608
|
enumerable: true,
|
|
@@ -2490,11 +2638,18 @@ CSSOM.CSSLayerBlockRule.prototype.type = 18;
|
|
|
2490
2638
|
Object.defineProperties(CSSOM.CSSLayerBlockRule.prototype, {
|
|
2491
2639
|
cssText: {
|
|
2492
2640
|
get: function () {
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2641
|
+
var values = "";
|
|
2642
|
+
var valuesArr = [" {"];
|
|
2643
|
+
if (this.cssRules.length) {
|
|
2644
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
2645
|
+
if (rule.cssText !== "") {
|
|
2646
|
+
acc.push(rule.cssText);
|
|
2647
|
+
}
|
|
2648
|
+
return acc;
|
|
2649
|
+
}, []).join("\n "));
|
|
2496
2650
|
}
|
|
2497
|
-
|
|
2651
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
2652
|
+
return "@layer" + (this.name ? " " + this.name : "") + values;
|
|
2498
2653
|
},
|
|
2499
2654
|
configurable: true,
|
|
2500
2655
|
enumerable: true,
|
|
@@ -2623,12 +2778,17 @@ Object.defineProperty(CSSOM.CSSPageRule.prototype, "style", {
|
|
|
2623
2778
|
|
|
2624
2779
|
Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
|
|
2625
2780
|
get: function() {
|
|
2626
|
-
var values = ""
|
|
2781
|
+
var values = "";
|
|
2627
2782
|
if (this.cssRules.length) {
|
|
2628
2783
|
var valuesArr = [" {"];
|
|
2629
2784
|
this.style.cssText && valuesArr.push(this.style.cssText);
|
|
2630
|
-
valuesArr.push(this.cssRules.
|
|
2631
|
-
|
|
2785
|
+
valuesArr.push(this.cssRules.reduce(function(acc, rule){
|
|
2786
|
+
if (rule.cssText !== "") {
|
|
2787
|
+
acc.push(rule.cssText);
|
|
2788
|
+
}
|
|
2789
|
+
return acc;
|
|
2790
|
+
}, []).join("\n "));
|
|
2791
|
+
values = valuesArr.join("\n ") + "\n}";
|
|
2632
2792
|
} else {
|
|
2633
2793
|
values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
|
|
2634
2794
|
}
|
|
@@ -2797,6 +2957,8 @@ CSSOM.CSSPageRule.parse = function(ruleText) {
|
|
|
2797
2957
|
* @param {string} token - The CSS string to parse.
|
|
2798
2958
|
* @param {object} [opts] - Optional parsing options.
|
|
2799
2959
|
* @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
|
|
2960
|
+
* @param {CSSOM.CSSStyleSheet} [opts.styleSheet] - Reuse a style sheet instead of creating a new one (e.g. as `parentStyleSheet`)
|
|
2961
|
+
* @param {CSSOM.CSSRuleList} [opts.cssRules] - Prepare all rules in this list instead of mutating the style sheet continually
|
|
2800
2962
|
* @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
|
|
2801
2963
|
* @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
|
|
2802
2964
|
*/
|
|
@@ -2843,14 +3005,26 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2843
3005
|
"pageBlock": true
|
|
2844
3006
|
};
|
|
2845
3007
|
|
|
2846
|
-
var styleSheet
|
|
3008
|
+
var styleSheet;
|
|
3009
|
+
if (opts && opts.styleSheet) {
|
|
3010
|
+
styleSheet = opts.styleSheet;
|
|
3011
|
+
} else {
|
|
3012
|
+
styleSheet = new CSSOM.CSSStyleSheet()
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
var topScope;
|
|
3016
|
+
if (opts && opts.cssRules) {
|
|
3017
|
+
topScope = { cssRules: opts.cssRules };
|
|
3018
|
+
} else {
|
|
3019
|
+
topScope = styleSheet;
|
|
3020
|
+
}
|
|
2847
3021
|
|
|
2848
3022
|
if (opts && opts.globalObject) {
|
|
2849
3023
|
styleSheet.__globalObject = opts.globalObject;
|
|
2850
3024
|
}
|
|
2851
3025
|
|
|
2852
3026
|
// @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
|
2853
|
-
var currentScope =
|
|
3027
|
+
var currentScope = topScope;
|
|
2854
3028
|
|
|
2855
3029
|
// @type CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
|
|
2856
3030
|
var parentRule;
|
|
@@ -3179,7 +3353,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3179
3353
|
var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
|
|
3180
3354
|
var ruleSlice = token.slice(i);
|
|
3181
3355
|
// Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
|
|
3182
|
-
var shouldPerformCheck = cannotBeNested && currentScope !==
|
|
3356
|
+
var shouldPerformCheck = cannotBeNested && currentScope !== topScope ? false : true;
|
|
3183
3357
|
// First, check if there is no invalid characters just after the at-rule
|
|
3184
3358
|
if (shouldPerformCheck && ruleSlice.search(ruleRegExp) === 0) {
|
|
3185
3359
|
// Find the closest allowed character before the at-rule (a opening or closing brace, a semicolon or a comment ending)
|
|
@@ -3311,7 +3485,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3311
3485
|
var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
|
|
3312
3486
|
|
|
3313
3487
|
// If it's a statement inside a nested rule, ignore only the statement
|
|
3314
|
-
if (ruleStatementMatch && currentScope !==
|
|
3488
|
+
if (ruleStatementMatch && currentScope !== topScope) {
|
|
3315
3489
|
var ignoreEnd = ruleStatementMatch[0].indexOf(";");
|
|
3316
3490
|
i += ruleStatementMatch.index + ignoreEnd;
|
|
3317
3491
|
return;
|
|
@@ -3336,6 +3510,255 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3336
3510
|
}
|
|
3337
3511
|
}
|
|
3338
3512
|
|
|
3513
|
+
// Helper functions for looseSelectorValidator
|
|
3514
|
+
// Defined outside to avoid recreation on every validation call
|
|
3515
|
+
|
|
3516
|
+
/**
|
|
3517
|
+
* Check if character is a valid identifier start
|
|
3518
|
+
* @param {string} c - Character to check
|
|
3519
|
+
* @returns {boolean}
|
|
3520
|
+
*/
|
|
3521
|
+
function isIdentStart(c) {
|
|
3522
|
+
return /[a-zA-Z_\u00A0-\uFFFF]/.test(c);
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
/**
|
|
3526
|
+
* Check if character is a valid identifier character
|
|
3527
|
+
* @param {string} c - Character to check
|
|
3528
|
+
* @returns {boolean}
|
|
3529
|
+
*/
|
|
3530
|
+
function isIdentChar(c) {
|
|
3531
|
+
return /[a-zA-Z0-9_\u00A0-\uFFFF\-]/.test(c);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
/**
|
|
3535
|
+
* Helper function to validate CSS selector syntax without regex backtracking.
|
|
3536
|
+
* Iteratively parses the selector string to identify valid components.
|
|
3537
|
+
*
|
|
3538
|
+
* Supports:
|
|
3539
|
+
* - Escaped special characters (e.g., .class\!, #id\@name)
|
|
3540
|
+
* - Namespace selectors (ns|element, *|element, |element)
|
|
3541
|
+
* - All standard CSS selectors (class, ID, type, attribute, pseudo, etc.)
|
|
3542
|
+
* - Combinators (>, +, ~, whitespace)
|
|
3543
|
+
* - Nesting selector (&)
|
|
3544
|
+
*
|
|
3545
|
+
* This approach eliminates exponential backtracking by using explicit character-by-character
|
|
3546
|
+
* parsing instead of nested quantifiers in regex.
|
|
3547
|
+
*
|
|
3548
|
+
* @param {string} selector - The selector to validate
|
|
3549
|
+
* @returns {boolean} - True if valid selector syntax
|
|
3550
|
+
*/
|
|
3551
|
+
function looseSelectorValidator(selector) {
|
|
3552
|
+
if (!selector || selector.length === 0) {
|
|
3553
|
+
return false;
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
var i = 0;
|
|
3557
|
+
var len = selector.length;
|
|
3558
|
+
var hasMatchedComponent = false;
|
|
3559
|
+
|
|
3560
|
+
// Helper: Skip escaped character (backslash + any char)
|
|
3561
|
+
function skipEscape() {
|
|
3562
|
+
if (i < len && selector[i] === '\\') {
|
|
3563
|
+
i += 2; // Skip backslash and next character
|
|
3564
|
+
return true;
|
|
3565
|
+
}
|
|
3566
|
+
return false;
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// Helper: Parse identifier (with possible escapes)
|
|
3570
|
+
function parseIdentifier() {
|
|
3571
|
+
var start = i;
|
|
3572
|
+
while (i < len) {
|
|
3573
|
+
if (skipEscape()) {
|
|
3574
|
+
continue;
|
|
3575
|
+
} else if (isIdentChar(selector[i])) {
|
|
3576
|
+
i++;
|
|
3577
|
+
} else {
|
|
3578
|
+
break;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
return i > start;
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
// Helper: Parse namespace prefix (optional)
|
|
3585
|
+
function parseNamespace() {
|
|
3586
|
+
var start = i;
|
|
3587
|
+
|
|
3588
|
+
// Match: *| or identifier| or |
|
|
3589
|
+
if (i < len && selector[i] === '*') {
|
|
3590
|
+
i++;
|
|
3591
|
+
} else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
|
|
3592
|
+
parseIdentifier();
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
if (i < len && selector[i] === '|') {
|
|
3596
|
+
i++;
|
|
3597
|
+
return true;
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// Rollback if no pipe found
|
|
3601
|
+
i = start;
|
|
3602
|
+
return false;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
// Helper: Parse pseudo-class/element arguments (with balanced parens)
|
|
3606
|
+
function parsePseudoArgs() {
|
|
3607
|
+
if (i >= len || selector[i] !== '(') {
|
|
3608
|
+
return false;
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
i++; // Skip opening paren
|
|
3612
|
+
var depth = 1;
|
|
3613
|
+
var inString = false;
|
|
3614
|
+
var stringChar = '';
|
|
3615
|
+
|
|
3616
|
+
while (i < len && depth > 0) {
|
|
3617
|
+
var c = selector[i];
|
|
3618
|
+
|
|
3619
|
+
if (c === '\\' && i + 1 < len) {
|
|
3620
|
+
i += 2; // Skip escaped character
|
|
3621
|
+
} else if (!inString && (c === '"' || c === '\'')) {
|
|
3622
|
+
inString = true;
|
|
3623
|
+
stringChar = c;
|
|
3624
|
+
i++;
|
|
3625
|
+
} else if (inString && c === stringChar) {
|
|
3626
|
+
inString = false;
|
|
3627
|
+
i++;
|
|
3628
|
+
} else if (!inString && c === '(') {
|
|
3629
|
+
depth++;
|
|
3630
|
+
i++;
|
|
3631
|
+
} else if (!inString && c === ')') {
|
|
3632
|
+
depth--;
|
|
3633
|
+
i++;
|
|
3634
|
+
} else {
|
|
3635
|
+
i++;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
return depth === 0;
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
// Main parsing loop
|
|
3643
|
+
while (i < len) {
|
|
3644
|
+
var matched = false;
|
|
3645
|
+
var start = i;
|
|
3646
|
+
|
|
3647
|
+
// Skip whitespace
|
|
3648
|
+
while (i < len && /\s/.test(selector[i])) {
|
|
3649
|
+
i++;
|
|
3650
|
+
}
|
|
3651
|
+
if (i > start) {
|
|
3652
|
+
hasMatchedComponent = true;
|
|
3653
|
+
continue;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
// Match combinators: >, +, ~
|
|
3657
|
+
if (i < len && /[>+~]/.test(selector[i])) {
|
|
3658
|
+
i++;
|
|
3659
|
+
hasMatchedComponent = true;
|
|
3660
|
+
// Skip trailing whitespace
|
|
3661
|
+
while (i < len && /\s/.test(selector[i])) {
|
|
3662
|
+
i++;
|
|
3663
|
+
}
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// Match nesting selector: &
|
|
3668
|
+
if (i < len && selector[i] === '&') {
|
|
3669
|
+
i++;
|
|
3670
|
+
hasMatchedComponent = true;
|
|
3671
|
+
matched = true;
|
|
3672
|
+
}
|
|
3673
|
+
// Match class selector: .identifier
|
|
3674
|
+
else if (i < len && selector[i] === '.') {
|
|
3675
|
+
i++;
|
|
3676
|
+
if (parseIdentifier()) {
|
|
3677
|
+
hasMatchedComponent = true;
|
|
3678
|
+
matched = true;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
// Match ID selector: #identifier
|
|
3682
|
+
else if (i < len && selector[i] === '#') {
|
|
3683
|
+
i++;
|
|
3684
|
+
if (parseIdentifier()) {
|
|
3685
|
+
hasMatchedComponent = true;
|
|
3686
|
+
matched = true;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
// Match pseudo-class/element: :identifier or ::identifier
|
|
3690
|
+
else if (i < len && selector[i] === ':') {
|
|
3691
|
+
i++;
|
|
3692
|
+
if (i < len && selector[i] === ':') {
|
|
3693
|
+
i++; // Pseudo-element
|
|
3694
|
+
}
|
|
3695
|
+
if (parseIdentifier()) {
|
|
3696
|
+
parsePseudoArgs(); // Optional arguments
|
|
3697
|
+
hasMatchedComponent = true;
|
|
3698
|
+
matched = true;
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
// Match attribute selector: [...]
|
|
3702
|
+
else if (i < len && selector[i] === '[') {
|
|
3703
|
+
i++;
|
|
3704
|
+
var depth = 1;
|
|
3705
|
+
while (i < len && depth > 0) {
|
|
3706
|
+
if (selector[i] === '\\') {
|
|
3707
|
+
i += 2;
|
|
3708
|
+
} else if (selector[i] === '\'') {
|
|
3709
|
+
i++;
|
|
3710
|
+
while (i < len && selector[i] !== '\'') {
|
|
3711
|
+
if (selector[i] === '\\') i += 2;
|
|
3712
|
+
else i++;
|
|
3713
|
+
}
|
|
3714
|
+
if (i < len) i++; // Skip closing quote
|
|
3715
|
+
} else if (selector[i] === '"') {
|
|
3716
|
+
i++;
|
|
3717
|
+
while (i < len && selector[i] !== '"') {
|
|
3718
|
+
if (selector[i] === '\\') i += 2;
|
|
3719
|
+
else i++;
|
|
3720
|
+
}
|
|
3721
|
+
if (i < len) i++; // Skip closing quote
|
|
3722
|
+
} else if (selector[i] === '[') {
|
|
3723
|
+
depth++;
|
|
3724
|
+
i++;
|
|
3725
|
+
} else if (selector[i] === ']') {
|
|
3726
|
+
depth--;
|
|
3727
|
+
i++;
|
|
3728
|
+
} else {
|
|
3729
|
+
i++;
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
if (depth === 0) {
|
|
3733
|
+
hasMatchedComponent = true;
|
|
3734
|
+
matched = true;
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
// Match type selector with optional namespace: [namespace|]identifier
|
|
3738
|
+
else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|')) {
|
|
3739
|
+
parseNamespace(); // Optional namespace prefix
|
|
3740
|
+
|
|
3741
|
+
if (i < len && selector[i] === '*') {
|
|
3742
|
+
i++; // Universal selector
|
|
3743
|
+
hasMatchedComponent = true;
|
|
3744
|
+
matched = true;
|
|
3745
|
+
} else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
|
|
3746
|
+
if (parseIdentifier()) {
|
|
3747
|
+
hasMatchedComponent = true;
|
|
3748
|
+
matched = true;
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
// If no match found, invalid selector
|
|
3754
|
+
if (!matched && i === start) {
|
|
3755
|
+
return false;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
return hasMatchedComponent;
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3339
3762
|
/**
|
|
3340
3763
|
* Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
|
|
3341
3764
|
* This function replaces the previous basicSelectorRegExp.
|
|
@@ -3360,6 +3783,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3360
3783
|
* @returns {boolean}
|
|
3361
3784
|
*/
|
|
3362
3785
|
function basicSelectorValidator(selector) {
|
|
3786
|
+
// Guard against extremely long selectors to prevent potential regex performance issues
|
|
3787
|
+
// Reasonable selectors are typically under 1000 characters
|
|
3788
|
+
if (selector.length > 10000) {
|
|
3789
|
+
return false;
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3363
3792
|
// Validate balanced syntax with attribute tracking and stack-based parentheses matching
|
|
3364
3793
|
if (!validateBalancedSyntax(selector, true, true)) {
|
|
3365
3794
|
return false;
|
|
@@ -3382,31 +3811,69 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3382
3811
|
|
|
3383
3812
|
// Check for invalid pseudo-class usage with quoted strings
|
|
3384
3813
|
// Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
|
|
3385
|
-
|
|
3386
|
-
var
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3814
|
+
// Using iterative parsing instead of regex to avoid exponential backtracking
|
|
3815
|
+
var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
|
|
3816
|
+
|
|
3817
|
+
for (var idx = 0; idx < selector.length; idx++) {
|
|
3818
|
+
// Look for pseudo-class/element start
|
|
3819
|
+
if (selector[idx] === ':') {
|
|
3820
|
+
var pseudoStart = idx;
|
|
3821
|
+
idx++;
|
|
3822
|
+
|
|
3823
|
+
// Skip second colon for pseudo-elements
|
|
3824
|
+
if (idx < selector.length && selector[idx] === ':') {
|
|
3825
|
+
idx++;
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
// Extract pseudo name
|
|
3829
|
+
var nameStart = idx;
|
|
3830
|
+
while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
|
|
3831
|
+
idx++;
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
if (idx === nameStart) {
|
|
3835
|
+
continue; // No name found
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
var pseudoName = selector.substring(nameStart, idx).toLowerCase();
|
|
3839
|
+
|
|
3840
|
+
// Check if this pseudo has arguments
|
|
3841
|
+
if (idx < selector.length && selector[idx] === '(') {
|
|
3842
|
+
idx++;
|
|
3843
|
+
var contentStart = idx;
|
|
3844
|
+
var depth = 1;
|
|
3845
|
+
|
|
3846
|
+
// Find matching closing paren (handle nesting)
|
|
3847
|
+
while (idx < selector.length && depth > 0) {
|
|
3848
|
+
if (selector[idx] === '\\') {
|
|
3849
|
+
idx += 2; // Skip escaped character
|
|
3850
|
+
} else if (selector[idx] === '(') {
|
|
3851
|
+
depth++;
|
|
3852
|
+
idx++;
|
|
3853
|
+
} else if (selector[idx] === ')') {
|
|
3854
|
+
depth--;
|
|
3855
|
+
idx++;
|
|
3856
|
+
} else {
|
|
3857
|
+
idx++;
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
if (depth === 0) {
|
|
3862
|
+
var pseudoContent = selector.substring(contentStart, idx - 1);
|
|
3863
|
+
|
|
3864
|
+
// Check if this pseudo should not have quoted strings
|
|
3865
|
+
for (var j = 0; j < noQuotesPseudos.length; j++) {
|
|
3866
|
+
if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
|
|
3867
|
+
return false;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3400
3871
|
}
|
|
3401
3872
|
}
|
|
3402
3873
|
}
|
|
3403
3874
|
|
|
3404
|
-
//
|
|
3405
|
-
|
|
3406
|
-
// Modified to support namespace selectors: *|element, prefix|element, |element
|
|
3407
|
-
// Fixed attribute selector regex to properly handle |=, ~=, ^=, $=, *= operators
|
|
3408
|
-
var looseSelectorRegExp = /^((?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|(?:(?:\*|[a-zA-Z_\u00A0-\uFFFF\\][a-zA-Z0-9_\u00A0-\uFFFF\-\\]*|)\|)?\*|#[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\.[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+|\[(?:[^\[\]'"]|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")*(?:\s+[iI])?\]|::?[a-zA-Z0-9_\u00A0-\uFFFF\-\\]+(?:\((.*)\))?|&|\s*[>+~]\s*|\s+)+$/;
|
|
3409
|
-
return looseSelectorRegExp.test(selector);
|
|
3875
|
+
// Use the iterative validator to avoid regex backtracking issues
|
|
3876
|
+
return looseSelectorValidator(selector);
|
|
3410
3877
|
}
|
|
3411
3878
|
|
|
3412
3879
|
/**
|
|
@@ -3424,9 +3891,96 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3424
3891
|
* - :nth-child(2n+1)
|
|
3425
3892
|
* - :has(.sel:nth-child(3n))
|
|
3426
3893
|
* - :not(".foo, .bar")
|
|
3427
|
-
*
|
|
3894
|
+
*
|
|
3895
|
+
* REPLACED WITH FUNCTION to avoid exponential backtracking.
|
|
3428
3896
|
*/
|
|
3429
|
-
|
|
3897
|
+
|
|
3898
|
+
/**
|
|
3899
|
+
* Extract pseudo-classes with arguments from a selector using iterative parsing.
|
|
3900
|
+
* Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
|
|
3901
|
+
*
|
|
3902
|
+
* Handles:
|
|
3903
|
+
* - Regular content without parentheses or quotes
|
|
3904
|
+
* - Single-quoted strings
|
|
3905
|
+
* - Double-quoted strings
|
|
3906
|
+
* - Nested parentheses (arbitrary depth)
|
|
3907
|
+
*
|
|
3908
|
+
* @param {string} selector - The CSS selector to parse
|
|
3909
|
+
* @returns {Array} Array of matches, each with: [fullMatch, pseudoName, pseudoArgs, startIndex]
|
|
3910
|
+
*/
|
|
3911
|
+
function extractPseudoClasses(selector) {
|
|
3912
|
+
var matches = [];
|
|
3913
|
+
|
|
3914
|
+
for (var i = 0; i < selector.length; i++) {
|
|
3915
|
+
// Look for pseudo-class start (single or double colon)
|
|
3916
|
+
if (selector[i] === ':') {
|
|
3917
|
+
var pseudoStart = i;
|
|
3918
|
+
i++;
|
|
3919
|
+
|
|
3920
|
+
// Skip second colon for pseudo-elements (::)
|
|
3921
|
+
if (i < selector.length && selector[i] === ':') {
|
|
3922
|
+
i++;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
// Extract pseudo name
|
|
3926
|
+
var nameStart = i;
|
|
3927
|
+
while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
|
|
3928
|
+
i++;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
if (i === nameStart) {
|
|
3932
|
+
continue; // No name found
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
var pseudoName = selector.substring(nameStart, i);
|
|
3936
|
+
|
|
3937
|
+
// Check if this pseudo has arguments
|
|
3938
|
+
if (i < selector.length && selector[i] === '(') {
|
|
3939
|
+
i++;
|
|
3940
|
+
var argsStart = i;
|
|
3941
|
+
var depth = 1;
|
|
3942
|
+
var inSingleQuote = false;
|
|
3943
|
+
var inDoubleQuote = false;
|
|
3944
|
+
|
|
3945
|
+
// Find matching closing paren (handle nesting and strings)
|
|
3946
|
+
while (i < selector.length && depth > 0) {
|
|
3947
|
+
var ch = selector[i];
|
|
3948
|
+
|
|
3949
|
+
if (ch === '\\') {
|
|
3950
|
+
i += 2; // Skip escaped character
|
|
3951
|
+
} else if (ch === "'" && !inDoubleQuote) {
|
|
3952
|
+
inSingleQuote = !inSingleQuote;
|
|
3953
|
+
i++;
|
|
3954
|
+
} else if (ch === '"' && !inSingleQuote) {
|
|
3955
|
+
inDoubleQuote = !inDoubleQuote;
|
|
3956
|
+
i++;
|
|
3957
|
+
} else if (ch === '(' && !inSingleQuote && !inDoubleQuote) {
|
|
3958
|
+
depth++;
|
|
3959
|
+
i++;
|
|
3960
|
+
} else if (ch === ')' && !inSingleQuote && !inDoubleQuote) {
|
|
3961
|
+
depth--;
|
|
3962
|
+
i++;
|
|
3963
|
+
} else {
|
|
3964
|
+
i++;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
if (depth === 0) {
|
|
3969
|
+
var pseudoArgs = selector.substring(argsStart, i - 1);
|
|
3970
|
+
var fullMatch = selector.substring(pseudoStart, i);
|
|
3971
|
+
|
|
3972
|
+
// Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
|
|
3973
|
+
matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
// Move back one since loop will increment
|
|
3977
|
+
i--;
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
return matches;
|
|
3983
|
+
}
|
|
3430
3984
|
|
|
3431
3985
|
/**
|
|
3432
3986
|
* Parses a CSS selector string and splits it into parts, handling nested parentheses.
|
|
@@ -3521,13 +4075,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3521
4075
|
return validatedSelectorsCache[selector];
|
|
3522
4076
|
}
|
|
3523
4077
|
|
|
3524
|
-
// Use
|
|
3525
|
-
var pseudoClassMatches =
|
|
3526
|
-
var pseudoClassRegExp = new RegExp(globalPseudoClassRegExp.source, globalPseudoClassRegExp.flags);
|
|
3527
|
-
var match;
|
|
3528
|
-
while ((match = pseudoClassRegExp.exec(selector)) !== null) {
|
|
3529
|
-
pseudoClassMatches.push(match);
|
|
3530
|
-
}
|
|
4078
|
+
// Use function-based parsing to extract pseudo-classes (avoids backtracking)
|
|
4079
|
+
var pseudoClassMatches = extractPseudoClasses(selector);
|
|
3531
4080
|
|
|
3532
4081
|
for (var j = 0; j < pseudoClassMatches.length; j++) {
|
|
3533
4082
|
var pseudoClass = pseudoClassMatches[j][1];
|
|
@@ -3668,6 +4217,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3668
4217
|
return true;
|
|
3669
4218
|
}
|
|
3670
4219
|
|
|
4220
|
+
function pushToAncestorRules(rule) {
|
|
4221
|
+
if (ancestorRules.indexOf(rule) === -1) {
|
|
4222
|
+
ancestorRules.push(rule);
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
|
|
3671
4226
|
function parseError(message, isNested) {
|
|
3672
4227
|
var lines = token.substring(0, i).split('\n');
|
|
3673
4228
|
var lineCount = lines.length;
|
|
@@ -3694,10 +4249,47 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3694
4249
|
case "importRule":
|
|
3695
4250
|
case "namespaceRule":
|
|
3696
4251
|
case "layerBlock":
|
|
3697
|
-
|
|
4252
|
+
if (character !== ";") {
|
|
4253
|
+
token += ";";
|
|
4254
|
+
break;
|
|
4255
|
+
}
|
|
4256
|
+
case "value":
|
|
4257
|
+
if (character !== "}") {
|
|
4258
|
+
if (character === ";") {
|
|
4259
|
+
token += "}"
|
|
4260
|
+
} else {
|
|
4261
|
+
token += ";";
|
|
4262
|
+
}
|
|
4263
|
+
endingIndex += 1;
|
|
4264
|
+
break;
|
|
4265
|
+
}
|
|
4266
|
+
case "name":
|
|
4267
|
+
case "before-name":
|
|
4268
|
+
if (character === "}") {
|
|
4269
|
+
token += " "
|
|
4270
|
+
} else {
|
|
4271
|
+
token += "}"
|
|
4272
|
+
}
|
|
4273
|
+
endingIndex += 1
|
|
4274
|
+
break;
|
|
4275
|
+
case "before-selector":
|
|
4276
|
+
if (character !== "}" && currentScope !== styleSheet) {
|
|
4277
|
+
token += "}"
|
|
4278
|
+
endingIndex += 1
|
|
4279
|
+
break;
|
|
4280
|
+
}
|
|
3698
4281
|
}
|
|
3699
4282
|
}
|
|
3700
4283
|
|
|
4284
|
+
// Handle escape sequences before processing special characters
|
|
4285
|
+
// If we encounter a backslash, add both the backslash and the next character to buffer
|
|
4286
|
+
// and skip the next iteration to prevent the escaped character from being interpreted
|
|
4287
|
+
if (character === '\\' && i + 1 < token.length) {
|
|
4288
|
+
buffer += character + token.charAt(i + 1);
|
|
4289
|
+
i++; // Skip the next character
|
|
4290
|
+
continue;
|
|
4291
|
+
}
|
|
4292
|
+
|
|
3701
4293
|
switch (character) {
|
|
3702
4294
|
|
|
3703
4295
|
case " ":
|
|
@@ -3795,6 +4387,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3795
4387
|
|
|
3796
4388
|
// At-rule
|
|
3797
4389
|
case "@":
|
|
4390
|
+
if (nestedSelectorRule) {
|
|
4391
|
+
if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
4392
|
+
currentScope.cssRules.push(styleRule);
|
|
4393
|
+
}
|
|
4394
|
+
if (nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
|
|
4395
|
+
styleRule = nestedSelectorRule.parentRule;
|
|
4396
|
+
}
|
|
4397
|
+
nestedSelectorRule = null;
|
|
4398
|
+
}
|
|
3798
4399
|
if (token.indexOf("@-moz-document", i) === i) {
|
|
3799
4400
|
validateAtRule("@-moz-document", function(){
|
|
3800
4401
|
state = "documentRule-begin";
|
|
@@ -3929,7 +4530,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3929
4530
|
break;
|
|
3930
4531
|
|
|
3931
4532
|
case "{":
|
|
3932
|
-
if (currentScope ===
|
|
4533
|
+
if (currentScope === topScope) {
|
|
3933
4534
|
nestedSelectorRule = null;
|
|
3934
4535
|
}
|
|
3935
4536
|
if (state === 'before-selector') {
|
|
@@ -3951,7 +4552,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3951
4552
|
|
|
3952
4553
|
if (parentRule) {
|
|
3953
4554
|
styleRule.__parentRule = parentRule;
|
|
3954
|
-
|
|
4555
|
+
pushToAncestorRules(parentRule);
|
|
3955
4556
|
}
|
|
3956
4557
|
|
|
3957
4558
|
currentScope = parentRule = styleRule;
|
|
@@ -3965,7 +4566,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3965
4566
|
|
|
3966
4567
|
if (parentRule) {
|
|
3967
4568
|
mediaRule.__parentRule = parentRule;
|
|
3968
|
-
|
|
4569
|
+
pushToAncestorRules(parentRule);
|
|
3969
4570
|
}
|
|
3970
4571
|
|
|
3971
4572
|
currentScope = parentRule = mediaRule;
|
|
@@ -3977,7 +4578,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3977
4578
|
|
|
3978
4579
|
if (parentRule) {
|
|
3979
4580
|
containerRule.__parentRule = parentRule;
|
|
3980
|
-
|
|
4581
|
+
pushToAncestorRules(parentRule);
|
|
3981
4582
|
}
|
|
3982
4583
|
currentScope = parentRule = containerRule;
|
|
3983
4584
|
containerRule.__parentStyleSheet = styleSheet;
|
|
@@ -3994,7 +4595,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3994
4595
|
|
|
3995
4596
|
if (parentRule) {
|
|
3996
4597
|
supportsRule.__parentRule = parentRule;
|
|
3997
|
-
|
|
4598
|
+
pushToAncestorRules(parentRule);
|
|
3998
4599
|
}
|
|
3999
4600
|
|
|
4000
4601
|
currentScope = parentRule = supportsRule;
|
|
@@ -4016,7 +4617,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4016
4617
|
|
|
4017
4618
|
if (parentRule) {
|
|
4018
4619
|
scopeRule.__parentRule = parentRule;
|
|
4019
|
-
|
|
4620
|
+
pushToAncestorRules(parentRule);
|
|
4020
4621
|
}
|
|
4021
4622
|
currentScope = parentRule = scopeRule;
|
|
4022
4623
|
scopeRule.__parentStyleSheet = styleSheet;
|
|
@@ -4030,7 +4631,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4030
4631
|
if (isValidName) {
|
|
4031
4632
|
if (parentRule) {
|
|
4032
4633
|
layerBlockRule.__parentRule = parentRule;
|
|
4033
|
-
|
|
4634
|
+
pushToAncestorRules(parentRule);
|
|
4034
4635
|
}
|
|
4035
4636
|
|
|
4036
4637
|
currentScope = parentRule = layerBlockRule;
|
|
@@ -4043,7 +4644,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4043
4644
|
|
|
4044
4645
|
if (parentRule) {
|
|
4045
4646
|
pageRule.__parentRule = parentRule;
|
|
4046
|
-
|
|
4647
|
+
pushToAncestorRules(parentRule);
|
|
4047
4648
|
}
|
|
4048
4649
|
|
|
4049
4650
|
currentScope = parentRule = pageRule;
|
|
@@ -4053,7 +4654,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4053
4654
|
state = "before-name";
|
|
4054
4655
|
} else if (state === "hostRule-begin") {
|
|
4055
4656
|
if (parentRule) {
|
|
4056
|
-
|
|
4657
|
+
pushToAncestorRules(parentRule);
|
|
4057
4658
|
}
|
|
4058
4659
|
|
|
4059
4660
|
currentScope = parentRule = hostRule;
|
|
@@ -4063,7 +4664,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4063
4664
|
} else if (state === "startingStyleRule-begin") {
|
|
4064
4665
|
if (parentRule) {
|
|
4065
4666
|
startingStyleRule.__parentRule = parentRule;
|
|
4066
|
-
|
|
4667
|
+
pushToAncestorRules(parentRule);
|
|
4067
4668
|
}
|
|
4068
4669
|
|
|
4069
4670
|
currentScope = parentRule = startingStyleRule;
|
|
@@ -4082,7 +4683,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4082
4683
|
} else if (state === "keyframesRule-begin") {
|
|
4083
4684
|
keyframesRule.name = buffer.trim();
|
|
4084
4685
|
if (parentRule) {
|
|
4085
|
-
|
|
4686
|
+
pushToAncestorRules(parentRule);
|
|
4086
4687
|
keyframesRule.__parentRule = parentRule;
|
|
4087
4688
|
}
|
|
4088
4689
|
keyframesRule.__parentStyleSheet = styleSheet;
|
|
@@ -4099,7 +4700,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4099
4700
|
// FIXME: what if this '{' is in the url text of the match function?
|
|
4100
4701
|
documentRule.matcher.matcherText = buffer.trim();
|
|
4101
4702
|
if (parentRule) {
|
|
4102
|
-
|
|
4703
|
+
pushToAncestorRules(parentRule);
|
|
4103
4704
|
documentRule.__parentRule = parentRule;
|
|
4104
4705
|
}
|
|
4105
4706
|
currentScope = parentRule = documentRule;
|
|
@@ -4112,21 +4713,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4112
4713
|
parentRule.cssRules.push(styleRule);
|
|
4113
4714
|
styleRule.__parentRule = parentRule;
|
|
4114
4715
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4115
|
-
|
|
4716
|
+
pushToAncestorRules(parentRule);
|
|
4116
4717
|
} else {
|
|
4117
4718
|
// If the styleRule is empty, we can assume that it's a nested selector
|
|
4118
|
-
|
|
4719
|
+
pushToAncestorRules(parentRule);
|
|
4119
4720
|
}
|
|
4120
4721
|
} else {
|
|
4121
4722
|
currentScope = parentRule = styleRule;
|
|
4122
|
-
|
|
4723
|
+
pushToAncestorRules(parentRule);
|
|
4123
4724
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4124
4725
|
}
|
|
4125
4726
|
|
|
4126
4727
|
styleRule = new CSSOM.CSSStyleRule();
|
|
4127
4728
|
var processedSelectorText = processSelectorText(buffer.trim());
|
|
4128
4729
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
4129
|
-
if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
|
|
4730
|
+
if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
|
|
4130
4731
|
styleRule.selectorText = processedSelectorText;
|
|
4131
4732
|
} else {
|
|
4132
4733
|
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
|
|
@@ -4225,20 +4826,20 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4225
4826
|
state = "before-selector";
|
|
4226
4827
|
break;
|
|
4227
4828
|
case "importRule":
|
|
4228
|
-
var isValid =
|
|
4829
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
|
|
4229
4830
|
return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
|
|
4230
4831
|
});
|
|
4231
4832
|
if (isValid) {
|
|
4232
4833
|
importRule = new CSSOM.CSSImportRule();
|
|
4233
4834
|
importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
|
|
4234
4835
|
importRule.cssText = buffer + character;
|
|
4235
|
-
|
|
4836
|
+
topScope.cssRules.push(importRule);
|
|
4236
4837
|
}
|
|
4237
4838
|
buffer = "";
|
|
4238
4839
|
state = "before-selector";
|
|
4239
4840
|
break;
|
|
4240
4841
|
case "namespaceRule":
|
|
4241
|
-
var isValid =
|
|
4842
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
|
|
4242
4843
|
return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
|
|
4243
4844
|
});
|
|
4244
4845
|
if (isValid) {
|
|
@@ -4249,7 +4850,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4249
4850
|
|
|
4250
4851
|
namespaceRule = testNamespaceRule;
|
|
4251
4852
|
namespaceRule.__parentStyleSheet = styleSheet;
|
|
4252
|
-
|
|
4853
|
+
topScope.cssRules.push(namespaceRule);
|
|
4253
4854
|
|
|
4254
4855
|
// Track the namespace prefix for validation
|
|
4255
4856
|
if (namespaceRule.prefix) {
|
|
@@ -4276,7 +4877,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4276
4877
|
layerStatementRule.__starts = layerBlockRule.__starts;
|
|
4277
4878
|
layerStatementRule.__ends = i;
|
|
4278
4879
|
layerStatementRule.nameList = nameListStr;
|
|
4279
|
-
|
|
4880
|
+
topScope.cssRules.push(layerStatementRule);
|
|
4280
4881
|
}
|
|
4281
4882
|
buffer = "";
|
|
4282
4883
|
state = "before-selector";
|
|
@@ -4317,7 +4918,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4317
4918
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4318
4919
|
|
|
4319
4920
|
if (currentScope === styleRule) {
|
|
4320
|
-
currentScope = parentRule ||
|
|
4921
|
+
currentScope = parentRule || topScope;
|
|
4321
4922
|
}
|
|
4322
4923
|
|
|
4323
4924
|
if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
|
|
@@ -4326,7 +4927,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4326
4927
|
}
|
|
4327
4928
|
parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
|
|
4328
4929
|
} else {
|
|
4329
|
-
|
|
4930
|
+
if (styleRule.parentRule) {
|
|
4931
|
+
styleRule.parentRule.cssRules.push(styleRule);
|
|
4932
|
+
} else {
|
|
4933
|
+
currentScope.cssRules.push(styleRule);
|
|
4934
|
+
}
|
|
4330
4935
|
}
|
|
4331
4936
|
buffer = "";
|
|
4332
4937
|
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
@@ -4336,7 +4941,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4336
4941
|
}
|
|
4337
4942
|
|
|
4338
4943
|
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
4339
|
-
if (currentScope !==
|
|
4944
|
+
if (currentScope !== topScope) {
|
|
4340
4945
|
nestedSelectorRule = currentScope;
|
|
4341
4946
|
}
|
|
4342
4947
|
styleRule = null;
|
|
@@ -4359,7 +4964,6 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4359
4964
|
break;
|
|
4360
4965
|
}
|
|
4361
4966
|
|
|
4362
|
-
|
|
4363
4967
|
while (ancestorRules.length > 0) {
|
|
4364
4968
|
parentRule = ancestorRules.pop();
|
|
4365
4969
|
|
|
@@ -4385,8 +4989,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4385
4989
|
}
|
|
4386
4990
|
} else {
|
|
4387
4991
|
prevScope = currentScope;
|
|
4388
|
-
|
|
4389
|
-
currentScope !== prevScope && currentScope.cssRules.push(prevScope);
|
|
4992
|
+
parentRule !== prevScope && parentRule.cssRules.push(prevScope);
|
|
4390
4993
|
break;
|
|
4391
4994
|
}
|
|
4392
4995
|
}
|
|
@@ -4394,12 +4997,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4394
4997
|
|
|
4395
4998
|
if (currentScope.parentRule == null) {
|
|
4396
4999
|
currentScope.__ends = i + 1;
|
|
4397
|
-
if (currentScope !==
|
|
5000
|
+
if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
|
|
4398
5001
|
return rule === currentScope
|
|
4399
5002
|
}) === -1) {
|
|
4400
|
-
|
|
5003
|
+
topScope.cssRules.push(currentScope);
|
|
4401
5004
|
}
|
|
4402
|
-
currentScope =
|
|
5005
|
+
currentScope = topScope;
|
|
4403
5006
|
if (nestedSelectorRule === parentRule) {
|
|
4404
5007
|
// Check if this selector is really starting inside another selector
|
|
4405
5008
|
var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
|
|
@@ -4418,6 +5021,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4418
5021
|
parentRule = null;
|
|
4419
5022
|
|
|
4420
5023
|
}
|
|
5024
|
+
} else {
|
|
5025
|
+
currentScope = parentRule;
|
|
4421
5026
|
}
|
|
4422
5027
|
|
|
4423
5028
|
buffer = "";
|
|
@@ -4430,7 +5035,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4430
5035
|
switch (state) {
|
|
4431
5036
|
case "before-selector":
|
|
4432
5037
|
state = "selector";
|
|
4433
|
-
if (styleRule && parentRule) {
|
|
5038
|
+
if ((styleRule || scopeRule) && parentRule) {
|
|
4434
5039
|
// Assuming it's a declaration inside Nested Selector OR a Nested Declaration
|
|
4435
5040
|
// If Declaration inside Nested Selector let's keep the same styleRule
|
|
4436
5041
|
if (
|