@acemir/cssom 0.9.19 → 0.9.21
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 +1121 -217
- package/lib/CSSConditionRule.js +8 -3
- package/lib/CSSContainerRule.js +21 -12
- package/lib/CSSDocumentRule.js +2 -1
- package/lib/CSSGroupingRule.js +2 -1
- package/lib/CSSHostRule.js +3 -2
- package/lib/CSSImportRule.js +92 -19
- package/lib/CSSKeyframesRule.js +2 -1
- package/lib/CSSLayerBlockRule.js +1 -1
- package/lib/CSSMediaRule.js +14 -5
- package/lib/CSSNamespaceRule.js +27 -11
- package/lib/CSSPageRule.js +275 -0
- package/lib/CSSRule.js +6 -0
- package/lib/CSSRuleList.js +26 -0
- package/lib/CSSScopeRule.js +53 -0
- package/lib/CSSStartingStyleRule.js +5 -4
- package/lib/CSSStyleRule.js +21 -5
- package/lib/CSSStyleSheet.js +49 -9
- package/lib/CSSSupportsRule.js +1 -0
- package/lib/StyleSheet.js +16 -1
- package/lib/clone.js +1 -0
- package/lib/index.js +3 -0
- package/lib/parse.js +504 -104
- package/package.json +1 -1
package/build/CSSOM.js
CHANGED
|
@@ -326,6 +326,12 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
|
|
|
326
326
|
|
|
327
327
|
constructor: { value: CSSOM.CSSRule },
|
|
328
328
|
|
|
329
|
+
cssRule: {
|
|
330
|
+
value: "",
|
|
331
|
+
configurable: true,
|
|
332
|
+
enumerable: true
|
|
333
|
+
},
|
|
334
|
+
|
|
329
335
|
parentRule: {
|
|
330
336
|
get: function() {
|
|
331
337
|
return this.__parentRule
|
|
@@ -364,6 +370,28 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
|
|
|
364
370
|
|
|
365
371
|
|
|
366
372
|
|
|
373
|
+
/**
|
|
374
|
+
* @constructor
|
|
375
|
+
* @see https://drafts.csswg.org/cssom/#the-cssrulelist-interface
|
|
376
|
+
*/
|
|
377
|
+
CSSOM.CSSRuleList = function CSSRuleList(){
|
|
378
|
+
const arr = new Array();
|
|
379
|
+
Object.setPrototypeOf(arr, CSSOM.CSSRuleList.prototype);
|
|
380
|
+
return arr;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
CSSOM.CSSRuleList.prototype = Object.create(Array.prototype);
|
|
384
|
+
CSSOM.CSSRuleList.prototype.constructor = CSSOM.CSSRuleList;
|
|
385
|
+
|
|
386
|
+
CSSOM.CSSRuleList.prototype.item = function(index) {
|
|
387
|
+
return this[index] || null;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
367
395
|
/**
|
|
368
396
|
* @constructor
|
|
369
397
|
* @see https://drafts.csswg.org/css-nesting-1/
|
|
@@ -408,7 +436,7 @@ Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", {
|
|
|
408
436
|
*/
|
|
409
437
|
CSSOM.CSSGroupingRule = function CSSGroupingRule() {
|
|
410
438
|
CSSOM.CSSRule.call(this);
|
|
411
|
-
this.cssRules =
|
|
439
|
+
this.cssRules = new CSSOM.CSSRuleList();
|
|
412
440
|
};
|
|
413
441
|
|
|
414
442
|
CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
|
|
@@ -525,13 +553,17 @@ CSSOM.CSSCounterStyleRule.prototype.type = 11;
|
|
|
525
553
|
*/
|
|
526
554
|
CSSOM.CSSConditionRule = function CSSConditionRule() {
|
|
527
555
|
CSSOM.CSSGroupingRule.call(this);
|
|
528
|
-
this.
|
|
556
|
+
this.__conditionText = '';
|
|
529
557
|
};
|
|
530
558
|
|
|
531
559
|
CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
|
|
532
560
|
CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
|
|
533
|
-
|
|
534
|
-
CSSOM.CSSConditionRule.prototype
|
|
561
|
+
|
|
562
|
+
Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
|
|
563
|
+
get: function () {
|
|
564
|
+
return this.__conditionText;
|
|
565
|
+
}
|
|
566
|
+
});
|
|
535
567
|
|
|
536
568
|
|
|
537
569
|
|
|
@@ -562,7 +594,20 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "selectorText", {
|
|
|
562
594
|
return this.__selectorText;
|
|
563
595
|
},
|
|
564
596
|
set: function(value) {
|
|
565
|
-
|
|
597
|
+
if (typeof value === "string") {
|
|
598
|
+
var trimmedValue = value.trim();
|
|
599
|
+
|
|
600
|
+
if (trimmedValue === '') {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// TODO: Setting invalid selectorText should be ignored
|
|
605
|
+
// There are some validations already on lib/parse.js
|
|
606
|
+
// but the same validations should be applied here.
|
|
607
|
+
// Check if we can move these validation logic to a shared function.
|
|
608
|
+
|
|
609
|
+
this.__selectorText = trimmedValue;
|
|
610
|
+
}
|
|
566
611
|
}
|
|
567
612
|
});
|
|
568
613
|
|
|
@@ -599,9 +644,11 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
|
|
|
599
644
|
return text;
|
|
600
645
|
},
|
|
601
646
|
set: function(cssText) {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
647
|
+
if (typeof cssText === "string") {
|
|
648
|
+
var rule = CSSOM.CSSStyleRule.parse(cssText);
|
|
649
|
+
this.__style = rule.style;
|
|
650
|
+
this.selectorText = rule.selectorText;
|
|
651
|
+
}
|
|
605
652
|
}
|
|
606
653
|
});
|
|
607
654
|
|
|
@@ -819,7 +866,7 @@ CSSOM.MediaList.prototype = {
|
|
|
819
866
|
*/
|
|
820
867
|
CSSOM.CSSMediaRule = function CSSMediaRule() {
|
|
821
868
|
CSSOM.CSSConditionRule.call(this);
|
|
822
|
-
this.
|
|
869
|
+
this.__media = new CSSOM.MediaList();
|
|
823
870
|
};
|
|
824
871
|
|
|
825
872
|
CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
|
|
@@ -828,16 +875,24 @@ CSSOM.CSSMediaRule.prototype.type = 4;
|
|
|
828
875
|
|
|
829
876
|
// https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
|
|
830
877
|
Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
|
|
831
|
-
"
|
|
878
|
+
"media": {
|
|
832
879
|
get: function() {
|
|
833
|
-
return this.
|
|
880
|
+
return this.__media;
|
|
834
881
|
},
|
|
835
882
|
set: function(value) {
|
|
836
|
-
|
|
883
|
+
if (typeof value === "string") {
|
|
884
|
+
this.__media.mediaText = value;
|
|
885
|
+
} else {
|
|
886
|
+
this.__media = value;
|
|
887
|
+
}
|
|
837
888
|
},
|
|
838
|
-
configurable: true,
|
|
839
889
|
enumerable: true
|
|
840
890
|
},
|
|
891
|
+
"conditionText": {
|
|
892
|
+
get: function() {
|
|
893
|
+
return this.media.mediaText;
|
|
894
|
+
}
|
|
895
|
+
},
|
|
841
896
|
"cssText": {
|
|
842
897
|
get: function() {
|
|
843
898
|
var cssTexts = [];
|
|
@@ -870,27 +925,35 @@ CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
|
|
|
870
925
|
CSSOM.CSSContainerRule.prototype.type = 17;
|
|
871
926
|
|
|
872
927
|
Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
|
|
873
|
-
"conditionText": {
|
|
874
|
-
get: function() {
|
|
875
|
-
return this.containerText;
|
|
876
|
-
},
|
|
877
|
-
set: function(value) {
|
|
878
|
-
this.containerText = value;
|
|
879
|
-
},
|
|
880
|
-
configurable: true,
|
|
881
|
-
enumerable: true
|
|
882
|
-
},
|
|
883
928
|
"cssText": {
|
|
884
929
|
get: function() {
|
|
885
930
|
var cssTexts = [];
|
|
886
931
|
for (var i=0, length=this.cssRules.length; i < length; i++) {
|
|
887
932
|
cssTexts.push(this.cssRules[i].cssText);
|
|
888
933
|
}
|
|
889
|
-
return "@container " + this.
|
|
934
|
+
return "@container " + this.conditionText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
|
|
890
935
|
},
|
|
891
936
|
configurable: true,
|
|
892
937
|
enumerable: true
|
|
893
|
-
}
|
|
938
|
+
},
|
|
939
|
+
"containerName": {
|
|
940
|
+
get: function() {
|
|
941
|
+
var parts = this.conditionText.trim().split(/\s+/);
|
|
942
|
+
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
|
|
943
|
+
return parts[0];
|
|
944
|
+
}
|
|
945
|
+
return "";
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
"containerQuery": {
|
|
949
|
+
get: function() {
|
|
950
|
+
var parts = this.conditionText.trim().split(/\s+/);
|
|
951
|
+
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
|
|
952
|
+
return parts.slice(1).join(' ');
|
|
953
|
+
}
|
|
954
|
+
return this.conditionText;
|
|
955
|
+
}
|
|
956
|
+
},
|
|
894
957
|
});
|
|
895
958
|
|
|
896
959
|
|
|
@@ -933,21 +996,25 @@ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
|
|
|
933
996
|
*/
|
|
934
997
|
CSSOM.CSSImportRule = function CSSImportRule() {
|
|
935
998
|
CSSOM.CSSRule.call(this);
|
|
936
|
-
this.
|
|
937
|
-
this.
|
|
938
|
-
this.
|
|
939
|
-
this.
|
|
940
|
-
this.
|
|
999
|
+
this.__href = "";
|
|
1000
|
+
this.__media = new CSSOM.MediaList();
|
|
1001
|
+
this.__layerName = null;
|
|
1002
|
+
this.__supportsText = null;
|
|
1003
|
+
this.__styleSheet = new CSSOM.CSSStyleSheet();
|
|
941
1004
|
};
|
|
942
1005
|
|
|
943
1006
|
CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
|
|
944
1007
|
CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
|
|
945
|
-
|
|
1008
|
+
|
|
1009
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
|
|
1010
|
+
value: 3,
|
|
1011
|
+
writable: false
|
|
1012
|
+
});
|
|
946
1013
|
|
|
947
1014
|
Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
948
1015
|
get: function() {
|
|
949
1016
|
var mediaText = this.media.mediaText;
|
|
950
|
-
return "@import url(\"" + this.href + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
|
|
1017
|
+
return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
|
|
951
1018
|
},
|
|
952
1019
|
set: function(cssText) {
|
|
953
1020
|
var i = 0;
|
|
@@ -966,8 +1033,40 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
966
1033
|
|
|
967
1034
|
var layerRegExp = /layer\(([^)]*)\)/;
|
|
968
1035
|
var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
|
|
969
|
-
var supportsRegExp = /supports\(([^)]+)\)/;
|
|
970
1036
|
var doubleOrMoreSpacesRegExp = /\s{2,}/g;
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Extracts the content inside supports() handling nested parentheses.
|
|
1040
|
+
* @param {string} text - The text to parse
|
|
1041
|
+
* @returns {object|null} - {content: string, endIndex: number} or null if not found
|
|
1042
|
+
*/
|
|
1043
|
+
function extractSupportsContent(text) {
|
|
1044
|
+
var supportsIndex = text.indexOf('supports(');
|
|
1045
|
+
if (supportsIndex !== 0) {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
var depth = 0;
|
|
1050
|
+
var start = supportsIndex + 'supports('.length;
|
|
1051
|
+
var i = start;
|
|
1052
|
+
|
|
1053
|
+
for (; i < text.length; i++) {
|
|
1054
|
+
if (text[i] === '(') {
|
|
1055
|
+
depth++;
|
|
1056
|
+
} else if (text[i] === ')') {
|
|
1057
|
+
if (depth === 0) {
|
|
1058
|
+
// Found the closing parenthesis for supports()
|
|
1059
|
+
return {
|
|
1060
|
+
content: text.slice(start, i),
|
|
1061
|
+
endIndex: i
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
depth--;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return null; // Unbalanced parentheses
|
|
1069
|
+
}
|
|
971
1070
|
|
|
972
1071
|
for (var character; (character = cssText.charAt(i)); i++) {
|
|
973
1072
|
|
|
@@ -1008,7 +1107,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1008
1107
|
url = url.slice(1, -1);
|
|
1009
1108
|
}
|
|
1010
1109
|
}
|
|
1011
|
-
this.
|
|
1110
|
+
this.__href = url;
|
|
1012
1111
|
i = index;
|
|
1013
1112
|
state = 'media';
|
|
1014
1113
|
}
|
|
@@ -1020,7 +1119,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1020
1119
|
if (!index) {
|
|
1021
1120
|
throw i + ": '\"' not found";
|
|
1022
1121
|
}
|
|
1023
|
-
this.
|
|
1122
|
+
this.__href = cssText.slice(i + 1, index);
|
|
1024
1123
|
i = index;
|
|
1025
1124
|
state = 'media';
|
|
1026
1125
|
}
|
|
@@ -1032,7 +1131,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1032
1131
|
if (!index) {
|
|
1033
1132
|
throw i + ': "\'" not found';
|
|
1034
1133
|
}
|
|
1035
|
-
this.
|
|
1134
|
+
this.__href = cssText.slice(i + 1, index);
|
|
1036
1135
|
i = index;
|
|
1037
1136
|
state = 'media';
|
|
1038
1137
|
}
|
|
@@ -1050,7 +1149,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1050
1149
|
var layerName = layerMatch[1].trim();
|
|
1051
1150
|
|
|
1052
1151
|
if (layerName.match(layerRuleNameRegExp) !== null) {
|
|
1053
|
-
this.
|
|
1152
|
+
this.__layerName = layerMatch[1].trim();
|
|
1054
1153
|
bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
|
|
1055
1154
|
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
|
|
1056
1155
|
.trim();
|
|
@@ -1063,19 +1162,19 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1063
1162
|
}
|
|
1064
1163
|
}
|
|
1065
1164
|
} else {
|
|
1066
|
-
this.
|
|
1165
|
+
this.__layerName = "";
|
|
1067
1166
|
bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
|
|
1068
1167
|
}
|
|
1069
1168
|
}
|
|
1070
1169
|
|
|
1071
|
-
var
|
|
1170
|
+
var supportsResult = extractSupportsContent(bufferTrimmed);
|
|
1072
1171
|
|
|
1073
|
-
if (
|
|
1172
|
+
if (supportsResult) {
|
|
1074
1173
|
// REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
|
|
1075
|
-
this.
|
|
1076
|
-
|
|
1077
|
-
.
|
|
1078
|
-
.trim();
|
|
1174
|
+
this.__supportsText = supportsResult.content.trim();
|
|
1175
|
+
// Remove the entire supports(...) from the buffer
|
|
1176
|
+
bufferTrimmed = bufferTrimmed.slice(0, 0) + bufferTrimmed.slice(supportsResult.endIndex + 1);
|
|
1177
|
+
bufferTrimmed = bufferTrimmed.replace(doubleOrMoreSpacesRegExp, ' ').trim();
|
|
1079
1178
|
}
|
|
1080
1179
|
|
|
1081
1180
|
// REVIEW: In the browser, any invalid media is replaced with 'not all'
|
|
@@ -1096,6 +1195,43 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1096
1195
|
}
|
|
1097
1196
|
});
|
|
1098
1197
|
|
|
1198
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
|
|
1199
|
+
get: function() {
|
|
1200
|
+
return this.__href;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
|
|
1205
|
+
get: function() {
|
|
1206
|
+
return this.__media;
|
|
1207
|
+
},
|
|
1208
|
+
set: function(value) {
|
|
1209
|
+
if (typeof value === "string") {
|
|
1210
|
+
this.__media.mediaText = value;
|
|
1211
|
+
} else {
|
|
1212
|
+
this.__media = value;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
|
|
1218
|
+
get: function() {
|
|
1219
|
+
return this.__layerName;
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
|
|
1224
|
+
get: function() {
|
|
1225
|
+
return this.__supportsText;
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
|
|
1230
|
+
get: function() {
|
|
1231
|
+
return this.__styleSheet;
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1099
1235
|
|
|
1100
1236
|
|
|
1101
1237
|
|
|
@@ -1107,23 +1243,25 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
|
1107
1243
|
*/
|
|
1108
1244
|
CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
|
|
1109
1245
|
CSSOM.CSSRule.call(this);
|
|
1110
|
-
this.
|
|
1111
|
-
this.
|
|
1112
|
-
this.styleSheet = new CSSOM.CSSStyleSheet();
|
|
1246
|
+
this.__prefix = "";
|
|
1247
|
+
this.__namespaceURI = "";
|
|
1113
1248
|
};
|
|
1114
1249
|
|
|
1115
1250
|
CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
|
|
1116
1251
|
CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
|
|
1117
|
-
|
|
1252
|
+
|
|
1253
|
+
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
|
|
1254
|
+
value: 10,
|
|
1255
|
+
writable: false
|
|
1256
|
+
});
|
|
1118
1257
|
|
|
1119
1258
|
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
|
|
1120
1259
|
get: function() {
|
|
1121
1260
|
return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
|
|
1122
1261
|
},
|
|
1123
1262
|
set: function(cssText) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
this.namespaceURI = "";
|
|
1263
|
+
var newPrefix = "";
|
|
1264
|
+
var newNamespaceURI = "";
|
|
1127
1265
|
|
|
1128
1266
|
// Remove @namespace and trim
|
|
1129
1267
|
var text = cssText.trim();
|
|
@@ -1146,26 +1284,40 @@ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
|
|
|
1146
1284
|
if (match) {
|
|
1147
1285
|
// If prefix is present
|
|
1148
1286
|
if (match[1]) {
|
|
1149
|
-
|
|
1287
|
+
newPrefix = match[1];
|
|
1150
1288
|
}
|
|
1151
1289
|
// If url(...) form with quotes
|
|
1152
1290
|
if (typeof match[3] !== "undefined") {
|
|
1153
|
-
|
|
1291
|
+
newNamespaceURI = match[3];
|
|
1154
1292
|
}
|
|
1155
1293
|
// If url(...) form without quotes
|
|
1156
1294
|
else if (typeof match[4] !== "undefined") {
|
|
1157
|
-
|
|
1295
|
+
newNamespaceURI = match[4].trim();
|
|
1158
1296
|
}
|
|
1159
1297
|
// If quoted string form
|
|
1160
1298
|
else if (typeof match[6] !== "undefined") {
|
|
1161
|
-
|
|
1299
|
+
newNamespaceURI = match[6];
|
|
1162
1300
|
}
|
|
1301
|
+
|
|
1302
|
+
this.__prefix = newPrefix;
|
|
1303
|
+
this.__namespaceURI = newNamespaceURI;
|
|
1163
1304
|
} else {
|
|
1164
1305
|
throw new DOMException("Invalid @namespace rule", "InvalidStateError");
|
|
1165
1306
|
}
|
|
1166
1307
|
}
|
|
1167
1308
|
});
|
|
1168
1309
|
|
|
1310
|
+
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "prefix", {
|
|
1311
|
+
get: function() {
|
|
1312
|
+
return this.__prefix;
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "namespaceURI", {
|
|
1317
|
+
get: function() {
|
|
1318
|
+
return this.__namespaceURI;
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1169
1321
|
|
|
1170
1322
|
|
|
1171
1323
|
|
|
@@ -1219,7 +1371,7 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
|
|
|
1219
1371
|
*/
|
|
1220
1372
|
CSSOM.CSSHostRule = function CSSHostRule() {
|
|
1221
1373
|
CSSOM.CSSRule.call(this);
|
|
1222
|
-
this.cssRules =
|
|
1374
|
+
this.cssRules = new CSSOM.CSSRuleList();
|
|
1223
1375
|
};
|
|
1224
1376
|
|
|
1225
1377
|
CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule();
|
|
@@ -1249,11 +1401,10 @@ Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", {
|
|
|
1249
1401
|
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
|
|
1250
1402
|
*/
|
|
1251
1403
|
CSSOM.CSSStartingStyleRule = function CSSStartingStyleRule() {
|
|
1252
|
-
CSSOM.
|
|
1253
|
-
this.cssRules = [];
|
|
1404
|
+
CSSOM.CSSGroupingRule.call(this);
|
|
1254
1405
|
};
|
|
1255
1406
|
|
|
1256
|
-
CSSOM.CSSStartingStyleRule.prototype = new CSSOM.
|
|
1407
|
+
CSSOM.CSSStartingStyleRule.prototype = new CSSOM.CSSGroupingRule();
|
|
1257
1408
|
CSSOM.CSSStartingStyleRule.prototype.constructor = CSSOM.CSSStartingStyleRule;
|
|
1258
1409
|
CSSOM.CSSStartingStyleRule.prototype.type = 1002;
|
|
1259
1410
|
//FIXME
|
|
@@ -1280,10 +1431,23 @@ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
|
|
|
1280
1431
|
* @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
|
|
1281
1432
|
*/
|
|
1282
1433
|
CSSOM.StyleSheet = function StyleSheet() {
|
|
1434
|
+
this.__media = new CSSOM.MediaList();
|
|
1283
1435
|
this.__parentStyleSheet = null;
|
|
1284
1436
|
};
|
|
1285
1437
|
|
|
1286
1438
|
Object.defineProperties(CSSOM.StyleSheet.prototype, {
|
|
1439
|
+
media: {
|
|
1440
|
+
get: function() {
|
|
1441
|
+
return this.__media;
|
|
1442
|
+
},
|
|
1443
|
+
set: function(value) {
|
|
1444
|
+
if (typeof value === "string") {
|
|
1445
|
+
this.__media.mediaText = value;
|
|
1446
|
+
} else {
|
|
1447
|
+
this.__media = value;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1287
1451
|
parentStyleSheet: {
|
|
1288
1452
|
get: function() {
|
|
1289
1453
|
return this.__parentStyleSheet;
|
|
@@ -1301,13 +1465,18 @@ Object.defineProperties(CSSOM.StyleSheet.prototype, {
|
|
|
1301
1465
|
*/
|
|
1302
1466
|
CSSOM.CSSStyleSheet = function CSSStyleSheet() {
|
|
1303
1467
|
CSSOM.StyleSheet.call(this);
|
|
1304
|
-
this.cssRules =
|
|
1468
|
+
this.cssRules = new CSSOM.CSSRuleList();
|
|
1305
1469
|
};
|
|
1306
1470
|
|
|
1307
1471
|
|
|
1308
1472
|
CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
|
|
1309
1473
|
CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
|
|
1310
1474
|
|
|
1475
|
+
Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
|
|
1476
|
+
get: function() {
|
|
1477
|
+
return this.cssRules;
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1311
1480
|
|
|
1312
1481
|
/**
|
|
1313
1482
|
* Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
|
|
@@ -1341,7 +1510,10 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
|
|
|
1341
1510
|
}
|
|
1342
1511
|
|
|
1343
1512
|
var ruleToParse = String(rule);
|
|
1344
|
-
var
|
|
1513
|
+
var parseErrors = [];
|
|
1514
|
+
var parsedSheet = CSSOM.parse(ruleToParse, undefined, function(err) {
|
|
1515
|
+
parseErrors.push(err);
|
|
1516
|
+
} );
|
|
1345
1517
|
if (parsedSheet.cssRules.length !== 1) {
|
|
1346
1518
|
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
|
|
1347
1519
|
}
|
|
@@ -1409,12 +1581,21 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
|
|
|
1409
1581
|
'HierarchyRequestError');
|
|
1410
1582
|
}
|
|
1411
1583
|
|
|
1584
|
+
// Cannot insert if there are already non-special rules
|
|
1585
|
+
if (firstNonImportNamespaceIndex < this.cssRules.length) {
|
|
1586
|
+
errorUtils.throwError(this, 'DOMException',
|
|
1587
|
+
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
|
|
1588
|
+
'InvalidStateError');
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1412
1591
|
// Cannot insert after other types of rules
|
|
1413
1592
|
if (index > firstNonImportNamespaceIndex) {
|
|
1414
1593
|
errorUtils.throwError(this, 'DOMException',
|
|
1415
1594
|
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
|
|
1416
1595
|
'HierarchyRequestError');
|
|
1417
1596
|
}
|
|
1597
|
+
|
|
1598
|
+
|
|
1418
1599
|
} else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
|
|
1419
1600
|
// @layer statement rules can be inserted anywhere before @import and @namespace
|
|
1420
1601
|
// No additional restrictions beyond what's already handled
|
|
@@ -1431,6 +1612,10 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
|
|
|
1431
1612
|
"Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
|
|
1432
1613
|
'HierarchyRequestError');
|
|
1433
1614
|
}
|
|
1615
|
+
|
|
1616
|
+
if (parseErrors.length !== 0) {
|
|
1617
|
+
errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
|
|
1618
|
+
}
|
|
1434
1619
|
}
|
|
1435
1620
|
|
|
1436
1621
|
cssRule.__parentStyleSheet = this;
|
|
@@ -1438,6 +1623,13 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
|
|
|
1438
1623
|
return index;
|
|
1439
1624
|
};
|
|
1440
1625
|
|
|
1626
|
+
CSSOM.CSSStyleSheet.prototype.addRule = function(selector, styleBlock, index) {
|
|
1627
|
+
if (index === void 0) {
|
|
1628
|
+
index = this.cssRules.length;
|
|
1629
|
+
}
|
|
1630
|
+
this.insertRule(selector + "{" + styleBlock + "}", index);
|
|
1631
|
+
return -1;
|
|
1632
|
+
};
|
|
1441
1633
|
|
|
1442
1634
|
/**
|
|
1443
1635
|
* Used to delete a rule from the style sheet.
|
|
@@ -1463,17 +1655,27 @@ CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
|
|
|
1463
1655
|
if (index >= this.cssRules.length) {
|
|
1464
1656
|
errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
|
|
1465
1657
|
}
|
|
1466
|
-
if (this.cssRules[index]
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1658
|
+
if (this.cssRules[index]) {
|
|
1659
|
+
if (this.cssRules[index].constructor.name == "CSSNamespaceRule") {
|
|
1660
|
+
var shouldContinue = this.cssRules.every(function (rule) {
|
|
1661
|
+
return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
|
|
1662
|
+
});
|
|
1663
|
+
if (!shouldContinue) {
|
|
1664
|
+
errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
|
|
1665
|
+
}
|
|
1472
1666
|
}
|
|
1667
|
+
if (this.cssRules[index].constructor.name == "CSSImportRule") {
|
|
1668
|
+
this.cssRules[index].styleSheet.__parentStyleSheet = null;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
this.cssRules[index].__parentStyleSheet = null;
|
|
1473
1672
|
}
|
|
1474
1673
|
this.cssRules.splice(index, 1);
|
|
1475
1674
|
};
|
|
1476
1675
|
|
|
1676
|
+
CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
|
|
1677
|
+
this.deleteRule(index);
|
|
1678
|
+
};
|
|
1477
1679
|
|
|
1478
1680
|
/**
|
|
1479
1681
|
* NON-STANDARD
|
|
@@ -1500,7 +1702,7 @@ CSSOM.CSSStyleSheet.prototype.toString = function() {
|
|
|
1500
1702
|
CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
|
|
1501
1703
|
CSSOM.CSSRule.call(this);
|
|
1502
1704
|
this.name = '';
|
|
1503
|
-
this.cssRules =
|
|
1705
|
+
this.cssRules = new CSSOM.CSSRuleList();
|
|
1504
1706
|
|
|
1505
1707
|
// Set up initial indexed access
|
|
1506
1708
|
this._setupIndexedAccess();
|
|
@@ -1826,7 +2028,7 @@ CSSOM.MatcherList.prototype = {
|
|
|
1826
2028
|
CSSOM.CSSDocumentRule = function CSSDocumentRule() {
|
|
1827
2029
|
CSSOM.CSSRule.call(this);
|
|
1828
2030
|
this.matcher = new CSSOM.MatcherList();
|
|
1829
|
-
this.cssRules =
|
|
2031
|
+
this.cssRules = new CSSOM.CSSRuleList();
|
|
1830
2032
|
};
|
|
1831
2033
|
|
|
1832
2034
|
CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule();
|
|
@@ -2227,6 +2429,51 @@ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
|
|
|
2227
2429
|
|
|
2228
2430
|
|
|
2229
2431
|
|
|
2432
|
+
/**
|
|
2433
|
+
* @constructor
|
|
2434
|
+
* @see https://drafts.csswg.org/css-cascade-6/#cssscoperule
|
|
2435
|
+
*/
|
|
2436
|
+
CSSOM.CSSScopeRule = function CSSScopeRule() {
|
|
2437
|
+
CSSOM.CSSGroupingRule.call(this);
|
|
2438
|
+
this.__start = null;
|
|
2439
|
+
this.__end = null;
|
|
2440
|
+
};
|
|
2441
|
+
|
|
2442
|
+
CSSOM.CSSScopeRule.prototype = new CSSOM.CSSGroupingRule();
|
|
2443
|
+
CSSOM.CSSScopeRule.prototype.constructor = CSSOM.CSSScopeRule;
|
|
2444
|
+
|
|
2445
|
+
|
|
2446
|
+
Object.defineProperties(CSSOM.CSSScopeRule.prototype, {
|
|
2447
|
+
type: {
|
|
2448
|
+
value: 0,
|
|
2449
|
+
writable: false,
|
|
2450
|
+
},
|
|
2451
|
+
cssText: {
|
|
2452
|
+
get: function () {
|
|
2453
|
+
var cssTexts = [];
|
|
2454
|
+
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
2455
|
+
cssTexts.push(this.cssRules[i].cssText);
|
|
2456
|
+
}
|
|
2457
|
+
return "@scope " + (this.start ? "(" + this.start + ") " : "") + (this.end ? "to (" + this.end + ") " : "") + "{" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
|
|
2458
|
+
},
|
|
2459
|
+
configurable: true,
|
|
2460
|
+
enumerable: true,
|
|
2461
|
+
},
|
|
2462
|
+
start: {
|
|
2463
|
+
get: function () {
|
|
2464
|
+
return this.__start;
|
|
2465
|
+
}
|
|
2466
|
+
},
|
|
2467
|
+
end: {
|
|
2468
|
+
get: function () {
|
|
2469
|
+
return this.__end;
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
|
|
2474
|
+
|
|
2475
|
+
|
|
2476
|
+
|
|
2230
2477
|
/**
|
|
2231
2478
|
* @constructor
|
|
2232
2479
|
* @see https://drafts.csswg.org/css-cascade-5/#csslayerblockrule
|
|
@@ -2234,7 +2481,6 @@ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
|
|
|
2234
2481
|
CSSOM.CSSLayerBlockRule = function CSSLayerBlockRule() {
|
|
2235
2482
|
CSSOM.CSSGroupingRule.call(this);
|
|
2236
2483
|
this.name = "";
|
|
2237
|
-
this.cssRules = [];
|
|
2238
2484
|
};
|
|
2239
2485
|
|
|
2240
2486
|
CSSOM.CSSLayerBlockRule.prototype = new CSSOM.CSSGroupingRule();
|
|
@@ -2286,63 +2532,325 @@ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
|
|
|
2286
2532
|
|
|
2287
2533
|
|
|
2288
2534
|
/**
|
|
2289
|
-
*
|
|
2290
|
-
*
|
|
2291
|
-
* @param {string} token - The CSS string to parse.
|
|
2292
|
-
* @param {object} [opts] - Optional parsing options.
|
|
2293
|
-
* @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
|
|
2294
|
-
* @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
|
|
2295
|
-
* @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
|
|
2535
|
+
* @constructor
|
|
2536
|
+
* @see https://drafts.csswg.org/cssom/#the-csspagerule-interface
|
|
2296
2537
|
*/
|
|
2297
|
-
CSSOM.
|
|
2298
|
-
|
|
2538
|
+
CSSOM.CSSPageRule = function CSSPageRule() {
|
|
2539
|
+
CSSOM.CSSGroupingRule.call(this);
|
|
2540
|
+
this.__style = new CSSOM.CSSStyleDeclaration();
|
|
2541
|
+
this.__style.parentRule = this;
|
|
2542
|
+
};
|
|
2299
2543
|
|
|
2300
|
-
|
|
2544
|
+
CSSOM.CSSPageRule.prototype = new CSSOM.CSSGroupingRule();
|
|
2545
|
+
CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
|
|
2301
2546
|
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2547
|
+
Object.defineProperty(CSSOM.CSSPageRule.prototype, "type", {
|
|
2548
|
+
value: 6,
|
|
2549
|
+
writable: false
|
|
2550
|
+
});
|
|
2551
|
+
|
|
2552
|
+
Object.defineProperty(CSSOM.CSSPageRule.prototype, "selectorText", {
|
|
2553
|
+
get: function() {
|
|
2554
|
+
return this.__selectorText;
|
|
2555
|
+
},
|
|
2556
|
+
set: function(value) {
|
|
2557
|
+
if (typeof value === "string") {
|
|
2558
|
+
var trimmedValue = value.trim();
|
|
2559
|
+
|
|
2560
|
+
// Empty selector is valid for @page
|
|
2561
|
+
if (trimmedValue === '') {
|
|
2562
|
+
this.__selectorText = '';
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// Parse @page selectorText for page name and pseudo-pages
|
|
2567
|
+
// Valid formats:
|
|
2568
|
+
// - (empty - no name, no pseudo-page)
|
|
2569
|
+
// - :left, :right, :first, :blank (pseudo-page only)
|
|
2570
|
+
// - named (named page only)
|
|
2571
|
+
// - named:first (named page with single pseudo-page)
|
|
2572
|
+
// - named:first:left (named page with multiple pseudo-pages)
|
|
2573
|
+
var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/;
|
|
2574
|
+
var match = trimmedValue.match(atPageRuleSelectorRegExp);
|
|
2575
|
+
if (match) {
|
|
2576
|
+
var pageName = match[1] || '';
|
|
2577
|
+
var pseudoPages = match[2] || '';
|
|
2578
|
+
|
|
2579
|
+
// Validate page name if present
|
|
2580
|
+
if (pageName) {
|
|
2581
|
+
var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
|
|
2582
|
+
// Page name can be an identifier or a string
|
|
2583
|
+
if (!cssCustomIdentifierRegExp.test(pageName)) {
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// Validate pseudo-pages if present
|
|
2589
|
+
if (pseudoPages) {
|
|
2590
|
+
var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
|
|
2591
|
+
var validPseudos = ['left', 'right', 'first', 'blank'];
|
|
2592
|
+
var allValid = true;
|
|
2593
|
+
for (var j = 0; j < pseudos.length; j++) {
|
|
2594
|
+
if (validPseudos.indexOf(pseudos[j].toLowerCase()) === -1) {
|
|
2595
|
+
allValid = false;
|
|
2596
|
+
break;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
if (!allValid) {
|
|
2601
|
+
return; // Invalid pseudo-page, do nothing
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
this.__selectorText = pageName + pseudoPages.toLowerCase();
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
});
|
|
2610
|
+
|
|
2611
|
+
Object.defineProperty(CSSOM.CSSPageRule.prototype, "style", {
|
|
2612
|
+
get: function() {
|
|
2613
|
+
return this.__style;
|
|
2614
|
+
},
|
|
2615
|
+
set: function(value) {
|
|
2616
|
+
if (typeof value === "string") {
|
|
2617
|
+
this.__style.cssText = value;
|
|
2618
|
+
} else {
|
|
2619
|
+
this.__style = value;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
});
|
|
2623
|
+
|
|
2624
|
+
Object.defineProperty(CSSOM.CSSPageRule.prototype, "cssText", {
|
|
2625
|
+
get: function() {
|
|
2626
|
+
var values = ""
|
|
2627
|
+
if (this.cssRules.length) {
|
|
2628
|
+
var valuesArr = [" {"];
|
|
2629
|
+
this.style.cssText && valuesArr.push(this.style.cssText);
|
|
2630
|
+
valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
|
|
2631
|
+
values = valuesArr.join("\n ") + "\n}"
|
|
2632
|
+
} else {
|
|
2633
|
+
values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
|
|
2634
|
+
}
|
|
2635
|
+
return "@page" + (this.selectorText ? " " + this.selectorText : "") + values;
|
|
2636
|
+
},
|
|
2637
|
+
set: function(cssText) {
|
|
2638
|
+
if (typeof value === "string") {
|
|
2639
|
+
var rule = CSSOM.CSSPageRule.parse(cssText);
|
|
2640
|
+
this.__style = rule.style;
|
|
2641
|
+
this.selectorText = rule.selectorText;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
});
|
|
2314
2645
|
|
|
2646
|
+
/**
|
|
2647
|
+
* NON-STANDARD
|
|
2648
|
+
* lightweight version of parse.js.
|
|
2649
|
+
* @param {string} ruleText
|
|
2650
|
+
* @return CSSPageRule
|
|
2651
|
+
*/
|
|
2652
|
+
CSSOM.CSSPageRule.parse = function(ruleText) {
|
|
2653
|
+
var i = 0;
|
|
2654
|
+
var state = "selector";
|
|
2315
2655
|
var index;
|
|
2656
|
+
var j = i;
|
|
2316
2657
|
var buffer = "";
|
|
2317
|
-
var valueParenthesisDepth = 0;
|
|
2318
2658
|
|
|
2319
2659
|
var SIGNIFICANT_WHITESPACE = {
|
|
2320
|
-
"name": true,
|
|
2321
|
-
"before-name": true,
|
|
2322
2660
|
"selector": true,
|
|
2323
|
-
"value": true
|
|
2324
|
-
"value-parenthesis": true,
|
|
2325
|
-
"atRule": true,
|
|
2326
|
-
"importRule-begin": true,
|
|
2327
|
-
"importRule": true,
|
|
2328
|
-
"namespaceRule-begin": true,
|
|
2329
|
-
"namespaceRule": true,
|
|
2330
|
-
"atBlock": true,
|
|
2331
|
-
"containerBlock": true,
|
|
2332
|
-
"conditionBlock": true,
|
|
2333
|
-
"counterStyleBlock": true,
|
|
2334
|
-
'documentRule-begin': true,
|
|
2335
|
-
"layerBlock": true
|
|
2661
|
+
"value": true
|
|
2336
2662
|
};
|
|
2337
2663
|
|
|
2338
|
-
var
|
|
2664
|
+
var pageRule = new CSSOM.CSSPageRule();
|
|
2665
|
+
var name, priority="";
|
|
2339
2666
|
|
|
2340
|
-
|
|
2341
|
-
styleSheet.__globalObject = opts.globalObject;
|
|
2342
|
-
}
|
|
2667
|
+
for (var character; (character = ruleText.charAt(i)); i++) {
|
|
2343
2668
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2669
|
+
switch (character) {
|
|
2670
|
+
|
|
2671
|
+
case " ":
|
|
2672
|
+
case "\t":
|
|
2673
|
+
case "\r":
|
|
2674
|
+
case "\n":
|
|
2675
|
+
case "\f":
|
|
2676
|
+
if (SIGNIFICANT_WHITESPACE[state]) {
|
|
2677
|
+
// Squash 2 or more white-spaces in the row into 1
|
|
2678
|
+
switch (ruleText.charAt(i - 1)) {
|
|
2679
|
+
case " ":
|
|
2680
|
+
case "\t":
|
|
2681
|
+
case "\r":
|
|
2682
|
+
case "\n":
|
|
2683
|
+
case "\f":
|
|
2684
|
+
break;
|
|
2685
|
+
default:
|
|
2686
|
+
buffer += " ";
|
|
2687
|
+
break;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
break;
|
|
2691
|
+
|
|
2692
|
+
// String
|
|
2693
|
+
case '"':
|
|
2694
|
+
j = i + 1;
|
|
2695
|
+
index = ruleText.indexOf('"', j) + 1;
|
|
2696
|
+
if (!index) {
|
|
2697
|
+
throw '" is missing';
|
|
2698
|
+
}
|
|
2699
|
+
buffer += ruleText.slice(i, index);
|
|
2700
|
+
i = index - 1;
|
|
2701
|
+
break;
|
|
2702
|
+
|
|
2703
|
+
case "'":
|
|
2704
|
+
j = i + 1;
|
|
2705
|
+
index = ruleText.indexOf("'", j) + 1;
|
|
2706
|
+
if (!index) {
|
|
2707
|
+
throw "' is missing";
|
|
2708
|
+
}
|
|
2709
|
+
buffer += ruleText.slice(i, index);
|
|
2710
|
+
i = index - 1;
|
|
2711
|
+
break;
|
|
2712
|
+
|
|
2713
|
+
// Comment
|
|
2714
|
+
case "/":
|
|
2715
|
+
if (ruleText.charAt(i + 1) === "*") {
|
|
2716
|
+
i += 2;
|
|
2717
|
+
index = ruleText.indexOf("*/", i);
|
|
2718
|
+
if (index === -1) {
|
|
2719
|
+
throw new SyntaxError("Missing */");
|
|
2720
|
+
} else {
|
|
2721
|
+
i = index + 1;
|
|
2722
|
+
}
|
|
2723
|
+
} else {
|
|
2724
|
+
buffer += character;
|
|
2725
|
+
}
|
|
2726
|
+
break;
|
|
2727
|
+
|
|
2728
|
+
case "{":
|
|
2729
|
+
if (state === "selector") {
|
|
2730
|
+
pageRule.selectorText = buffer.trim();
|
|
2731
|
+
buffer = "";
|
|
2732
|
+
state = "name";
|
|
2733
|
+
}
|
|
2734
|
+
break;
|
|
2735
|
+
|
|
2736
|
+
case ":":
|
|
2737
|
+
if (state === "name") {
|
|
2738
|
+
name = buffer.trim();
|
|
2739
|
+
buffer = "";
|
|
2740
|
+
state = "value";
|
|
2741
|
+
} else {
|
|
2742
|
+
buffer += character;
|
|
2743
|
+
}
|
|
2744
|
+
break;
|
|
2745
|
+
|
|
2746
|
+
case "!":
|
|
2747
|
+
if (state === "value" && ruleText.indexOf("!important", i) === i) {
|
|
2748
|
+
priority = "important";
|
|
2749
|
+
i += "important".length;
|
|
2750
|
+
} else {
|
|
2751
|
+
buffer += character;
|
|
2752
|
+
}
|
|
2753
|
+
break;
|
|
2754
|
+
|
|
2755
|
+
case ";":
|
|
2756
|
+
if (state === "value") {
|
|
2757
|
+
pageRule.style.setProperty(name, buffer.trim(), priority);
|
|
2758
|
+
priority = "";
|
|
2759
|
+
buffer = "";
|
|
2760
|
+
state = "name";
|
|
2761
|
+
} else {
|
|
2762
|
+
buffer += character;
|
|
2763
|
+
}
|
|
2764
|
+
break;
|
|
2765
|
+
|
|
2766
|
+
case "}":
|
|
2767
|
+
if (state === "value") {
|
|
2768
|
+
pageRule.style.setProperty(name, buffer.trim(), priority);
|
|
2769
|
+
priority = "";
|
|
2770
|
+
buffer = "";
|
|
2771
|
+
} else if (state === "name") {
|
|
2772
|
+
break;
|
|
2773
|
+
} else {
|
|
2774
|
+
buffer += character;
|
|
2775
|
+
}
|
|
2776
|
+
state = "selector";
|
|
2777
|
+
break;
|
|
2778
|
+
|
|
2779
|
+
default:
|
|
2780
|
+
buffer += character;
|
|
2781
|
+
break;
|
|
2782
|
+
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
return pageRule;
|
|
2787
|
+
|
|
2788
|
+
};
|
|
2789
|
+
|
|
2790
|
+
|
|
2791
|
+
|
|
2792
|
+
|
|
2793
|
+
|
|
2794
|
+
/**
|
|
2795
|
+
* Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
|
|
2796
|
+
*
|
|
2797
|
+
* @param {string} token - The CSS string to parse.
|
|
2798
|
+
* @param {object} [opts] - Optional parsing options.
|
|
2799
|
+
* @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
|
|
2800
|
+
* @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
|
|
2801
|
+
* @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
|
|
2802
|
+
*/
|
|
2803
|
+
CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
2804
|
+
errorHandler = errorHandler === true ? (console && console.error) : errorHandler;
|
|
2805
|
+
|
|
2806
|
+
var i = 0;
|
|
2807
|
+
|
|
2808
|
+
/**
|
|
2809
|
+
"before-selector" or
|
|
2810
|
+
"selector" or
|
|
2811
|
+
"atRule" or
|
|
2812
|
+
"atBlock" or
|
|
2813
|
+
"conditionBlock" or
|
|
2814
|
+
"before-name" or
|
|
2815
|
+
"name" or
|
|
2816
|
+
"before-value" or
|
|
2817
|
+
"value"
|
|
2818
|
+
*/
|
|
2819
|
+
var state = "before-selector";
|
|
2820
|
+
|
|
2821
|
+
var index;
|
|
2822
|
+
var buffer = "";
|
|
2823
|
+
var valueParenthesisDepth = 0;
|
|
2824
|
+
|
|
2825
|
+
var SIGNIFICANT_WHITESPACE = {
|
|
2826
|
+
"name": true,
|
|
2827
|
+
"before-name": true,
|
|
2828
|
+
"selector": true,
|
|
2829
|
+
"value": true,
|
|
2830
|
+
"value-parenthesis": true,
|
|
2831
|
+
"atRule": true,
|
|
2832
|
+
"importRule-begin": true,
|
|
2833
|
+
"importRule": true,
|
|
2834
|
+
"namespaceRule-begin": true,
|
|
2835
|
+
"namespaceRule": true,
|
|
2836
|
+
"atBlock": true,
|
|
2837
|
+
"containerBlock": true,
|
|
2838
|
+
"conditionBlock": true,
|
|
2839
|
+
"counterStyleBlock": true,
|
|
2840
|
+
'documentRule-begin': true,
|
|
2841
|
+
"scopeBlock": true,
|
|
2842
|
+
"layerBlock": true,
|
|
2843
|
+
"pageBlock": true
|
|
2844
|
+
};
|
|
2845
|
+
|
|
2846
|
+
var styleSheet = new CSSOM.CSSStyleSheet();
|
|
2847
|
+
|
|
2848
|
+
if (opts && opts.globalObject) {
|
|
2849
|
+
styleSheet.__globalObject = opts.globalObject;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
|
2853
|
+
var currentScope = styleSheet;
|
|
2346
2854
|
|
|
2347
2855
|
// @type CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
|
|
2348
2856
|
var parentRule;
|
|
@@ -2350,7 +2858,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2350
2858
|
var ancestorRules = [];
|
|
2351
2859
|
var prevScope;
|
|
2352
2860
|
|
|
2353
|
-
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
2861
|
+
var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, pageRule, layerBlockRule, layerStatementRule, nestedSelectorRule, namespaceRule;
|
|
2354
2862
|
|
|
2355
2863
|
// Track defined namespace prefixes for validation
|
|
2356
2864
|
var definedNamespacePrefixes = {};
|
|
@@ -2360,11 +2868,13 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2360
2868
|
// var atRulesStatemenRegExp = /(?<!{.*)[;}]\s*/; // Match a statement by verifying it finds a semicolon or closing brace not followed by another semicolon or closing brace
|
|
2361
2869
|
var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
|
|
2362
2870
|
var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
|
|
2363
|
-
var forwardRuleValidationRegExp = /(?:\(
|
|
2871
|
+
var forwardRuleValidationRegExp = /(?:\s|\/\*|\{|\()/; // Match that the rule is followed by any whitespace, a opening comment, a condition opening parenthesis or a opening brace
|
|
2364
2872
|
var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
|
|
2365
2873
|
var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
|
|
2366
2874
|
var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
|
|
2367
|
-
var
|
|
2875
|
+
var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
|
|
2876
|
+
var startsWithCombinatorRegExp = /^\s*[>+~]/; // Checks if a selector starts with a CSS combinator (>, +, ~)
|
|
2877
|
+
var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/;
|
|
2368
2878
|
|
|
2369
2879
|
/**
|
|
2370
2880
|
* Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
|
|
@@ -2456,24 +2966,214 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2456
2966
|
return i;
|
|
2457
2967
|
}
|
|
2458
2968
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
} catch(e) {
|
|
2472
|
-
errorHandler && errorHandler(e);
|
|
2969
|
+
/**
|
|
2970
|
+
* Parses the scope prelude and extracts start and end selectors.
|
|
2971
|
+
* @param {string} preludeContent - The scope prelude content (without @scope keyword)
|
|
2972
|
+
* @returns {object} Object with startSelector and endSelector properties
|
|
2973
|
+
*/
|
|
2974
|
+
function parseScopePrelude(preludeContent) {
|
|
2975
|
+
var parts = preludeContent.split(/\s*\)\s*to\s+\(/);
|
|
2976
|
+
|
|
2977
|
+
// Restore the parentheses that were consumed by the split
|
|
2978
|
+
if (parts.length === 2) {
|
|
2979
|
+
parts[0] = parts[0] + ')';
|
|
2980
|
+
parts[1] = '(' + parts[1];
|
|
2473
2981
|
}
|
|
2982
|
+
|
|
2983
|
+
var hasStart = parts[0] &&
|
|
2984
|
+
parts[0].charAt(0) === '(' &&
|
|
2985
|
+
parts[0].charAt(parts[0].length - 1) === ')';
|
|
2986
|
+
var hasEnd = parts[1] &&
|
|
2987
|
+
parts[1].charAt(0) === '(' &&
|
|
2988
|
+
parts[1].charAt(parts[1].length - 1) === ')';
|
|
2989
|
+
|
|
2990
|
+
// Handle case: @scope to (<end>)
|
|
2991
|
+
var hasOnlyEnd = !hasStart &&
|
|
2992
|
+
!hasEnd &&
|
|
2993
|
+
parts[0].indexOf('to (') === 0 &&
|
|
2994
|
+
parts[0].charAt(parts[0].length - 1) === ')';
|
|
2995
|
+
|
|
2996
|
+
var startSelector = '';
|
|
2997
|
+
var endSelector = '';
|
|
2998
|
+
|
|
2999
|
+
if (hasStart) {
|
|
3000
|
+
startSelector = parts[0].slice(1, -1).trim();
|
|
3001
|
+
}
|
|
3002
|
+
if (hasEnd) {
|
|
3003
|
+
endSelector = parts[1].slice(1, -1).trim();
|
|
3004
|
+
}
|
|
3005
|
+
if (hasOnlyEnd) {
|
|
3006
|
+
endSelector = parts[0].slice(4, -1).trim();
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
return {
|
|
3010
|
+
startSelector: startSelector,
|
|
3011
|
+
endSelector: endSelector,
|
|
3012
|
+
hasStart: hasStart,
|
|
3013
|
+
hasEnd: hasEnd,
|
|
3014
|
+
hasOnlyEnd: hasOnlyEnd
|
|
3015
|
+
};
|
|
2474
3016
|
};
|
|
2475
3017
|
|
|
2476
|
-
|
|
3018
|
+
/**
|
|
3019
|
+
* Checks if a selector contains pseudo-elements.
|
|
3020
|
+
* @param {string} selector - The CSS selector to check
|
|
3021
|
+
* @returns {boolean} True if the selector contains pseudo-elements
|
|
3022
|
+
*/
|
|
3023
|
+
function hasPseudoElement(selector) {
|
|
3024
|
+
// Match only double-colon (::) pseudo-elements
|
|
3025
|
+
// Also match legacy single-colon pseudo-elements: :before, :after, :first-line, :first-letter
|
|
3026
|
+
// These must NOT be followed by alphanumeric characters (to avoid matching :before-x or similar)
|
|
3027
|
+
var pseudoElementRegex = /::[a-zA-Z][\w-]*|:(before|after|first-line|first-letter)(?![a-zA-Z0-9_-])/;
|
|
3028
|
+
return pseudoElementRegex.test(selector);
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
/**
|
|
3032
|
+
* Validates balanced parentheses, brackets, and quotes in a selector.
|
|
3033
|
+
*
|
|
3034
|
+
* @param {string} selector - The CSS selector to validate
|
|
3035
|
+
* @param {boolean} trackAttributes - Whether to track attribute selector context
|
|
3036
|
+
* @param {boolean} useStack - Whether to use a stack for parentheses (needed for nested validation)
|
|
3037
|
+
* @returns {boolean} True if the syntax is valid (all brackets, parentheses, and quotes are balanced)
|
|
3038
|
+
*/
|
|
3039
|
+
function validateBalancedSyntax(selector, trackAttributes, useStack) {
|
|
3040
|
+
var parenDepth = 0;
|
|
3041
|
+
var bracketDepth = 0;
|
|
3042
|
+
var inSingleQuote = false;
|
|
3043
|
+
var inDoubleQuote = false;
|
|
3044
|
+
var inAttr = false;
|
|
3045
|
+
var stack = useStack ? [] : null;
|
|
3046
|
+
|
|
3047
|
+
for (var i = 0; i < selector.length; i++) {
|
|
3048
|
+
var char = selector[i];
|
|
3049
|
+
var prevChar = i > 0 ? selector[i - 1] : '';
|
|
3050
|
+
|
|
3051
|
+
if (inSingleQuote) {
|
|
3052
|
+
if (char === "'" && prevChar !== "\\") {
|
|
3053
|
+
inSingleQuote = false;
|
|
3054
|
+
}
|
|
3055
|
+
} else if (inDoubleQuote) {
|
|
3056
|
+
if (char === '"' && prevChar !== "\\") {
|
|
3057
|
+
inDoubleQuote = false;
|
|
3058
|
+
}
|
|
3059
|
+
} else if (trackAttributes && inAttr) {
|
|
3060
|
+
if (char === "]") {
|
|
3061
|
+
inAttr = false;
|
|
3062
|
+
} else if (char === "'") {
|
|
3063
|
+
inSingleQuote = true;
|
|
3064
|
+
} else if (char === '"') {
|
|
3065
|
+
inDoubleQuote = true;
|
|
3066
|
+
}
|
|
3067
|
+
} else {
|
|
3068
|
+
if (trackAttributes && char === "[") {
|
|
3069
|
+
inAttr = true;
|
|
3070
|
+
} else if (char === "'") {
|
|
3071
|
+
inSingleQuote = true;
|
|
3072
|
+
} else if (char === '"') {
|
|
3073
|
+
inDoubleQuote = true;
|
|
3074
|
+
} else if (char === '(') {
|
|
3075
|
+
if (useStack) {
|
|
3076
|
+
stack.push("(");
|
|
3077
|
+
} else {
|
|
3078
|
+
parenDepth++;
|
|
3079
|
+
}
|
|
3080
|
+
} else if (char === ')') {
|
|
3081
|
+
if (useStack) {
|
|
3082
|
+
if (!stack.length || stack.pop() !== "(") {
|
|
3083
|
+
return false;
|
|
3084
|
+
}
|
|
3085
|
+
} else {
|
|
3086
|
+
parenDepth--;
|
|
3087
|
+
if (parenDepth < 0) {
|
|
3088
|
+
return false;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
} else if (char === '[') {
|
|
3092
|
+
bracketDepth++;
|
|
3093
|
+
} else if (char === ']') {
|
|
3094
|
+
bracketDepth--;
|
|
3095
|
+
if (bracketDepth < 0) {
|
|
3096
|
+
return false;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// Check if everything is balanced
|
|
3103
|
+
if (useStack) {
|
|
3104
|
+
return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
|
|
3105
|
+
} else {
|
|
3106
|
+
return parenDepth === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote;
|
|
3107
|
+
}
|
|
3108
|
+
};
|
|
3109
|
+
|
|
3110
|
+
/**
|
|
3111
|
+
* Checks for basic syntax errors in selectors (mismatched parentheses, brackets, quotes).
|
|
3112
|
+
* @param {string} selector - The CSS selector to check
|
|
3113
|
+
* @returns {boolean} True if there are syntax errors
|
|
3114
|
+
*/
|
|
3115
|
+
function hasBasicSyntaxError(selector) {
|
|
3116
|
+
return !validateBalancedSyntax(selector, false, false);
|
|
3117
|
+
};
|
|
3118
|
+
|
|
3119
|
+
/**
|
|
3120
|
+
* Checks for invalid combinator patterns in selectors.
|
|
3121
|
+
* @param {string} selector - The CSS selector to check
|
|
3122
|
+
* @returns {boolean} True if the selector contains invalid combinators
|
|
3123
|
+
*/
|
|
3124
|
+
function hasInvalidCombinators(selector) {
|
|
3125
|
+
// Check for invalid combinator patterns:
|
|
3126
|
+
// - <> (not a valid combinator)
|
|
3127
|
+
// - >> (deep descendant combinator, deprecated and invalid)
|
|
3128
|
+
// - Multiple consecutive combinators like >>, >~, etc.
|
|
3129
|
+
if (/<>/.test(selector)) return true;
|
|
3130
|
+
if (/>>/.test(selector)) return true;
|
|
3131
|
+
// Check for other invalid consecutive combinator patterns
|
|
3132
|
+
if (/[>+~]\s*[>+~]/.test(selector)) return true;
|
|
3133
|
+
return false;
|
|
3134
|
+
};
|
|
3135
|
+
|
|
3136
|
+
/**
|
|
3137
|
+
* Checks for invalid pseudo-like syntax (function calls without proper pseudo prefix).
|
|
3138
|
+
* @param {string} selector - The CSS selector to check
|
|
3139
|
+
* @returns {boolean} True if the selector contains invalid pseudo-like syntax
|
|
3140
|
+
*/
|
|
3141
|
+
function hasInvalidPseudoSyntax(selector) {
|
|
3142
|
+
// Check for specific known pseudo-elements used without : or :: prefix
|
|
3143
|
+
// Examples: slotted(div), part(name), cue(selector)
|
|
3144
|
+
// These are ONLY valid as ::slotted(), ::part(), ::cue()
|
|
3145
|
+
var invalidPatterns = [
|
|
3146
|
+
/(?:^|[\s>+~,\[])slotted\s*\(/i,
|
|
3147
|
+
/(?:^|[\s>+~,\[])part\s*\(/i,
|
|
3148
|
+
/(?:^|[\s>+~,\[])cue\s*\(/i,
|
|
3149
|
+
/(?:^|[\s>+~,\[])cue-region\s*\(/i
|
|
3150
|
+
];
|
|
3151
|
+
|
|
3152
|
+
for (var i = 0; i < invalidPatterns.length; i++) {
|
|
3153
|
+
if (invalidPatterns[i].test(selector)) {
|
|
3154
|
+
return true;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
return false;
|
|
3158
|
+
};
|
|
3159
|
+
|
|
3160
|
+
/**
|
|
3161
|
+
* Checks for invalid nesting selector (&) usage.
|
|
3162
|
+
* The & selector cannot be directly followed by a type selector without a delimiter.
|
|
3163
|
+
* Valid: &.class, &#id, &[attr], &:hover, &::before, & div, &>div
|
|
3164
|
+
* Invalid: &div, &span
|
|
3165
|
+
* @param {string} selector - The CSS selector to check
|
|
3166
|
+
* @returns {boolean} True if the selector contains invalid & usage
|
|
3167
|
+
*/
|
|
3168
|
+
function hasInvalidNestingSelector(selector) {
|
|
3169
|
+
// Check for & followed directly by a letter (type selector) without any delimiter
|
|
3170
|
+
// This regex matches & followed by a letter (start of type selector) that's not preceded by an escape
|
|
3171
|
+
// We need to exclude valid cases like &.class, &#id, &[attr], &:pseudo, &::pseudo, & (with space), &>
|
|
3172
|
+
var invalidNestingPattern = /&(?![.\#\[:>\+~\s])[a-zA-Z]/;
|
|
3173
|
+
return invalidNestingPattern.test(selector);
|
|
3174
|
+
};
|
|
3175
|
+
|
|
3176
|
+
function validateAtRule(atRuleKey, validCallback, cannotBeNested) {
|
|
2477
3177
|
var isValid = false;
|
|
2478
3178
|
var sourceRuleRegExp = atRuleKey === "@import" ? forwardImportRuleValidationRegExp : forwardRuleValidationRegExp;
|
|
2479
3179
|
var ruleRegExp = new RegExp(atRuleKey + sourceRuleRegExp.source, sourceRuleRegExp.flags);
|
|
@@ -2494,6 +3194,114 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2494
3194
|
isValid = true;
|
|
2495
3195
|
}
|
|
2496
3196
|
}
|
|
3197
|
+
|
|
3198
|
+
// Additional validation for @scope rule
|
|
3199
|
+
if (isValid && atRuleKey === "@scope") {
|
|
3200
|
+
var openBraceIndex = ruleSlice.indexOf('{');
|
|
3201
|
+
if (openBraceIndex !== -1) {
|
|
3202
|
+
// Extract the rule prelude (everything between the at-rule and {)
|
|
3203
|
+
var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
|
|
3204
|
+
|
|
3205
|
+
// Skip past at-rule keyword and whitespace
|
|
3206
|
+
var preludeContent = rulePrelude.slice("@scope".length).trim();
|
|
3207
|
+
|
|
3208
|
+
if (preludeContent.length > 0) {
|
|
3209
|
+
// Parse the scope prelude
|
|
3210
|
+
var parsedScopePrelude = parseScopePrelude(preludeContent);
|
|
3211
|
+
var startSelector = parsedScopePrelude.startSelector;
|
|
3212
|
+
var endSelector = parsedScopePrelude.endSelector;
|
|
3213
|
+
var hasStart = parsedScopePrelude.hasStart;
|
|
3214
|
+
var hasEnd = parsedScopePrelude.hasEnd;
|
|
3215
|
+
var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
|
|
3216
|
+
|
|
3217
|
+
// Validation rules for @scope:
|
|
3218
|
+
// 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
|
|
3219
|
+
if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
|
|
3220
|
+
isValid = false;
|
|
3221
|
+
}
|
|
3222
|
+
// 2. Pseudo-elements are invalid in scope selectors
|
|
3223
|
+
else if ((startSelector && hasPseudoElement(startSelector)) || (endSelector && hasPseudoElement(endSelector))) {
|
|
3224
|
+
isValid = false;
|
|
3225
|
+
}
|
|
3226
|
+
// 3. Basic syntax errors (mismatched parens, brackets, quotes)
|
|
3227
|
+
else if ((startSelector && hasBasicSyntaxError(startSelector)) || (endSelector && hasBasicSyntaxError(endSelector))) {
|
|
3228
|
+
isValid = false;
|
|
3229
|
+
}
|
|
3230
|
+
// 4. Invalid combinator patterns
|
|
3231
|
+
else if ((startSelector && hasInvalidCombinators(startSelector)) || (endSelector && hasInvalidCombinators(endSelector))) {
|
|
3232
|
+
isValid = false;
|
|
3233
|
+
}
|
|
3234
|
+
// 5. Invalid pseudo-like syntax (function without : or :: prefix)
|
|
3235
|
+
else if ((startSelector && hasInvalidPseudoSyntax(startSelector)) || (endSelector && hasInvalidPseudoSyntax(endSelector))) {
|
|
3236
|
+
isValid = false;
|
|
3237
|
+
}
|
|
3238
|
+
// 6. Invalid structure (no proper parentheses found when prelude is not empty)
|
|
3239
|
+
else if (!hasStart && !hasOnlyEnd) {
|
|
3240
|
+
isValid = false;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
// Empty prelude (@scope {}) is valid
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
if (isValid && atRuleKey === "@page") {
|
|
3248
|
+
var openBraceIndex = ruleSlice.indexOf('{');
|
|
3249
|
+
if (openBraceIndex !== -1) {
|
|
3250
|
+
// Extract the rule prelude (everything between the at-rule and {)
|
|
3251
|
+
var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
|
|
3252
|
+
|
|
3253
|
+
// Skip past at-rule keyword and whitespace
|
|
3254
|
+
var preludeContent = rulePrelude.slice("@page".length).trim();
|
|
3255
|
+
|
|
3256
|
+
if (preludeContent.length > 0) {
|
|
3257
|
+
var trimmedValue = preludeContent.trim();
|
|
3258
|
+
|
|
3259
|
+
// Empty selector is valid for @page
|
|
3260
|
+
if (trimmedValue !== '') {
|
|
3261
|
+
// Parse @page selectorText for page name and pseudo-pages
|
|
3262
|
+
// Valid formats:
|
|
3263
|
+
// - (empty - no name, no pseudo-page)
|
|
3264
|
+
// - :left, :right, :first, :blank (pseudo-page only)
|
|
3265
|
+
// - named (named page only)
|
|
3266
|
+
// - named:first (named page with single pseudo-page)
|
|
3267
|
+
// - named:first:left (named page with multiple pseudo-pages)
|
|
3268
|
+
var match = trimmedValue.match(atPageRuleSelectorRegExp);
|
|
3269
|
+
if (match) {
|
|
3270
|
+
var pageName = match[1] || '';
|
|
3271
|
+
var pseudoPages = match[2] || '';
|
|
3272
|
+
|
|
3273
|
+
// Validate page name if present
|
|
3274
|
+
if (pageName) {
|
|
3275
|
+
if (!cssCustomIdentifierRegExp.test(pageName)) {
|
|
3276
|
+
isValid = false;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
// Validate pseudo-pages if present
|
|
3281
|
+
if (pseudoPages) {
|
|
3282
|
+
var pseudos = pseudoPages.split(':').filter(function(p) { return p; });
|
|
3283
|
+
var validPseudos = ['left', 'right', 'first', 'blank'];
|
|
3284
|
+
var allValid = true;
|
|
3285
|
+
for (var j = 0; j < pseudos.length; j++) {
|
|
3286
|
+
if (validPseudos.indexOf(pseudos[j].toLowerCase()) === -1) {
|
|
3287
|
+
allValid = false;
|
|
3288
|
+
break;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
if (!allValid) {
|
|
3293
|
+
isValid = false;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
} else {
|
|
3297
|
+
isValid = false;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
2497
3305
|
if (!isValid) {
|
|
2498
3306
|
// If it's invalid the browser will simply ignore the entire invalid block
|
|
2499
3307
|
// Use regex to find the closing brace of the invalid rule
|
|
@@ -2552,55 +3360,47 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2552
3360
|
* @returns {boolean}
|
|
2553
3361
|
*/
|
|
2554
3362
|
function basicSelectorValidator(selector) {
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
var inSingleQuote = false;
|
|
2560
|
-
var inDoubleQuote = false;
|
|
3363
|
+
// Validate balanced syntax with attribute tracking and stack-based parentheses matching
|
|
3364
|
+
if (!validateBalancedSyntax(selector, true, true)) {
|
|
3365
|
+
return false;
|
|
3366
|
+
}
|
|
2561
3367
|
|
|
2562
|
-
|
|
2563
|
-
|
|
3368
|
+
// Check for invalid combinator patterns
|
|
3369
|
+
if (hasInvalidCombinators(selector)) {
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
2564
3372
|
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
}
|
|
2569
|
-
} else if (inDoubleQuote) {
|
|
2570
|
-
if (char === '"' && selector[i - 1] !== "\\") {
|
|
2571
|
-
inDoubleQuote = false;
|
|
2572
|
-
}
|
|
2573
|
-
} else if (inAttr) {
|
|
2574
|
-
if (char === "]") {
|
|
2575
|
-
inAttr = false;
|
|
2576
|
-
} else if (char === "'") {
|
|
2577
|
-
inSingleQuote = true;
|
|
2578
|
-
} else if (char === '"') {
|
|
2579
|
-
inDoubleQuote = true;
|
|
2580
|
-
}
|
|
2581
|
-
} else {
|
|
2582
|
-
if (char === "[") {
|
|
2583
|
-
inAttr = true;
|
|
2584
|
-
} else if (char === "'") {
|
|
2585
|
-
inSingleQuote = true;
|
|
2586
|
-
} else if (char === '"') {
|
|
2587
|
-
inDoubleQuote = true;
|
|
2588
|
-
} else if (char === "(") {
|
|
2589
|
-
stack.push("(");
|
|
2590
|
-
} else if (char === ")") {
|
|
2591
|
-
if (!stack.length || stack.pop() !== "(") {
|
|
2592
|
-
return false;
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
i++;
|
|
3373
|
+
// Check for invalid pseudo-like syntax
|
|
3374
|
+
if (hasInvalidPseudoSyntax(selector)) {
|
|
3375
|
+
return false;
|
|
2597
3376
|
}
|
|
2598
3377
|
|
|
2599
|
-
//
|
|
2600
|
-
if (
|
|
3378
|
+
// Check for invalid nesting selector (&) usage
|
|
3379
|
+
if (hasInvalidNestingSelector(selector)) {
|
|
2601
3380
|
return false;
|
|
2602
3381
|
}
|
|
2603
3382
|
|
|
3383
|
+
// Check for invalid pseudo-class usage with quoted strings
|
|
3384
|
+
// Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
|
|
3385
|
+
var pseudoPattern = /::?([a-zA-Z][\w-]*)\(([^)]+)\)/g;
|
|
3386
|
+
var pseudoMatch;
|
|
3387
|
+
while ((pseudoMatch = pseudoPattern.exec(selector)) !== null) {
|
|
3388
|
+
var pseudoName = pseudoMatch[1];
|
|
3389
|
+
var pseudoContent = pseudoMatch[2];
|
|
3390
|
+
|
|
3391
|
+
// List of pseudo-classes that should not accept quoted strings
|
|
3392
|
+
// :lang() - accepts language codes: en, fr-CA
|
|
3393
|
+
// :dir() - accepts direction: ltr, rtl
|
|
3394
|
+
// :nth-*() - accepts An+B notation: 2n+1, odd, even
|
|
3395
|
+
var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
|
|
3396
|
+
|
|
3397
|
+
for (var i = 0; i < noQuotesPseudos.length; i++) {
|
|
3398
|
+
if (pseudoName === noQuotesPseudos[i] && /['"]/.test(pseudoContent)) {
|
|
3399
|
+
return false;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
2604
3404
|
// Fallback to a loose regexp for the overall selector structure (without deep paren matching)
|
|
2605
3405
|
// This is similar to the original, but without nested paren limitations
|
|
2606
3406
|
// Modified to support namespace selectors: *|element, prefix|element, |element
|
|
@@ -2608,7 +3408,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2608
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+)+$/;
|
|
2609
3409
|
return looseSelectorRegExp.test(selector);
|
|
2610
3410
|
}
|
|
2611
|
-
|
|
3411
|
+
|
|
2612
3412
|
/**
|
|
2613
3413
|
* Regular expression to match CSS pseudo-classes with arguments.
|
|
2614
3414
|
*
|
|
@@ -2639,48 +3439,56 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2639
3439
|
* @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
|
|
2640
3440
|
*/
|
|
2641
3441
|
function parseAndSplitNestedSelectors(selector) {
|
|
2642
|
-
var depth = 0;
|
|
2643
|
-
var buffer = "";
|
|
2644
|
-
var parts = [];
|
|
2645
|
-
var inSingleQuote = false;
|
|
2646
|
-
var inDoubleQuote = false;
|
|
3442
|
+
var depth = 0; // Track parenthesis nesting depth
|
|
3443
|
+
var buffer = ""; // Accumulate characters for current selector part
|
|
3444
|
+
var parts = []; // Array of split selector parts
|
|
3445
|
+
var inSingleQuote = false; // Track if we're inside single quotes
|
|
3446
|
+
var inDoubleQuote = false; // Track if we're inside double quotes
|
|
2647
3447
|
var i, char;
|
|
2648
3448
|
|
|
2649
3449
|
for (i = 0; i < selector.length; i++) {
|
|
2650
3450
|
char = selector.charAt(i);
|
|
2651
3451
|
|
|
3452
|
+
// Handle single quote strings
|
|
2652
3453
|
if (char === "'" && !inDoubleQuote) {
|
|
2653
3454
|
inSingleQuote = !inSingleQuote;
|
|
2654
3455
|
buffer += char;
|
|
2655
|
-
}
|
|
3456
|
+
}
|
|
3457
|
+
// Handle double quote strings
|
|
3458
|
+
else if (char === '"' && !inSingleQuote) {
|
|
2656
3459
|
inDoubleQuote = !inDoubleQuote;
|
|
2657
3460
|
buffer += char;
|
|
2658
|
-
}
|
|
3461
|
+
}
|
|
3462
|
+
// Process characters outside of quoted strings
|
|
3463
|
+
else if (!inSingleQuote && !inDoubleQuote) {
|
|
2659
3464
|
if (char === '(') {
|
|
3465
|
+
// Entering a nested level (e.g., :is(...))
|
|
2660
3466
|
depth++;
|
|
2661
3467
|
buffer += char;
|
|
2662
3468
|
} else if (char === ')') {
|
|
3469
|
+
// Exiting a nested level
|
|
2663
3470
|
depth--;
|
|
2664
3471
|
buffer += char;
|
|
2665
|
-
if (depth === 0) {
|
|
2666
|
-
parts.push(buffer.replace(/^\s+|\s+$/g, ""));
|
|
2667
|
-
buffer = "";
|
|
2668
|
-
}
|
|
2669
3472
|
} else if (char === ',' && depth === 0) {
|
|
2670
|
-
|
|
2671
|
-
|
|
3473
|
+
// Found a top-level comma separator - split here
|
|
3474
|
+
if (buffer.trim()) {
|
|
3475
|
+
parts.push(buffer.trim());
|
|
2672
3476
|
}
|
|
2673
3477
|
buffer = "";
|
|
2674
3478
|
} else {
|
|
3479
|
+
// Regular character - add to buffer
|
|
2675
3480
|
buffer += char;
|
|
2676
3481
|
}
|
|
2677
|
-
}
|
|
3482
|
+
}
|
|
3483
|
+
// Characters inside quoted strings - add to buffer
|
|
3484
|
+
else {
|
|
2678
3485
|
buffer += char;
|
|
2679
3486
|
}
|
|
2680
3487
|
}
|
|
2681
3488
|
|
|
2682
|
-
|
|
2683
|
-
|
|
3489
|
+
// Add any remaining content in buffer as the last part
|
|
3490
|
+
if (buffer.trim()) {
|
|
3491
|
+
parts.push(buffer.trim());
|
|
2684
3492
|
}
|
|
2685
3493
|
|
|
2686
3494
|
return parts;
|
|
@@ -2697,8 +3505,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2697
3505
|
* @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
|
|
2698
3506
|
*/
|
|
2699
3507
|
|
|
2700
|
-
// Cache to store validated selectors
|
|
2701
|
-
var validatedSelectorsCache =
|
|
3508
|
+
// Cache to store validated selectors (previously a ES6 Map, now an ES5-compliant object)
|
|
3509
|
+
var validatedSelectorsCache = {};
|
|
2702
3510
|
|
|
2703
3511
|
// Only pseudo-classes that accept selector lists should recurse
|
|
2704
3512
|
var selectorListPseudoClasses = {
|
|
@@ -2709,8 +3517,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2709
3517
|
};
|
|
2710
3518
|
|
|
2711
3519
|
function validateSelector(selector) {
|
|
2712
|
-
if (validatedSelectorsCache.
|
|
2713
|
-
return validatedSelectorsCache
|
|
3520
|
+
if (validatedSelectorsCache.hasOwnProperty(selector)) {
|
|
3521
|
+
return validatedSelectorsCache[selector];
|
|
2714
3522
|
}
|
|
2715
3523
|
|
|
2716
3524
|
// Use a non-global regex to find all pseudo-classes with arguments
|
|
@@ -2727,15 +3535,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2727
3535
|
var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
|
|
2728
3536
|
for (var i = 0; i < nestedSelectors.length; i++) {
|
|
2729
3537
|
var nestedSelector = nestedSelectors[i];
|
|
2730
|
-
if (!validatedSelectorsCache.
|
|
3538
|
+
if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
|
|
2731
3539
|
var nestedSelectorValidation = validateSelector(nestedSelector);
|
|
2732
|
-
validatedSelectorsCache
|
|
3540
|
+
validatedSelectorsCache[nestedSelector] = nestedSelectorValidation;
|
|
2733
3541
|
if (!nestedSelectorValidation) {
|
|
2734
|
-
validatedSelectorsCache
|
|
3542
|
+
validatedSelectorsCache[selector] = false;
|
|
2735
3543
|
return false;
|
|
2736
3544
|
}
|
|
2737
|
-
} else if (!validatedSelectorsCache
|
|
2738
|
-
validatedSelectorsCache
|
|
3545
|
+
} else if (!validatedSelectorsCache[nestedSelector]) {
|
|
3546
|
+
validatedSelectorsCache[selector] = false;
|
|
2739
3547
|
return false;
|
|
2740
3548
|
}
|
|
2741
3549
|
}
|
|
@@ -2743,7 +3551,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2743
3551
|
}
|
|
2744
3552
|
|
|
2745
3553
|
var basicSelectorValidation = basicSelectorValidator(selector);
|
|
2746
|
-
validatedSelectorsCache
|
|
3554
|
+
validatedSelectorsCache[selector] = basicSelectorValidation;
|
|
2747
3555
|
|
|
2748
3556
|
return basicSelectorValidation;
|
|
2749
3557
|
}
|
|
@@ -2807,6 +3615,28 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2807
3615
|
return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
|
|
2808
3616
|
}
|
|
2809
3617
|
|
|
3618
|
+
/**
|
|
3619
|
+
* Processes a CSS selector text
|
|
3620
|
+
*
|
|
3621
|
+
* @param {string} selectorText - The CSS selector text to process
|
|
3622
|
+
* @returns {string} The processed selector text with normalized whitespace
|
|
3623
|
+
*/
|
|
3624
|
+
function processSelectorText(selectorText) {
|
|
3625
|
+
// TODO: Remove invalid selectors that appears inside pseudo classes
|
|
3626
|
+
// TODO: The same processing here needs to be reused in CSSStyleRule.selectorText setter
|
|
3627
|
+
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
3628
|
+
|
|
3629
|
+
/**
|
|
3630
|
+
* Normalizes whitespace and preserving quoted strings.
|
|
3631
|
+
* Replaces all newline characters (CRLF, CR, or LF) with spaces while keeping quoted
|
|
3632
|
+
* strings (single or double quotes) intact, including any escaped characters within them.
|
|
3633
|
+
*/
|
|
3634
|
+
return selectorText.replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
|
|
3635
|
+
if (newline) return " ";
|
|
3636
|
+
return match;
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
|
|
2810
3640
|
/**
|
|
2811
3641
|
* Checks if a given CSS selector text is valid by splitting it by commas
|
|
2812
3642
|
* and validating each individual selector using the `validateSelector` function.
|
|
@@ -2815,6 +3645,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2815
3645
|
* @returns {boolean} Returns true if all selectors are valid, otherwise false.
|
|
2816
3646
|
*/
|
|
2817
3647
|
function isValidSelectorText(selectorText) {
|
|
3648
|
+
// TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
|
|
3649
|
+
// TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
|
|
3650
|
+
|
|
2818
3651
|
// Check for newlines inside single or double quotes using regex
|
|
2819
3652
|
// This matches any quoted string (single or double) containing a newline
|
|
2820
3653
|
var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
|
|
@@ -2835,6 +3668,23 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2835
3668
|
return true;
|
|
2836
3669
|
}
|
|
2837
3670
|
|
|
3671
|
+
function parseError(message) {
|
|
3672
|
+
var lines = token.substring(0, i).split('\n');
|
|
3673
|
+
var lineCount = lines.length;
|
|
3674
|
+
var charCount = lines.pop().length + 1;
|
|
3675
|
+
var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
|
|
3676
|
+
error.line = lineCount;
|
|
3677
|
+
/* jshint sub : true */
|
|
3678
|
+
error['char'] = charCount;
|
|
3679
|
+
error.styleSheet = styleSheet;
|
|
3680
|
+
// Print the error but continue parsing the sheet
|
|
3681
|
+
try {
|
|
3682
|
+
throw error;
|
|
3683
|
+
} catch(e) {
|
|
3684
|
+
errorHandler && errorHandler(e);
|
|
3685
|
+
}
|
|
3686
|
+
};
|
|
3687
|
+
|
|
2838
3688
|
var endingIndex = token.length - 1;
|
|
2839
3689
|
|
|
2840
3690
|
for (var character; (character = token.charAt(i)); i++) {
|
|
@@ -2924,7 +3774,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2924
3774
|
i += 2;
|
|
2925
3775
|
index = token.indexOf("*/", i);
|
|
2926
3776
|
if (index === -1) {
|
|
2927
|
-
|
|
3777
|
+
i = token.length - 1;
|
|
3778
|
+
buffer = "";
|
|
2928
3779
|
} else {
|
|
2929
3780
|
i = index + 1;
|
|
2930
3781
|
}
|
|
@@ -2979,6 +3830,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2979
3830
|
}, true);
|
|
2980
3831
|
buffer = "";
|
|
2981
3832
|
break;
|
|
3833
|
+
} else if (token.indexOf("@scope", i) === i) {
|
|
3834
|
+
validateAtRule("@scope", function(){
|
|
3835
|
+
state = "scopeBlock";
|
|
3836
|
+
scopeRule = new CSSOM.CSSScopeRule();
|
|
3837
|
+
scopeRule.__starts = i;
|
|
3838
|
+
i += "scope".length;
|
|
3839
|
+
});
|
|
3840
|
+
buffer = "";
|
|
3841
|
+
break;
|
|
2982
3842
|
} else if (token.indexOf("@layer", i) === i) {
|
|
2983
3843
|
validateAtRule("@layer", function(){
|
|
2984
3844
|
state = "layerBlock"
|
|
@@ -2988,6 +3848,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
2988
3848
|
});
|
|
2989
3849
|
buffer = "";
|
|
2990
3850
|
break;
|
|
3851
|
+
} else if (token.indexOf("@page", i) === i) {
|
|
3852
|
+
validateAtRule("@page", function(){
|
|
3853
|
+
state = "pageBlock"
|
|
3854
|
+
pageRule = new CSSOM.CSSPageRule();
|
|
3855
|
+
pageRule.__starts = i;
|
|
3856
|
+
i += "page".length;
|
|
3857
|
+
});
|
|
3858
|
+
buffer = "";
|
|
3859
|
+
break;
|
|
2991
3860
|
} else if (token.indexOf("@supports", i) === i) {
|
|
2992
3861
|
validateAtRule("@supports", function(){
|
|
2993
3862
|
state = "conditionBlock";
|
|
@@ -3085,10 +3954,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3085
3954
|
}
|
|
3086
3955
|
|
|
3087
3956
|
currentScope = parentRule = styleRule;
|
|
3088
|
-
styleRule.selectorText = buffer.trim()
|
|
3089
|
-
if (newline) return " ";
|
|
3090
|
-
return match;
|
|
3091
|
-
});
|
|
3957
|
+
styleRule.selectorText = processSelectorText(buffer.trim());
|
|
3092
3958
|
styleRule.style.__starts = i;
|
|
3093
3959
|
styleRule.__parentStyleSheet = styleSheet;
|
|
3094
3960
|
buffer = "";
|
|
@@ -3106,7 +3972,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3106
3972
|
buffer = "";
|
|
3107
3973
|
state = "before-selector";
|
|
3108
3974
|
} else if (state === "containerBlock") {
|
|
3109
|
-
containerRule.
|
|
3975
|
+
containerRule.__conditionText = buffer.trim();
|
|
3110
3976
|
|
|
3111
3977
|
if (parentRule) {
|
|
3112
3978
|
containerRule.__parentRule = parentRule;
|
|
@@ -3123,7 +3989,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3123
3989
|
counterStyleRule.__parentStyleSheet = styleSheet;
|
|
3124
3990
|
buffer = "";
|
|
3125
3991
|
} else if (state === "conditionBlock") {
|
|
3126
|
-
supportsRule.
|
|
3992
|
+
supportsRule.__conditionText = buffer.trim();
|
|
3127
3993
|
|
|
3128
3994
|
if (parentRule) {
|
|
3129
3995
|
supportsRule.__parentRule = parentRule;
|
|
@@ -3134,10 +4000,31 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3134
4000
|
supportsRule.__parentStyleSheet = styleSheet;
|
|
3135
4001
|
buffer = "";
|
|
3136
4002
|
state = "before-selector";
|
|
4003
|
+
} else if (state === "scopeBlock") {
|
|
4004
|
+
var parsedScopePrelude = parseScopePrelude(buffer.trim());
|
|
4005
|
+
|
|
4006
|
+
if (parsedScopePrelude.hasStart) {
|
|
4007
|
+
scopeRule.__start = parsedScopePrelude.startSelector;
|
|
4008
|
+
}
|
|
4009
|
+
if (parsedScopePrelude.hasEnd) {
|
|
4010
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
4011
|
+
}
|
|
4012
|
+
if (parsedScopePrelude.hasOnlyEnd) {
|
|
4013
|
+
scopeRule.__end = parsedScopePrelude.endSelector;
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
if (parentRule) {
|
|
4017
|
+
scopeRule.__parentRule = parentRule;
|
|
4018
|
+
ancestorRules.push(parentRule);
|
|
4019
|
+
}
|
|
4020
|
+
currentScope = parentRule = scopeRule;
|
|
4021
|
+
scopeRule.__parentStyleSheet = styleSheet;
|
|
4022
|
+
buffer = "";
|
|
4023
|
+
state = "before-selector";
|
|
3137
4024
|
} else if (state === "layerBlock") {
|
|
3138
4025
|
layerBlockRule.name = buffer.trim();
|
|
3139
4026
|
|
|
3140
|
-
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(
|
|
4027
|
+
var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
|
|
3141
4028
|
|
|
3142
4029
|
if (isValidName) {
|
|
3143
4030
|
if (parentRule) {
|
|
@@ -3150,6 +4037,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3150
4037
|
}
|
|
3151
4038
|
buffer = "";
|
|
3152
4039
|
state = "before-selector";
|
|
4040
|
+
} else if (state === "pageBlock") {
|
|
4041
|
+
pageRule.selectorText = buffer.trim();
|
|
4042
|
+
|
|
4043
|
+
if (parentRule) {
|
|
4044
|
+
pageRule.__parentRule = parentRule;
|
|
4045
|
+
ancestorRules.push(parentRule);
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
currentScope = parentRule = pageRule;
|
|
4049
|
+
pageRule.__parentStyleSheet = styleSheet;
|
|
4050
|
+
styleRule = pageRule;
|
|
4051
|
+
buffer = "";
|
|
4052
|
+
state = "before-name";
|
|
3153
4053
|
} else if (state === "hostRule-begin") {
|
|
3154
4054
|
if (parentRule) {
|
|
3155
4055
|
ancestorRules.push(parentRule);
|
|
@@ -3223,17 +4123,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3223
4123
|
}
|
|
3224
4124
|
|
|
3225
4125
|
styleRule = new CSSOM.CSSStyleRule();
|
|
3226
|
-
var processedSelectorText = buffer.trim()
|
|
3227
|
-
if (newline) return " ";
|
|
3228
|
-
return match;
|
|
3229
|
-
});
|
|
4126
|
+
var processedSelectorText = processSelectorText(buffer.trim());
|
|
3230
4127
|
// In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
|
|
3231
4128
|
if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
|
|
3232
4129
|
styleRule.selectorText = processedSelectorText;
|
|
3233
4130
|
} else {
|
|
3234
4131
|
styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function(sel) {
|
|
3235
|
-
|
|
3236
|
-
|
|
4132
|
+
// Add & at the beginning if there's no & in the selector, or if it starts with a combinator
|
|
4133
|
+
return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
|
|
4134
|
+
}).join(', ');
|
|
3237
4135
|
}
|
|
3238
4136
|
styleRule.style.__starts = i - buffer.length;
|
|
3239
4137
|
styleRule.__parentRule = parentRule;
|
|
@@ -3349,7 +4247,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3349
4247
|
testNamespaceRule.cssText = buffer + character;
|
|
3350
4248
|
|
|
3351
4249
|
namespaceRule = testNamespaceRule;
|
|
3352
|
-
namespaceRule.__parentStyleSheet =
|
|
4250
|
+
namespaceRule.__parentStyleSheet = styleSheet;
|
|
3353
4251
|
styleSheet.cssRules.push(namespaceRule);
|
|
3354
4252
|
|
|
3355
4253
|
// Track the namespace prefix for validation
|
|
@@ -3368,7 +4266,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3368
4266
|
return name.trim();
|
|
3369
4267
|
});
|
|
3370
4268
|
var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
|
|
3371
|
-
return name.trim().match(
|
|
4269
|
+
return name.trim().match(cssCustomIdentifierRegExp) === null;
|
|
3372
4270
|
});
|
|
3373
4271
|
|
|
3374
4272
|
if (!isInvalid) {
|
|
@@ -3469,6 +4367,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3469
4367
|
|| parentRule.constructor.name === "CSSMediaRule"
|
|
3470
4368
|
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
3471
4369
|
|| parentRule.constructor.name === "CSSContainerRule"
|
|
4370
|
+
|| parentRule.constructor.name === "CSSScopeRule"
|
|
3472
4371
|
|| parentRule.constructor.name === "CSSLayerBlockRule"
|
|
3473
4372
|
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
|
3474
4373
|
) {
|
|
@@ -3538,6 +4437,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3538
4437
|
|| parentRule.constructor.name === "CSSMediaRule"
|
|
3539
4438
|
|| parentRule.constructor.name === "CSSSupportsRule"
|
|
3540
4439
|
|| parentRule.constructor.name === "CSSContainerRule"
|
|
4440
|
+
|| parentRule.constructor.name === "CSSScopeRule"
|
|
3541
4441
|
|| parentRule.constructor.name === "CSSLayerBlockRule"
|
|
3542
4442
|
|| parentRule.constructor.name === "CSSStartingStyleRule"
|
|
3543
4443
|
) {
|
|
@@ -3593,6 +4493,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
|
|
|
3593
4493
|
}
|
|
3594
4494
|
}
|
|
3595
4495
|
|
|
4496
|
+
if (buffer.trim() !== "") {
|
|
4497
|
+
parseError("Unexpected end of input");
|
|
4498
|
+
}
|
|
4499
|
+
|
|
3596
4500
|
return styleSheet;
|
|
3597
4501
|
};
|
|
3598
4502
|
|