@acemir/cssom 0.9.20 → 0.9.22

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 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
@@ -547,12 +553,17 @@ CSSOM.CSSCounterStyleRule.prototype.type = 11;
547
553
  */
548
554
  CSSOM.CSSConditionRule = function CSSConditionRule() {
549
555
  CSSOM.CSSGroupingRule.call(this);
556
+ this.__conditionText = '';
550
557
  };
551
558
 
552
559
  CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
553
560
  CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
554
- CSSOM.CSSConditionRule.prototype.conditionText = ''
555
- CSSOM.CSSConditionRule.prototype.cssText = ''
561
+
562
+ Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
563
+ get: function () {
564
+ return this.__conditionText;
565
+ }
566
+ });
556
567
 
557
568
 
558
569
 
@@ -583,7 +594,20 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "selectorText", {
583
594
  return this.__selectorText;
584
595
  },
585
596
  set: function(value) {
586
- this.__selectorText = value;
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
+ }
587
611
  }
588
612
  });
589
613
 
@@ -620,9 +644,11 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
620
644
  return text;
621
645
  },
622
646
  set: function(cssText) {
623
- var rule = CSSOM.CSSStyleRule.parse(cssText);
624
- this.__style = rule.style;
625
- this.__selectorText = rule.selectorText;
647
+ if (typeof cssText === "string") {
648
+ var rule = CSSOM.CSSStyleRule.parse(cssText);
649
+ this.__style = rule.style;
650
+ this.selectorText = rule.selectorText;
651
+ }
626
652
  }
627
653
  });
628
654
 
@@ -840,7 +866,7 @@ CSSOM.MediaList.prototype = {
840
866
  */
841
867
  CSSOM.CSSMediaRule = function CSSMediaRule() {
842
868
  CSSOM.CSSConditionRule.call(this);
843
- this.media = new CSSOM.MediaList();
869
+ this.__media = new CSSOM.MediaList();
844
870
  };
845
871
 
846
872
  CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
@@ -849,16 +875,24 @@ CSSOM.CSSMediaRule.prototype.type = 4;
849
875
 
850
876
  // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
851
877
  Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
852
- "conditionText": {
878
+ "media": {
853
879
  get: function() {
854
- return this.media.mediaText;
880
+ return this.__media;
855
881
  },
856
882
  set: function(value) {
857
- this.media.mediaText = value;
883
+ if (typeof value === "string") {
884
+ this.__media.mediaText = value;
885
+ } else {
886
+ this.__media = value;
887
+ }
858
888
  },
859
- configurable: true,
860
889
  enumerable: true
861
890
  },
891
+ "conditionText": {
892
+ get: function() {
893
+ return this.media.mediaText;
894
+ }
895
+ },
862
896
  "cssText": {
863
897
  get: function() {
864
898
  var cssTexts = [];
@@ -891,27 +925,35 @@ CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
891
925
  CSSOM.CSSContainerRule.prototype.type = 17;
892
926
 
893
927
  Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
894
- "conditionText": {
895
- get: function() {
896
- return this.containerText;
897
- },
898
- set: function(value) {
899
- this.containerText = value;
900
- },
901
- configurable: true,
902
- enumerable: true
903
- },
904
928
  "cssText": {
905
929
  get: function() {
906
930
  var cssTexts = [];
907
931
  for (var i=0, length=this.cssRules.length; i < length; i++) {
908
932
  cssTexts.push(this.cssRules[i].cssText);
909
933
  }
910
- return "@container " + this.containerText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
934
+ return "@container " + this.conditionText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
911
935
  },
912
936
  configurable: true,
913
937
  enumerable: true
914
- }
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
+ },
915
957
  });
916
958
 
917
959
 
@@ -954,21 +996,25 @@ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
954
996
  */
955
997
  CSSOM.CSSImportRule = function CSSImportRule() {
956
998
  CSSOM.CSSRule.call(this);
957
- this.href = "";
958
- this.media = new CSSOM.MediaList();
959
- this.layerName = null;
960
- this.supportsText = null;
961
- this.styleSheet = new CSSOM.CSSStyleSheet();
999
+ this.__href = "";
1000
+ this.__media = new CSSOM.MediaList();
1001
+ this.__layerName = null;
1002
+ this.__supportsText = null;
1003
+ this.__styleSheet = new CSSOM.CSSStyleSheet();
962
1004
  };
963
1005
 
964
1006
  CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
965
1007
  CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
966
- CSSOM.CSSImportRule.prototype.type = 3;
1008
+
1009
+ Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
1010
+ value: 3,
1011
+ writable: false
1012
+ });
967
1013
 
968
1014
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
969
1015
  get: function() {
970
1016
  var mediaText = this.media.mediaText;
971
- 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 : "") + ";";
972
1018
  },
973
1019
  set: function(cssText) {
974
1020
  var i = 0;
@@ -987,8 +1033,40 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
987
1033
 
988
1034
  var layerRegExp = /layer\(([^)]*)\)/;
989
1035
  var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
990
- var supportsRegExp = /supports\(([^)]+)\)/;
991
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
+ }
992
1070
 
993
1071
  for (var character; (character = cssText.charAt(i)); i++) {
994
1072
 
@@ -1029,7 +1107,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1029
1107
  url = url.slice(1, -1);
1030
1108
  }
1031
1109
  }
1032
- this.href = url;
1110
+ this.__href = url;
1033
1111
  i = index;
1034
1112
  state = 'media';
1035
1113
  }
@@ -1041,7 +1119,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1041
1119
  if (!index) {
1042
1120
  throw i + ": '\"' not found";
1043
1121
  }
1044
- this.href = cssText.slice(i + 1, index);
1122
+ this.__href = cssText.slice(i + 1, index);
1045
1123
  i = index;
1046
1124
  state = 'media';
1047
1125
  }
@@ -1053,7 +1131,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1053
1131
  if (!index) {
1054
1132
  throw i + ': "\'" not found';
1055
1133
  }
1056
- this.href = cssText.slice(i + 1, index);
1134
+ this.__href = cssText.slice(i + 1, index);
1057
1135
  i = index;
1058
1136
  state = 'media';
1059
1137
  }
@@ -1071,7 +1149,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1071
1149
  var layerName = layerMatch[1].trim();
1072
1150
 
1073
1151
  if (layerName.match(layerRuleNameRegExp) !== null) {
1074
- this.layerName = layerMatch[1].trim();
1152
+ this.__layerName = layerMatch[1].trim();
1075
1153
  bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
1076
1154
  .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1077
1155
  .trim();
@@ -1084,19 +1162,19 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1084
1162
  }
1085
1163
  }
1086
1164
  } else {
1087
- this.layerName = "";
1165
+ this.__layerName = "";
1088
1166
  bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
1089
1167
  }
1090
1168
  }
1091
1169
 
1092
- var supportsMatch = bufferTrimmed.match(supportsRegExp);
1170
+ var supportsResult = extractSupportsContent(bufferTrimmed);
1093
1171
 
1094
- if (supportsMatch && supportsMatch.index === 0) {
1172
+ if (supportsResult) {
1095
1173
  // REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
1096
- this.supportsText = supportsMatch[1].trim();
1097
- bufferTrimmed = bufferTrimmed.replace(supportsRegExp, '')
1098
- .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1099
- .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();
1100
1178
  }
1101
1179
 
1102
1180
  // REVIEW: In the browser, any invalid media is replaced with 'not all'
@@ -1117,6 +1195,43 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1117
1195
  }
1118
1196
  });
1119
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
+
1120
1235
 
1121
1236
 
1122
1237
 
@@ -1128,23 +1243,25 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1128
1243
  */
1129
1244
  CSSOM.CSSNamespaceRule = function CSSNamespaceRule() {
1130
1245
  CSSOM.CSSRule.call(this);
1131
- this.prefix = "";
1132
- this.namespaceURI = "";
1133
- this.styleSheet = new CSSOM.CSSStyleSheet();
1246
+ this.__prefix = "";
1247
+ this.__namespaceURI = "";
1134
1248
  };
1135
1249
 
1136
1250
  CSSOM.CSSNamespaceRule.prototype = new CSSOM.CSSRule();
1137
1251
  CSSOM.CSSNamespaceRule.prototype.constructor = CSSOM.CSSNamespaceRule;
1138
- CSSOM.CSSNamespaceRule.prototype.type = 10;
1252
+
1253
+ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "type", {
1254
+ value: 10,
1255
+ writable: false
1256
+ });
1139
1257
 
1140
1258
  Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1141
1259
  get: function() {
1142
1260
  return "@namespace" + (this.prefix && " " + this.prefix) + " url(\"" + this.namespaceURI + "\");";
1143
1261
  },
1144
1262
  set: function(cssText) {
1145
- // Reset prefix and namespaceURI
1146
- this.prefix = "";
1147
- this.namespaceURI = "";
1263
+ var newPrefix = "";
1264
+ var newNamespaceURI = "";
1148
1265
 
1149
1266
  // Remove @namespace and trim
1150
1267
  var text = cssText.trim();
@@ -1167,26 +1284,40 @@ Object.defineProperty(CSSOM.CSSNamespaceRule.prototype, "cssText", {
1167
1284
  if (match) {
1168
1285
  // If prefix is present
1169
1286
  if (match[1]) {
1170
- this.prefix = match[1];
1287
+ newPrefix = match[1];
1171
1288
  }
1172
1289
  // If url(...) form with quotes
1173
1290
  if (typeof match[3] !== "undefined") {
1174
- this.namespaceURI = match[3];
1291
+ newNamespaceURI = match[3];
1175
1292
  }
1176
1293
  // If url(...) form without quotes
1177
1294
  else if (typeof match[4] !== "undefined") {
1178
- this.namespaceURI = match[4].trim();
1295
+ newNamespaceURI = match[4].trim();
1179
1296
  }
1180
1297
  // If quoted string form
1181
1298
  else if (typeof match[6] !== "undefined") {
1182
- this.namespaceURI = match[6];
1299
+ newNamespaceURI = match[6];
1183
1300
  }
1301
+
1302
+ this.__prefix = newPrefix;
1303
+ this.__namespaceURI = newNamespaceURI;
1184
1304
  } else {
1185
1305
  throw new DOMException("Invalid @namespace rule", "InvalidStateError");
1186
1306
  }
1187
1307
  }
1188
1308
  });
1189
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
+ });
1190
1321
 
1191
1322
 
1192
1323
 
@@ -1300,10 +1431,23 @@ Object.defineProperty(CSSOM.CSSStartingStyleRule.prototype, "cssText", {
1300
1431
  * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
1301
1432
  */
1302
1433
  CSSOM.StyleSheet = function StyleSheet() {
1434
+ this.__media = new CSSOM.MediaList();
1303
1435
  this.__parentStyleSheet = null;
1304
1436
  };
1305
1437
 
1306
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
+ },
1307
1451
  parentStyleSheet: {
1308
1452
  get: function() {
1309
1453
  return this.__parentStyleSheet;
@@ -1366,7 +1510,10 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1366
1510
  }
1367
1511
 
1368
1512
  var ruleToParse = String(rule);
1369
- var parsedSheet = CSSOM.parse(ruleToParse);
1513
+ var parseErrors = [];
1514
+ var parsedSheet = CSSOM.parse(ruleToParse, undefined, function(err) {
1515
+ parseErrors.push(err);
1516
+ } );
1370
1517
  if (parsedSheet.cssRules.length !== 1) {
1371
1518
  errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
1372
1519
  }
@@ -1434,12 +1581,21 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1434
1581
  'HierarchyRequestError');
1435
1582
  }
1436
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
+
1437
1591
  // Cannot insert after other types of rules
1438
1592
  if (index > firstNonImportNamespaceIndex) {
1439
1593
  errorUtils.throwError(this, 'DOMException',
1440
1594
  "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1441
1595
  'HierarchyRequestError');
1442
1596
  }
1597
+
1598
+
1443
1599
  } else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
1444
1600
  // @layer statement rules can be inserted anywhere before @import and @namespace
1445
1601
  // No additional restrictions beyond what's already handled
@@ -1456,6 +1612,10 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1456
1612
  "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1457
1613
  'HierarchyRequestError');
1458
1614
  }
1615
+
1616
+ if (parseErrors.filter(function(error) { return !error.isNested; }).length !== 0) {
1617
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
1618
+ }
1459
1619
  }
1460
1620
 
1461
1621
  cssRule.__parentStyleSheet = this;
@@ -1495,13 +1655,20 @@ CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1495
1655
  if (index >= this.cssRules.length) {
1496
1656
  errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
1497
1657
  }
1498
- if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
1499
- var shouldContinue = this.cssRules.every(function (rule) {
1500
- return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1501
- });
1502
- if (!shouldContinue) {
1503
- errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
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
+ }
1666
+ }
1667
+ if (this.cssRules[index].constructor.name == "CSSImportRule") {
1668
+ this.cssRules[index].styleSheet.__parentStyleSheet = null;
1504
1669
  }
1670
+
1671
+ this.cssRules[index].__parentStyleSheet = null;
1505
1672
  }
1506
1673
  this.cssRules.splice(index, 1);
1507
1674
  };
@@ -2364,6 +2531,266 @@ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2364
2531
 
2365
2532
 
2366
2533
 
2534
+ /**
2535
+ * @constructor
2536
+ * @see https://drafts.csswg.org/cssom/#the-csspagerule-interface
2537
+ */
2538
+ CSSOM.CSSPageRule = function CSSPageRule() {
2539
+ CSSOM.CSSGroupingRule.call(this);
2540
+ this.__style = new CSSOM.CSSStyleDeclaration();
2541
+ this.__style.parentRule = this;
2542
+ };
2543
+
2544
+ CSSOM.CSSPageRule.prototype = new CSSOM.CSSGroupingRule();
2545
+ CSSOM.CSSPageRule.prototype.constructor = CSSOM.CSSPageRule;
2546
+
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
+ });
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";
2655
+ var index;
2656
+ var j = i;
2657
+ var buffer = "";
2658
+
2659
+ var SIGNIFICANT_WHITESPACE = {
2660
+ "selector": true,
2661
+ "value": true
2662
+ };
2663
+
2664
+ var pageRule = new CSSOM.CSSPageRule();
2665
+ var name, priority="";
2666
+
2667
+ for (var character; (character = ruleText.charAt(i)); i++) {
2668
+
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
+
2367
2794
  /**
2368
2795
  * Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
2369
2796
  *
@@ -2412,7 +2839,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2412
2839
  "counterStyleBlock": true,
2413
2840
  'documentRule-begin': true,
2414
2841
  "scopeBlock": true,
2415
- "layerBlock": true
2842
+ "layerBlock": true,
2843
+ "pageBlock": true
2416
2844
  };
2417
2845
 
2418
2846
  var styleSheet = new CSSOM.CSSStyleSheet();
@@ -2430,7 +2858,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2430
2858
  var ancestorRules = [];
2431
2859
  var prevScope;
2432
2860
 
2433
- var name, priority="", styleRule, mediaRule, containerRule, counterStyleRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule, startingStyleRule, scopeRule, 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;
2434
2862
 
2435
2863
  // Track defined namespace prefixes for validation
2436
2864
  var definedNamespacePrefixes = {};
@@ -2444,8 +2872,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2444
2872
  var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
2445
2873
  var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
2446
2874
  var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
2447
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a single @layer name
2875
+ var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
2448
2876
  var startsWithCombinatorRegExp = /^\s*[>+~]/; // Checks if a selector starts with a CSS combinator (>, +, ~)
2877
+ var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/;
2449
2878
 
2450
2879
  /**
2451
2880
  * Searches for the first occurrence of a CSS at-rule statement terminator (`;` or `}`)
@@ -2770,12 +3199,12 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2770
3199
  if (isValid && atRuleKey === "@scope") {
2771
3200
  var openBraceIndex = ruleSlice.indexOf('{');
2772
3201
  if (openBraceIndex !== -1) {
2773
- // Extract the scope prelude (everything between @scope and {)
2774
- var scopePrelude = ruleSlice.slice(0, openBraceIndex).trim();
2775
-
2776
- // Skip past '@scope' keyword and whitespace
2777
- var preludeContent = scopePrelude.slice(6).trim();
2778
-
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
+
2779
3208
  if (preludeContent.length > 0) {
2780
3209
  // Parse the scope prelude
2781
3210
  var parsedScopePrelude = parseScopePrelude(preludeContent);
@@ -2814,6 +3243,64 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2814
3243
  // Empty prelude (@scope {}) is valid
2815
3244
  }
2816
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
+ }
2817
3304
 
2818
3305
  if (!isValid) {
2819
3306
  // If it's invalid the browser will simply ignore the entire invalid block
@@ -2893,6 +3380,27 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2893
3380
  return false;
2894
3381
  }
2895
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
+
2896
3404
  // Fallback to a loose regexp for the overall selector structure (without deep paren matching)
2897
3405
  // This is similar to the original, but without nested paren limitations
2898
3406
  // Modified to support namespace selectors: *|element, prefix|element, |element
@@ -2997,7 +3505,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
2997
3505
  * @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
2998
3506
  */
2999
3507
 
3000
- // Cache to store validated selectors (ES5-compliant object)
3508
+ // Cache to store validated selectors (previously a ES6 Map, now an ES5-compliant object)
3001
3509
  var validatedSelectorsCache = {};
3002
3510
 
3003
3511
  // Only pseudo-classes that accept selector lists should recurse
@@ -3107,6 +3615,28 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3107
3615
  return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
3108
3616
  }
3109
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
+
3110
3640
  /**
3111
3641
  * Checks if a given CSS selector text is valid by splitting it by commas
3112
3642
  * and validating each individual selector using the `validateSelector` function.
@@ -3115,6 +3645,9 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3115
3645
  * @returns {boolean} Returns true if all selectors are valid, otherwise false.
3116
3646
  */
3117
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
+
3118
3651
  // Check for newlines inside single or double quotes using regex
3119
3652
  // This matches any quoted string (single or double) containing a newline
3120
3653
  var quotedNewlineRegExp = /(['"])(?:\\.|[^\\])*?\1/g;
@@ -3135,7 +3668,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3135
3668
  return true;
3136
3669
  }
3137
3670
 
3138
- function parseError(message) {
3671
+ function parseError(message, isNested) {
3139
3672
  var lines = token.substring(0, i).split('\n');
3140
3673
  var lineCount = lines.length;
3141
3674
  var charCount = lines.pop().length + 1;
@@ -3144,6 +3677,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3144
3677
  /* jshint sub : true */
3145
3678
  error['char'] = charCount;
3146
3679
  error.styleSheet = styleSheet;
3680
+ error.isNested = !!isNested;
3147
3681
  // Print the error but continue parsing the sheet
3148
3682
  try {
3149
3683
  throw error;
@@ -3241,7 +3775,8 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3241
3775
  i += 2;
3242
3776
  index = token.indexOf("*/", i);
3243
3777
  if (index === -1) {
3244
- parseError("Missing */");
3778
+ i = token.length - 1;
3779
+ buffer = "";
3245
3780
  } else {
3246
3781
  i = index + 1;
3247
3782
  }
@@ -3314,6 +3849,15 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3314
3849
  });
3315
3850
  buffer = "";
3316
3851
  break;
3852
+ } else if (token.indexOf("@page", i) === i) {
3853
+ validateAtRule("@page", function(){
3854
+ state = "pageBlock"
3855
+ pageRule = new CSSOM.CSSPageRule();
3856
+ pageRule.__starts = i;
3857
+ i += "page".length;
3858
+ });
3859
+ buffer = "";
3860
+ break;
3317
3861
  } else if (token.indexOf("@supports", i) === i) {
3318
3862
  validateAtRule("@supports", function(){
3319
3863
  state = "conditionBlock";
@@ -3411,10 +3955,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3411
3955
  }
3412
3956
 
3413
3957
  currentScope = parentRule = styleRule;
3414
- styleRule.selectorText = buffer.trim().replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
3415
- if (newline) return " ";
3416
- return match;
3417
- });
3958
+ styleRule.selectorText = processSelectorText(buffer.trim());
3418
3959
  styleRule.style.__starts = i;
3419
3960
  styleRule.__parentStyleSheet = styleSheet;
3420
3961
  buffer = "";
@@ -3432,7 +3973,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3432
3973
  buffer = "";
3433
3974
  state = "before-selector";
3434
3975
  } else if (state === "containerBlock") {
3435
- containerRule.containerText = buffer.trim();
3976
+ containerRule.__conditionText = buffer.trim();
3436
3977
 
3437
3978
  if (parentRule) {
3438
3979
  containerRule.__parentRule = parentRule;
@@ -3449,7 +3990,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3449
3990
  counterStyleRule.__parentStyleSheet = styleSheet;
3450
3991
  buffer = "";
3451
3992
  } else if (state === "conditionBlock") {
3452
- supportsRule.conditionText = buffer.trim();
3993
+ supportsRule.__conditionText = buffer.trim();
3453
3994
 
3454
3995
  if (parentRule) {
3455
3996
  supportsRule.__parentRule = parentRule;
@@ -3484,7 +4025,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3484
4025
  } else if (state === "layerBlock") {
3485
4026
  layerBlockRule.name = buffer.trim();
3486
4027
 
3487
- var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(layerRuleNameRegExp) !== null;
4028
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
3488
4029
 
3489
4030
  if (isValidName) {
3490
4031
  if (parentRule) {
@@ -3497,6 +4038,19 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3497
4038
  }
3498
4039
  buffer = "";
3499
4040
  state = "before-selector";
4041
+ } else if (state === "pageBlock") {
4042
+ pageRule.selectorText = buffer.trim();
4043
+
4044
+ if (parentRule) {
4045
+ pageRule.__parentRule = parentRule;
4046
+ ancestorRules.push(parentRule);
4047
+ }
4048
+
4049
+ currentScope = parentRule = pageRule;
4050
+ pageRule.__parentStyleSheet = styleSheet;
4051
+ styleRule = pageRule;
4052
+ buffer = "";
4053
+ state = "before-name";
3500
4054
  } else if (state === "hostRule-begin") {
3501
4055
  if (parentRule) {
3502
4056
  ancestorRules.push(parentRule);
@@ -3570,10 +4124,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3570
4124
  }
3571
4125
 
3572
4126
  styleRule = new CSSOM.CSSStyleRule();
3573
- var processedSelectorText = buffer.trim().replace(/(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g, function(match, _, newline) {
3574
- if (newline) return " ";
3575
- return match;
3576
- });
4127
+ var processedSelectorText = processSelectorText(buffer.trim());
3577
4128
  // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
3578
4129
  if (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null) {
3579
4130
  styleRule.selectorText = processedSelectorText;
@@ -3697,7 +4248,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3697
4248
  testNamespaceRule.cssText = buffer + character;
3698
4249
 
3699
4250
  namespaceRule = testNamespaceRule;
3700
- namespaceRule.__parentStyleSheet = namespaceRule.styleSheet.__parentStyleSheet = styleSheet;
4251
+ namespaceRule.__parentStyleSheet = styleSheet;
3701
4252
  styleSheet.cssRules.push(namespaceRule);
3702
4253
 
3703
4254
  // Track the namespace prefix for validation
@@ -3716,7 +4267,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3716
4267
  return name.trim();
3717
4268
  });
3718
4269
  var isInvalid = parentRule !== undefined || nameListStr.some(function (name) {
3719
- return name.trim().match(layerRuleNameRegExp) === null;
4270
+ return name.trim().match(cssCustomIdentifierRegExp) === null;
3720
4271
  });
3721
4272
 
3722
4273
  if (!isInvalid) {
@@ -3773,7 +4324,7 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3773
4324
  if (styleRule === nestedSelectorRule) {
3774
4325
  nestedSelectorRule = null;
3775
4326
  }
3776
- parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")');
4327
+ parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
3777
4328
  } else {
3778
4329
  currentScope.cssRules.push(styleRule);
3779
4330
  }
@@ -3943,6 +4494,10 @@ CSSOM.parse = function parse(token, opts, errorHandler) {
3943
4494
  }
3944
4495
  }
3945
4496
 
4497
+ if (buffer.trim() !== "") {
4498
+ parseError("Unexpected end of input");
4499
+ }
4500
+
3946
4501
  return styleSheet;
3947
4502
  };
3948
4503