@acemir/cssom 0.9.23 → 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 +714 -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 +509 -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;
|
|
@@ -3695,11 +4250,46 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3695
4250
|
case "namespaceRule":
|
|
3696
4251
|
case "layerBlock":
|
|
3697
4252
|
if (character !== ";") {
|
|
3698
|
-
token += ";"
|
|
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;
|
|
3699
4280
|
}
|
|
3700
4281
|
}
|
|
3701
4282
|
}
|
|
3702
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
|
+
|
|
3703
4293
|
switch (character) {
|
|
3704
4294
|
|
|
3705
4295
|
case " ":
|
|
@@ -3797,6 +4387,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3797
4387
|
|
|
3798
4388
|
// At-rule
|
|
3799
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
|
+
}
|
|
3800
4399
|
if (token.indexOf("@-moz-document", i) === i) {
|
|
3801
4400
|
validateAtRule("@-moz-document", function(){
|
|
3802
4401
|
state = "documentRule-begin";
|
|
@@ -3931,7 +4530,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3931
4530
|
break;
|
|
3932
4531
|
|
|
3933
4532
|
case "{":
|
|
3934
|
-
if (currentScope ===
|
|
4533
|
+
if (currentScope === topScope) {
|
|
3935
4534
|
nestedSelectorRule = null;
|
|
3936
4535
|
}
|
|
3937
4536
|
if (state === 'before-selector') {
|
|
@@ -3953,7 +4552,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3953
4552
|
|
|
3954
4553
|
if (parentRule) {
|
|
3955
4554
|
styleRule.__parentRule = parentRule;
|
|
3956
|
-
|
|
4555
|
+
pushToAncestorRules(parentRule);
|
|
3957
4556
|
}
|
|
3958
4557
|
|
|
3959
4558
|
currentScope = parentRule = styleRule;
|
|
@@ -3967,7 +4566,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3967
4566
|
|
|
3968
4567
|
if (parentRule) {
|
|
3969
4568
|
mediaRule.__parentRule = parentRule;
|
|
3970
|
-
|
|
4569
|
+
pushToAncestorRules(parentRule);
|
|
3971
4570
|
}
|
|
3972
4571
|
|
|
3973
4572
|
currentScope = parentRule = mediaRule;
|
|
@@ -3979,7 +4578,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3979
4578
|
|
|
3980
4579
|
if (parentRule) {
|
|
3981
4580
|
containerRule.__parentRule = parentRule;
|
|
3982
|
-
|
|
4581
|
+
pushToAncestorRules(parentRule);
|
|
3983
4582
|
}
|
|
3984
4583
|
currentScope = parentRule = containerRule;
|
|
3985
4584
|
containerRule.__parentStyleSheet = styleSheet;
|
|
@@ -3996,7 +4595,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3996
4595
|
|
|
3997
4596
|
if (parentRule) {
|
|
3998
4597
|
supportsRule.__parentRule = parentRule;
|
|
3999
|
-
|
|
4598
|
+
pushToAncestorRules(parentRule);
|
|
4000
4599
|
}
|
|
4001
4600
|
|
|
4002
4601
|
currentScope = parentRule = supportsRule;
|
|
@@ -4018,7 +4617,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4018
4617
|
|
|
4019
4618
|
if (parentRule) {
|
|
4020
4619
|
scopeRule.__parentRule = parentRule;
|
|
4021
|
-
|
|
4620
|
+
pushToAncestorRules(parentRule);
|
|
4022
4621
|
}
|
|
4023
4622
|
currentScope = parentRule = scopeRule;
|
|
4024
4623
|
scopeRule.__parentStyleSheet = styleSheet;
|
|
@@ -4032,7 +4631,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4032
4631
|
if (isValidName) {
|
|
4033
4632
|
if (parentRule) {
|
|
4034
4633
|
layerBlockRule.__parentRule = parentRule;
|
|
4035
|
-
|
|
4634
|
+
pushToAncestorRules(parentRule);
|
|
4036
4635
|
}
|
|
4037
4636
|
|
|
4038
4637
|
currentScope = parentRule = layerBlockRule;
|
|
@@ -4045,7 +4644,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4045
4644
|
|
|
4046
4645
|
if (parentRule) {
|
|
4047
4646
|
pageRule.__parentRule = parentRule;
|
|
4048
|
-
|
|
4647
|
+
pushToAncestorRules(parentRule);
|
|
4049
4648
|
}
|
|
4050
4649
|
|
|
4051
4650
|
currentScope = parentRule = pageRule;
|
|
@@ -4055,7 +4654,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4055
4654
|
state = "before-name";
|
|
4056
4655
|
} else if (state === "hostRule-begin") {
|
|
4057
4656
|
if (parentRule) {
|
|
4058
|
-
|
|
4657
|
+
pushToAncestorRules(parentRule);
|
|
4059
4658
|
}
|
|
4060
4659
|
|
|
4061
4660
|
currentScope = parentRule = hostRule;
|
|
@@ -4065,7 +4664,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4065
4664
|
} else if (state === "startingStyleRule-begin") {
|
|
4066
4665
|
if (parentRule) {
|
|
4067
4666
|
startingStyleRule.__parentRule = parentRule;
|
|
4068
|
-
|
|
4667
|
+
pushToAncestorRules(parentRule);
|
|
4069
4668
|
}
|
|
4070
4669
|
|
|
4071
4670
|
currentScope = parentRule = startingStyleRule;
|
|
@@ -4084,7 +4683,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4084
4683
|
} else if (state === "keyframesRule-begin") {
|
|
4085
4684
|
keyframesRule.name = buffer.trim();
|
|
4086
4685
|
if (parentRule) {
|
|
4087
|
-
|
|
4686
|
+
pushToAncestorRules(parentRule);
|
|
4088
4687
|
keyframesRule.__parentRule = parentRule;
|
|
4089
4688
|
}
|
|
4090
4689
|
keyframesRule.__parentStyleSheet = styleSheet;
|
|
@@ -4101,7 +4700,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4101
4700
|
// FIXME: what if this '{' is in the url text of the match function?
|
|
4102
4701
|
documentRule.matcher.matcherText = buffer.trim();
|
|
4103
4702
|
if (parentRule) {
|
|
4104
|
-
|
|
4703
|
+
pushToAncestorRules(parentRule);
|
|
4105
4704
|
documentRule.__parentRule = parentRule;
|
|
4106
4705
|
}
|
|
4107
4706
|
currentScope = parentRule = documentRule;
|
|
@@ -4114,21 +4713,21 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4114
4713
|
parentRule.cssRules.push(styleRule);
|
|
4115
4714
|
styleRule.__parentRule = parentRule;
|
|
4116
4715
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4117
|
-
|
|
4716
|
+
pushToAncestorRules(parentRule);
|
|
4118
4717
|
} else {
|
|
4119
4718
|
// If the styleRule is empty, we can assume that it's a nested selector
|
|
4120
|
-
|
|
4719
|
+
pushToAncestorRules(parentRule);
|
|
4121
4720
|
}
|
|
4122
4721
|
} else {
|
|
4123
4722
|
currentScope = parentRule = styleRule;
|
|
4124
|
-
|
|
4723
|
+
pushToAncestorRules(parentRule);
|
|
4125
4724
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4126
4725
|
}
|
|
4127
4726
|
|
|
4128
4727
|
styleRule = new CSSOM.CSSStyleRule();
|
|
4129
4728
|
var processedSelectorText = processSelectorText(buffer.trim());
|
|
4130
4729
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
4131
|
-
if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
|
|
4730
|
+
if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
|
|
4132
4731
|
styleRule.selectorText = processedSelectorText;
|
|
4133
4732
|
} else {
|
|
4134
4733
|
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
|
|
@@ -4227,20 +4826,20 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4227
4826
|
state = "before-selector";
|
|
4228
4827
|
break;
|
|
4229
4828
|
case "importRule":
|
|
4230
|
-
var isValid =
|
|
4829
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
|
|
4231
4830
|
return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
|
|
4232
4831
|
});
|
|
4233
4832
|
if (isValid) {
|
|
4234
4833
|
importRule = new CSSOM.CSSImportRule();
|
|
4235
4834
|
importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
|
|
4236
4835
|
importRule.cssText = buffer + character;
|
|
4237
|
-
|
|
4836
|
+
topScope.cssRules.push(importRule);
|
|
4238
4837
|
}
|
|
4239
4838
|
buffer = "";
|
|
4240
4839
|
state = "before-selector";
|
|
4241
4840
|
break;
|
|
4242
4841
|
case "namespaceRule":
|
|
4243
|
-
var isValid =
|
|
4842
|
+
var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
|
|
4244
4843
|
return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
|
|
4245
4844
|
});
|
|
4246
4845
|
if (isValid) {
|
|
@@ -4251,7 +4850,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4251
4850
|
|
|
4252
4851
|
namespaceRule = testNamespaceRule;
|
|
4253
4852
|
namespaceRule.__parentStyleSheet = styleSheet;
|
|
4254
|
-
|
|
4853
|
+
topScope.cssRules.push(namespaceRule);
|
|
4255
4854
|
|
|
4256
4855
|
// Track the namespace prefix for validation
|
|
4257
4856
|
if (namespaceRule.prefix) {
|
|
@@ -4278,7 +4877,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4278
4877
|
layerStatementRule.__starts = layerBlockRule.__starts;
|
|
4279
4878
|
layerStatementRule.__ends = i;
|
|
4280
4879
|
layerStatementRule.nameList = nameListStr;
|
|
4281
|
-
|
|
4880
|
+
topScope.cssRules.push(layerStatementRule);
|
|
4282
4881
|
}
|
|
4283
4882
|
buffer = "";
|
|
4284
4883
|
state = "before-selector";
|
|
@@ -4319,7 +4918,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4319
4918
|
styleRule.__parentStyleSheet = styleSheet;
|
|
4320
4919
|
|
|
4321
4920
|
if (currentScope === styleRule) {
|
|
4322
|
-
currentScope = parentRule ||
|
|
4921
|
+
currentScope = parentRule || topScope;
|
|
4323
4922
|
}
|
|
4324
4923
|
|
|
4325
4924
|
if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
|
|
@@ -4328,7 +4927,11 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4328
4927
|
}
|
|
4329
4928
|
parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
|
|
4330
4929
|
} else {
|
|
4331
|
-
|
|
4930
|
+
if (styleRule.parentRule) {
|
|
4931
|
+
styleRule.parentRule.cssRules.push(styleRule);
|
|
4932
|
+
} else {
|
|
4933
|
+
currentScope.cssRules.push(styleRule);
|
|
4934
|
+
}
|
|
4332
4935
|
}
|
|
4333
4936
|
buffer = "";
|
|
4334
4937
|
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
@@ -4338,7 +4941,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4338
4941
|
}
|
|
4339
4942
|
|
|
4340
4943
|
if (styleRule.constructor.name === "CSSNestedDeclarations") {
|
|
4341
|
-
if (currentScope !==
|
|
4944
|
+
if (currentScope !== topScope) {
|
|
4342
4945
|
nestedSelectorRule = currentScope;
|
|
4343
4946
|
}
|
|
4344
4947
|
styleRule = null;
|
|
@@ -4361,7 +4964,6 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4361
4964
|
break;
|
|
4362
4965
|
}
|
|
4363
4966
|
|
|
4364
|
-
|
|
4365
4967
|
while (ancestorRules.length > 0) {
|
|
4366
4968
|
parentRule = ancestorRules.pop();
|
|
4367
4969
|
|
|
@@ -4387,8 +4989,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4387
4989
|
}
|
|
4388
4990
|
} else {
|
|
4389
4991
|
prevScope = currentScope;
|
|
4390
|
-
|
|
4391
|
-
currentScope !== prevScope && currentScope.cssRules.push(prevScope);
|
|
4992
|
+
parentRule !== prevScope && parentRule.cssRules.push(prevScope);
|
|
4392
4993
|
break;
|
|
4393
4994
|
}
|
|
4394
4995
|
}
|
|
@@ -4396,12 +4997,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4396
4997
|
|
|
4397
4998
|
if (currentScope.parentRule == null) {
|
|
4398
4999
|
currentScope.__ends = i + 1;
|
|
4399
|
-
if (currentScope !==
|
|
5000
|
+
if (currentScope !== topScope && topScope.cssRules.findIndex(function (rule) {
|
|
4400
5001
|
return rule === currentScope
|
|
4401
5002
|
}) === -1) {
|
|
4402
|
-
|
|
5003
|
+
topScope.cssRules.push(currentScope);
|
|
4403
5004
|
}
|
|
4404
|
-
currentScope =
|
|
5005
|
+
currentScope = topScope;
|
|
4405
5006
|
if (nestedSelectorRule === parentRule) {
|
|
4406
5007
|
// Check if this selector is really starting inside another selector
|
|
4407
5008
|
var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
|
|
@@ -4420,6 +5021,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4420
5021
|
parentRule = null;
|
|
4421
5022
|
|
|
4422
5023
|
}
|
|
5024
|
+
} else {
|
|
5025
|
+
currentScope = parentRule;
|
|
4423
5026
|
}
|
|
4424
5027
|
|
|
4425
5028
|
buffer = "";
|
|
@@ -4432,7 +5035,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
4432
5035
|
switch (state) {
|
|
4433
5036
|
case "before-selector":
|
|
4434
5037
|
state = "selector";
|
|
4435
|
-
if (styleRule && parentRule) {
|
|
5038
|
+
if ((styleRule || scopeRule) && parentRule) {
|
|
4436
5039
|
// Assuming it's a declaration inside Nested Selector OR a Nested Declaration
|
|
4437
5040
|
// If Declaration inside Nested Selector let's keep the same styleRule
|
|
4438
5041
|
if (
|