@acemir/cssom 0.9.17 → 0.9.18

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
@@ -5,27 +5,27 @@ var CSSOM = {};
5
5
 
6
6
  /**
7
7
  * Gets the appropriate error constructor from the global object context.
8
- * Tries to find the error constructor from parentStyleSheet._globalObject,
9
- * then from _globalObject, then falls back to the native constructor.
8
+ * Tries to find the error constructor from parentStyleSheet.__globalObject,
9
+ * then from __globalObject, then falls back to the native constructor.
10
10
  *
11
11
  * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
12
12
  * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
13
13
  * @return {Function} The error constructor
14
14
  */
15
15
  function getErrorConstructor(context, errorType) {
16
- // Try parentStyleSheet._globalObject first
17
- if (context.parentStyleSheet && context.parentStyleSheet._globalObject && context.parentStyleSheet._globalObject[errorType]) {
18
- return context.parentStyleSheet._globalObject[errorType];
16
+ // Try parentStyleSheet.__globalObject first
17
+ if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
18
+ return context.parentStyleSheet.__globalObject[errorType];
19
19
  }
20
20
 
21
21
  // Try __parentStyleSheet (alternative naming)
22
- if (context.__parentStyleSheet && context.__parentStyleSheet._globalObject && context.__parentStyleSheet._globalObject[errorType]) {
23
- return context.__parentStyleSheet._globalObject[errorType];
22
+ if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
23
+ return context.__parentStyleSheet.__globalObject[errorType];
24
24
  }
25
25
 
26
- // Try _globalObject on the context itself
27
- if (context._globalObject && context._globalObject[errorType]) {
28
- return context._globalObject[errorType];
26
+ // Try __globalObject on the context itself
27
+ if (context.__globalObject && context.__globalObject[errorType]) {
28
+ return context.__globalObject[errorType];
29
29
  }
30
30
 
31
31
  // Fall back to native constructor
@@ -66,16 +66,6 @@ function throwMissingArguments(context, methodName, objectName, required, provid
66
66
  throwError(context, 'TypeError', message);
67
67
  }
68
68
 
69
- /**
70
- * Throws a RangeError for index out of bounds.
71
- *
72
- * @param {Object} context - The CSSOM object
73
- * @param {string} [message] - Optional custom message, defaults to 'INDEX_SIZE_ERR'
74
- */
75
- function throwIndexSizeError(context, message) {
76
- throwError(context, 'RangeError', message || 'INDEX_SIZE_ERR');
77
- }
78
-
79
69
  /**
80
70
  * Throws a DOMException for parse errors.
81
71
  *
@@ -111,7 +101,6 @@ var errorUtils = {
111
101
  getErrorConstructor: getErrorConstructor,
112
102
  throwError: throwError,
113
103
  throwMissingArguments: throwMissingArguments,
114
- throwIndexSizeError: throwIndexSizeError,
115
104
  throwParseError: throwParseError,
116
105
  throwIndexError: throwIndexError
117
106
  };
@@ -422,10 +411,41 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
422
411
  * @return {number} The index within the grouping rule's collection of the newly inserted rule.
423
412
  */
424
413
  CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule, index) {
425
- if (index < 0 || index > this.cssRules.length) {
426
- errorUtils.throwIndexSizeError(this);
414
+ if (rule === undefined && index === undefined) {
415
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
416
+ }
417
+ if (index === void 0) {
418
+ index = 0;
419
+ }
420
+ index = Number(index);
421
+ if (index < 0) {
422
+ index = 4294967296 + index;
423
+ }
424
+ if (index > this.cssRules.length) {
425
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
426
+ }
427
+
428
+ var ruleToParse = String(rule);
429
+ var parsedSheet = CSSOM.parse(ruleToParse);
430
+ if (parsedSheet.cssRules.length !== 1) {
431
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
427
432
  }
428
- var cssRule = CSSOM.parse(rule).cssRules[0];
433
+ var cssRule = parsedSheet.cssRules[0];
434
+
435
+ // Check for rules that cannot be inserted inside a CSSGroupingRule
436
+ if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
437
+ var ruleKeyword = cssRule.constructor.name === 'CSSImportRule' ? '@import' : '@namespace';
438
+ errorUtils.throwError(this, 'DOMException',
439
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
440
+ "'" + ruleKeyword + "' rules cannot be inserted inside a group rule.",
441
+ 'HierarchyRequestError');
442
+ }
443
+
444
+ // Check for CSSLayerStatementRule (@layer statement rules)
445
+ if (cssRule.constructor.name === 'CSSLayerStatementRule') {
446
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
447
+ }
448
+
429
449
  cssRule.__parentRule = this;
430
450
  this.cssRules.splice(index, 0, cssRule);
431
451
  return index;
@@ -444,8 +464,15 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
444
464
  * @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
445
465
  */
446
466
  CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
447
- if (index < 0 || index >= this.cssRules.length) {
448
- errorUtils.throwIndexSizeError(this);
467
+ if (index === undefined) {
468
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
469
+ }
470
+ index = Number(index);
471
+ if (index < 0) {
472
+ index = 4294967296 + index;
473
+ }
474
+ if (index >= this.cssRules.length) {
475
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
449
476
  }
450
477
  this.cssRules.splice(index, 1)[0].__parentRule = null;
451
478
  };
@@ -542,7 +569,7 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
542
569
  valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
543
570
  values = valuesArr.join("\n ") + "\n}"
544
571
  } else {
545
- values = " { " + this.style.cssText + " }";
572
+ values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
546
573
  }
547
574
  text = this.selectorText + values;
548
575
  } else {
@@ -899,7 +926,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
899
926
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
900
927
  get: function() {
901
928
  var mediaText = this.media.mediaText;
902
- return "@import url(" + this.href + ")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
929
+ return "@import url(\"" + this.href + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
903
930
  },
904
931
  set: function(cssText) {
905
932
  var i = 0;
@@ -917,7 +944,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
917
944
  var index;
918
945
 
919
946
  var layerRegExp = /layer\(([^)]*)\)/;
920
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
947
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
921
948
  var supportsRegExp = /supports\(([^)]+)\)/;
922
949
  var doubleOrMoreSpacesRegExp = /\s{2,}/g;
923
950
 
@@ -1000,12 +1027,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1000
1027
 
1001
1028
  if (layerMatch) {
1002
1029
  var layerName = layerMatch[1].trim();
1003
- bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
1004
- .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1005
- .trim();
1006
1030
 
1007
1031
  if (layerName.match(layerRuleNameRegExp) !== null) {
1008
1032
  this.layerName = layerMatch[1].trim();
1033
+ bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
1034
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1035
+ .trim();
1009
1036
  } else {
1010
1037
  // REVIEW: In the browser, an empty layer() is not processed as a unamed layer
1011
1038
  // and treats the rest of the string as mediaText, ignoring the parse of supports()
@@ -1156,7 +1183,7 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "style", {
1156
1183
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
1157
1184
  Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
1158
1185
  get: function() {
1159
- return "@font-face { " + this.style.cssText + " }";
1186
+ return "@font-face {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
1160
1187
  }
1161
1188
  });
1162
1189
 
@@ -1279,24 +1306,112 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
1279
1306
  */
1280
1307
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1281
1308
  if (rule === undefined && index === undefined) {
1282
- errorUtils.throwMissingArguments(this, 'insertRule', 'CSSStyleSheet');
1309
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
1283
1310
  }
1284
1311
  if (index === void 0) {
1285
1312
  index = 0;
1286
1313
  }
1287
- if (index < 0 || index > this.cssRules.length) {
1288
- errorUtils.throwIndexSizeError(this);
1314
+ index = Number(index);
1315
+ if (index < 0) {
1316
+ index = 4294967296 + index;
1317
+ }
1318
+ if (index > this.cssRules.length) {
1319
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
1289
1320
  }
1321
+
1290
1322
  var ruleToParse = String(rule);
1291
1323
  var parsedSheet = CSSOM.parse(ruleToParse);
1292
1324
  if (parsedSheet.cssRules.length !== 1) {
1293
- var domExceptionName = "SyntaxError";
1294
- if (ruleToParse.trimStart().startsWith('@namespace')) {
1295
- domExceptionName = "InvalidStateError";
1296
- }
1297
- errorUtils.throwParseError(this, 'insertRule', 'CSSStyleSheet', ruleToParse, domExceptionName);
1325
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
1298
1326
  }
1299
1327
  var cssRule = parsedSheet.cssRules[0];
1328
+
1329
+ // Helper function to find the last index of a specific rule constructor
1330
+ function findLastIndexOfConstructor(rules, constructorName) {
1331
+ for (var i = rules.length - 1; i >= 0; i--) {
1332
+ if (rules[i].constructor.name === constructorName) {
1333
+ return i;
1334
+ }
1335
+ }
1336
+ return -1;
1337
+ }
1338
+
1339
+ // Helper function to find the first index of a rule that's NOT of specified constructors
1340
+ function findFirstNonConstructorIndex(rules, constructorNames) {
1341
+ for (var i = 0; i < rules.length; i++) {
1342
+ if (constructorNames.indexOf(rules[i].constructor.name) === -1) {
1343
+ return i;
1344
+ }
1345
+ }
1346
+ return rules.length;
1347
+ }
1348
+
1349
+ // Validate rule ordering based on CSS specification
1350
+ if (cssRule.constructor.name === 'CSSImportRule') {
1351
+ // @import rules cannot be inserted after @layer rules that already exist
1352
+ // They can only be inserted at the beginning or after other @import rules
1353
+ var firstLayerIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
1354
+ if (firstLayerIndex < this.cssRules.length && this.cssRules[firstLayerIndex].constructor.name === 'CSSLayerStatementRule' && index > firstLayerIndex) {
1355
+ errorUtils.throwError(this, 'DOMException',
1356
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1357
+ 'HierarchyRequestError');
1358
+ }
1359
+
1360
+ // Also cannot insert after @namespace or other rules
1361
+ var firstNonImportIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
1362
+ if (index > firstNonImportIndex && firstNonImportIndex < this.cssRules.length &&
1363
+ this.cssRules[firstNonImportIndex].constructor.name !== 'CSSLayerStatementRule') {
1364
+ errorUtils.throwError(this, 'DOMException',
1365
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1366
+ 'HierarchyRequestError');
1367
+ }
1368
+ } else if (cssRule.constructor.name === 'CSSNamespaceRule') {
1369
+ // @namespace rules can come after @layer and @import, but before any other rules
1370
+ // They cannot come before @import rules
1371
+ var firstImportIndex = -1;
1372
+ for (var i = 0; i < this.cssRules.length; i++) {
1373
+ if (this.cssRules[i].constructor.name === 'CSSImportRule') {
1374
+ firstImportIndex = i;
1375
+ break;
1376
+ }
1377
+ }
1378
+ var firstNonImportNamespaceIndex = findFirstNonConstructorIndex(this.cssRules, [
1379
+ 'CSSLayerStatementRule',
1380
+ 'CSSImportRule',
1381
+ 'CSSNamespaceRule'
1382
+ ]);
1383
+
1384
+ // Cannot insert before @import rules
1385
+ if (firstImportIndex !== -1 && index <= firstImportIndex) {
1386
+ errorUtils.throwError(this, 'DOMException',
1387
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1388
+ 'HierarchyRequestError');
1389
+ }
1390
+
1391
+ // Cannot insert after other types of rules
1392
+ if (index > firstNonImportNamespaceIndex) {
1393
+ errorUtils.throwError(this, 'DOMException',
1394
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1395
+ 'HierarchyRequestError');
1396
+ }
1397
+ } else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
1398
+ // @layer statement rules can be inserted anywhere before @import and @namespace
1399
+ // No additional restrictions beyond what's already handled
1400
+ } else {
1401
+ // Any other rule cannot be inserted before @import and @namespace
1402
+ var firstNonSpecialRuleIndex = findFirstNonConstructorIndex(this.cssRules, [
1403
+ 'CSSLayerStatementRule',
1404
+ 'CSSImportRule',
1405
+ 'CSSNamespaceRule'
1406
+ ]);
1407
+
1408
+ if (index < firstNonSpecialRuleIndex) {
1409
+ errorUtils.throwError(this, 'DOMException',
1410
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1411
+ 'HierarchyRequestError');
1412
+ }
1413
+ }
1414
+
1300
1415
  cssRule.__parentStyleSheet = this;
1301
1416
  this.cssRules.splice(index, 0, cssRule);
1302
1417
  return index;
@@ -1318,21 +1433,21 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1318
1433
  */
1319
1434
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1320
1435
  if (index === undefined) {
1321
- errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSStyleSheet');
1436
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
1322
1437
  }
1323
1438
  index = Number(index);
1324
1439
  if (index < 0) {
1325
1440
  index = 4294967296 + index;
1326
1441
  }
1327
1442
  if (index >= this.cssRules.length) {
1328
- errorUtils.throwIndexError(this, 'deleteRule', 'CSSStyleSheet', index, this.cssRules.length);
1443
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
1329
1444
  }
1330
1445
  if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
1331
1446
  var shouldContinue = this.cssRules.every(function (rule) {
1332
1447
  return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1333
1448
  });
1334
1449
  if (!shouldContinue) {
1335
- errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on 'CSSStyleSheet': Deleting a CSSNamespaceRule is not allowed when there is rules other than @import, @layer statement, or @namespace.", "InvalidStateError");
1450
+ errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
1336
1451
  }
1337
1452
  }
1338
1453
  this.cssRules.splice(index, 1);
@@ -1616,7 +1731,7 @@ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "style", {
1616
1731
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
1617
1732
  Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", {
1618
1733
  get: function() {
1619
- return this.keyText + " { " + this.style.cssText + " }";
1734
+ return this.keyText + " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
1620
1735
  }
1621
1736
  });
1622
1737
 
@@ -2150,10 +2265,16 @@ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2150
2265
 
2151
2266
 
2152
2267
  /**
2153
- * @param {string} token
2268
+ * Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
2269
+ *
2270
+ * @param {string} token - The CSS string to parse.
2271
+ * @param {object} [opts] - Optional parsing options.
2272
+ * @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
2273
+ * @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
2274
+ * @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
2154
2275
  */
2155
- CSSOM.parse = function parse(token, errorHandler) {
2156
- errorHandler = errorHandler === undefined ? (console && console.error) : errorHandler;
2276
+ CSSOM.parse = function parse(token, opts, errorHandler) {
2277
+ errorHandler = errorHandler === true ? (console && console.error) : errorHandler;
2157
2278
 
2158
2279
  var i = 0;
2159
2280
 
@@ -2195,6 +2316,10 @@ CSSOM.parse = function parse(token, errorHandler) {
2195
2316
 
2196
2317
  var styleSheet = new CSSOM.CSSStyleSheet();
2197
2318
 
2319
+ if (opts && opts.globalObject) {
2320
+ styleSheet.__globalObject = opts.globalObject;
2321
+ }
2322
+
2198
2323
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
2199
2324
  var currentScope = styleSheet;
2200
2325
 
@@ -2892,7 +3017,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2892
3017
  i += "font-face".length;
2893
3018
  fontFaceRule = new CSSOM.CSSFontFaceRule();
2894
3019
  fontFaceRule.__starts = i;
2895
- }, parentRule && parentRule.constructor.name === "CSSStyleRule" );
3020
+ }, true);
2896
3021
  break;
2897
3022
  } else {
2898
3023
  atKeyframesRegExp.lastIndex = i;
@@ -45,7 +45,7 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "style", {
45
45
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
46
46
  Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
47
47
  get: function() {
48
- return "@font-face { " + this.style.cssText + " }";
48
+ return "@font-face {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
49
49
  }
50
50
  });
51
51
 
@@ -37,10 +37,41 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
37
37
  * @return {number} The index within the grouping rule's collection of the newly inserted rule.
38
38
  */
39
39
  CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule, index) {
40
- if (index < 0 || index > this.cssRules.length) {
41
- errorUtils.throwIndexSizeError(this);
40
+ if (rule === undefined && index === undefined) {
41
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
42
42
  }
43
- var cssRule = CSSOM.parse(rule).cssRules[0];
43
+ if (index === void 0) {
44
+ index = 0;
45
+ }
46
+ index = Number(index);
47
+ if (index < 0) {
48
+ index = 4294967296 + index;
49
+ }
50
+ if (index > this.cssRules.length) {
51
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
52
+ }
53
+
54
+ var ruleToParse = String(rule);
55
+ var parsedSheet = CSSOM.parse(ruleToParse);
56
+ if (parsedSheet.cssRules.length !== 1) {
57
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
58
+ }
59
+ var cssRule = parsedSheet.cssRules[0];
60
+
61
+ // Check for rules that cannot be inserted inside a CSSGroupingRule
62
+ if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
63
+ var ruleKeyword = cssRule.constructor.name === 'CSSImportRule' ? '@import' : '@namespace';
64
+ errorUtils.throwError(this, 'DOMException',
65
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
66
+ "'" + ruleKeyword + "' rules cannot be inserted inside a group rule.",
67
+ 'HierarchyRequestError');
68
+ }
69
+
70
+ // Check for CSSLayerStatementRule (@layer statement rules)
71
+ if (cssRule.constructor.name === 'CSSLayerStatementRule') {
72
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
73
+ }
74
+
44
75
  cssRule.__parentRule = this;
45
76
  this.cssRules.splice(index, 0, cssRule);
46
77
  return index;
@@ -59,8 +90,15 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
59
90
  * @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
60
91
  */
61
92
  CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
62
- if (index < 0 || index >= this.cssRules.length) {
63
- errorUtils.throwIndexSizeError(this);
93
+ if (index === undefined) {
94
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
95
+ }
96
+ index = Number(index);
97
+ if (index < 0) {
98
+ index = 4294967296 + index;
99
+ }
100
+ if (index >= this.cssRules.length) {
101
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
64
102
  }
65
103
  this.cssRules.splice(index, 1)[0].__parentRule = null;
66
104
  };
@@ -28,7 +28,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
28
28
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
29
29
  get: function() {
30
30
  var mediaText = this.media.mediaText;
31
- return "@import url(" + this.href + ")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
31
+ return "@import url(\"" + this.href + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
32
32
  },
33
33
  set: function(cssText) {
34
34
  var i = 0;
@@ -46,7 +46,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
46
46
  var index;
47
47
 
48
48
  var layerRegExp = /layer\(([^)]*)\)/;
49
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
49
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
50
50
  var supportsRegExp = /supports\(([^)]+)\)/;
51
51
  var doubleOrMoreSpacesRegExp = /\s{2,}/g;
52
52
 
@@ -129,12 +129,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
129
129
 
130
130
  if (layerMatch) {
131
131
  var layerName = layerMatch[1].trim();
132
- bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
133
- .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
134
- .trim();
135
132
 
136
133
  if (layerName.match(layerRuleNameRegExp) !== null) {
137
134
  this.layerName = layerMatch[1].trim();
135
+ bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
136
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
137
+ .trim();
138
138
  } else {
139
139
  // REVIEW: In the browser, an empty layer() is not processed as a unamed layer
140
140
  // and treats the rest of the string as mediaText, ignoring the parse of supports()
@@ -46,7 +46,7 @@ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "style", {
46
46
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
47
47
  Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", {
48
48
  get: function() {
49
- return this.keyText + " { " + this.style.cssText + " }";
49
+ return this.keyText + " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
50
50
  }
51
51
  });
52
52
 
@@ -66,7 +66,7 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
66
66
  valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
67
67
  values = valuesArr.join("\n ") + "\n}"
68
68
  } else {
69
- values = " { " + this.style.cssText + " }";
69
+ values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
70
70
  }
71
71
  text = this.selectorText + values;
72
72
  } else {
@@ -39,24 +39,112 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
39
39
  */
40
40
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
41
41
  if (rule === undefined && index === undefined) {
42
- errorUtils.throwMissingArguments(this, 'insertRule', 'CSSStyleSheet');
42
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
43
43
  }
44
44
  if (index === void 0) {
45
45
  index = 0;
46
46
  }
47
- if (index < 0 || index > this.cssRules.length) {
48
- errorUtils.throwIndexSizeError(this);
47
+ index = Number(index);
48
+ if (index < 0) {
49
+ index = 4294967296 + index;
50
+ }
51
+ if (index > this.cssRules.length) {
52
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
49
53
  }
54
+
50
55
  var ruleToParse = String(rule);
51
56
  var parsedSheet = CSSOM.parse(ruleToParse);
52
57
  if (parsedSheet.cssRules.length !== 1) {
53
- var domExceptionName = "SyntaxError";
54
- if (ruleToParse.trimStart().startsWith('@namespace')) {
55
- domExceptionName = "InvalidStateError";
56
- }
57
- errorUtils.throwParseError(this, 'insertRule', 'CSSStyleSheet', ruleToParse, domExceptionName);
58
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
58
59
  }
59
60
  var cssRule = parsedSheet.cssRules[0];
61
+
62
+ // Helper function to find the last index of a specific rule constructor
63
+ function findLastIndexOfConstructor(rules, constructorName) {
64
+ for (var i = rules.length - 1; i >= 0; i--) {
65
+ if (rules[i].constructor.name === constructorName) {
66
+ return i;
67
+ }
68
+ }
69
+ return -1;
70
+ }
71
+
72
+ // Helper function to find the first index of a rule that's NOT of specified constructors
73
+ function findFirstNonConstructorIndex(rules, constructorNames) {
74
+ for (var i = 0; i < rules.length; i++) {
75
+ if (constructorNames.indexOf(rules[i].constructor.name) === -1) {
76
+ return i;
77
+ }
78
+ }
79
+ return rules.length;
80
+ }
81
+
82
+ // Validate rule ordering based on CSS specification
83
+ if (cssRule.constructor.name === 'CSSImportRule') {
84
+ // @import rules cannot be inserted after @layer rules that already exist
85
+ // They can only be inserted at the beginning or after other @import rules
86
+ var firstLayerIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
87
+ if (firstLayerIndex < this.cssRules.length && this.cssRules[firstLayerIndex].constructor.name === 'CSSLayerStatementRule' && index > firstLayerIndex) {
88
+ errorUtils.throwError(this, 'DOMException',
89
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
90
+ 'HierarchyRequestError');
91
+ }
92
+
93
+ // Also cannot insert after @namespace or other rules
94
+ var firstNonImportIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
95
+ if (index > firstNonImportIndex && firstNonImportIndex < this.cssRules.length &&
96
+ this.cssRules[firstNonImportIndex].constructor.name !== 'CSSLayerStatementRule') {
97
+ errorUtils.throwError(this, 'DOMException',
98
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
99
+ 'HierarchyRequestError');
100
+ }
101
+ } else if (cssRule.constructor.name === 'CSSNamespaceRule') {
102
+ // @namespace rules can come after @layer and @import, but before any other rules
103
+ // They cannot come before @import rules
104
+ var firstImportIndex = -1;
105
+ for (var i = 0; i < this.cssRules.length; i++) {
106
+ if (this.cssRules[i].constructor.name === 'CSSImportRule') {
107
+ firstImportIndex = i;
108
+ break;
109
+ }
110
+ }
111
+ var firstNonImportNamespaceIndex = findFirstNonConstructorIndex(this.cssRules, [
112
+ 'CSSLayerStatementRule',
113
+ 'CSSImportRule',
114
+ 'CSSNamespaceRule'
115
+ ]);
116
+
117
+ // Cannot insert before @import rules
118
+ if (firstImportIndex !== -1 && index <= firstImportIndex) {
119
+ errorUtils.throwError(this, 'DOMException',
120
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
121
+ 'HierarchyRequestError');
122
+ }
123
+
124
+ // Cannot insert after other types of rules
125
+ if (index > firstNonImportNamespaceIndex) {
126
+ errorUtils.throwError(this, 'DOMException',
127
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
128
+ 'HierarchyRequestError');
129
+ }
130
+ } else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
131
+ // @layer statement rules can be inserted anywhere before @import and @namespace
132
+ // No additional restrictions beyond what's already handled
133
+ } else {
134
+ // Any other rule cannot be inserted before @import and @namespace
135
+ var firstNonSpecialRuleIndex = findFirstNonConstructorIndex(this.cssRules, [
136
+ 'CSSLayerStatementRule',
137
+ 'CSSImportRule',
138
+ 'CSSNamespaceRule'
139
+ ]);
140
+
141
+ if (index < firstNonSpecialRuleIndex) {
142
+ errorUtils.throwError(this, 'DOMException',
143
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
144
+ 'HierarchyRequestError');
145
+ }
146
+ }
147
+
60
148
  cssRule.__parentStyleSheet = this;
61
149
  this.cssRules.splice(index, 0, cssRule);
62
150
  return index;
@@ -78,21 +166,21 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
78
166
  */
79
167
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
80
168
  if (index === undefined) {
81
- errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSStyleSheet');
169
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
82
170
  }
83
171
  index = Number(index);
84
172
  if (index < 0) {
85
173
  index = 4294967296 + index;
86
174
  }
87
175
  if (index >= this.cssRules.length) {
88
- errorUtils.throwIndexError(this, 'deleteRule', 'CSSStyleSheet', index, this.cssRules.length);
176
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
89
177
  }
90
178
  if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
91
179
  var shouldContinue = this.cssRules.every(function (rule) {
92
180
  return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
93
181
  });
94
182
  if (!shouldContinue) {
95
- errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on 'CSSStyleSheet': Deleting a CSSNamespaceRule is not allowed when there is rules other than @import, @layer statement, or @namespace.", "InvalidStateError");
183
+ errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
96
184
  }
97
185
  }
98
186
  this.cssRules.splice(index, 1);
package/lib/errorUtils.js CHANGED
@@ -2,27 +2,27 @@
2
2
 
3
3
  /**
4
4
  * Gets the appropriate error constructor from the global object context.
5
- * Tries to find the error constructor from parentStyleSheet._globalObject,
6
- * then from _globalObject, then falls back to the native constructor.
5
+ * Tries to find the error constructor from parentStyleSheet.__globalObject,
6
+ * then from __globalObject, then falls back to the native constructor.
7
7
  *
8
8
  * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
9
9
  * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
10
10
  * @return {Function} The error constructor
11
11
  */
12
12
  function getErrorConstructor(context, errorType) {
13
- // Try parentStyleSheet._globalObject first
14
- if (context.parentStyleSheet && context.parentStyleSheet._globalObject && context.parentStyleSheet._globalObject[errorType]) {
15
- return context.parentStyleSheet._globalObject[errorType];
13
+ // Try parentStyleSheet.__globalObject first
14
+ if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
15
+ return context.parentStyleSheet.__globalObject[errorType];
16
16
  }
17
17
 
18
18
  // Try __parentStyleSheet (alternative naming)
19
- if (context.__parentStyleSheet && context.__parentStyleSheet._globalObject && context.__parentStyleSheet._globalObject[errorType]) {
20
- return context.__parentStyleSheet._globalObject[errorType];
19
+ if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
20
+ return context.__parentStyleSheet.__globalObject[errorType];
21
21
  }
22
22
 
23
- // Try _globalObject on the context itself
24
- if (context._globalObject && context._globalObject[errorType]) {
25
- return context._globalObject[errorType];
23
+ // Try __globalObject on the context itself
24
+ if (context.__globalObject && context.__globalObject[errorType]) {
25
+ return context.__globalObject[errorType];
26
26
  }
27
27
 
28
28
  // Fall back to native constructor
@@ -63,16 +63,6 @@ function throwMissingArguments(context, methodName, objectName, required, provid
63
63
  throwError(context, 'TypeError', message);
64
64
  }
65
65
 
66
- /**
67
- * Throws a RangeError for index out of bounds.
68
- *
69
- * @param {Object} context - The CSSOM object
70
- * @param {string} [message] - Optional custom message, defaults to 'INDEX_SIZE_ERR'
71
- */
72
- function throwIndexSizeError(context, message) {
73
- throwError(context, 'RangeError', message || 'INDEX_SIZE_ERR');
74
- }
75
-
76
66
  /**
77
67
  * Throws a DOMException for parse errors.
78
68
  *
@@ -108,7 +98,6 @@ var errorUtils = {
108
98
  getErrorConstructor: getErrorConstructor,
109
99
  throwError: throwError,
110
100
  throwMissingArguments: throwMissingArguments,
111
- throwIndexSizeError: throwIndexSizeError,
112
101
  throwParseError: throwParseError,
113
102
  throwIndexError: throwIndexError
114
103
  };
package/lib/parse.js CHANGED
@@ -4,10 +4,16 @@ var CSSOM = {};
4
4
 
5
5
 
6
6
  /**
7
- * @param {string} token
7
+ * Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
8
+ *
9
+ * @param {string} token - The CSS string to parse.
10
+ * @param {object} [opts] - Optional parsing options.
11
+ * @param {object} [opts.globalObject] - An optional global object to attach to the stylesheet. Useful on jsdom webplatform tests.
12
+ * @param {function|boolean} [errorHandler] - Optional error handler function or `true` to use `console.error`.
13
+ * @returns {CSSOM.CSSStyleSheet} The parsed CSSStyleSheet object.
8
14
  */
9
- CSSOM.parse = function parse(token, errorHandler) {
10
- errorHandler = errorHandler === undefined ? (console && console.error) : errorHandler;
15
+ CSSOM.parse = function parse(token, opts, errorHandler) {
16
+ errorHandler = errorHandler === true ? (console && console.error) : errorHandler;
11
17
 
12
18
  var i = 0;
13
19
 
@@ -49,6 +55,10 @@ CSSOM.parse = function parse(token, errorHandler) {
49
55
 
50
56
  var styleSheet = new CSSOM.CSSStyleSheet();
51
57
 
58
+ if (opts && opts.globalObject) {
59
+ styleSheet.__globalObject = opts.globalObject;
60
+ }
61
+
52
62
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
53
63
  var currentScope = styleSheet;
54
64
 
@@ -746,7 +756,7 @@ CSSOM.parse = function parse(token, errorHandler) {
746
756
  i += "font-face".length;
747
757
  fontFaceRule = new CSSOM.CSSFontFaceRule();
748
758
  fontFaceRule.__starts = i;
749
- }, parentRule && parentRule.constructor.name === "CSSStyleRule" );
759
+ }, true);
750
760
  break;
751
761
  } else {
752
762
  atKeyframesRegExp.lastIndex = i;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "parser",
8
8
  "styleSheet"
9
9
  ],
10
- "version": "0.9.17",
10
+ "version": "0.9.18",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"