@acemir/cssom 0.9.17 → 0.9.19

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
  };
@@ -312,6 +301,27 @@ CSSOM.CSSRule = function CSSRule() {
312
301
  this.__parentStyleSheet = null;
313
302
  };
314
303
 
304
+ CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete
305
+ CSSOM.CSSRule.STYLE_RULE = 1;
306
+ CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete
307
+ CSSOM.CSSRule.IMPORT_RULE = 3;
308
+ CSSOM.CSSRule.MEDIA_RULE = 4;
309
+ CSSOM.CSSRule.FONT_FACE_RULE = 5;
310
+ CSSOM.CSSRule.PAGE_RULE = 6;
311
+ CSSOM.CSSRule.KEYFRAMES_RULE = 7;
312
+ CSSOM.CSSRule.KEYFRAME_RULE = 8;
313
+ CSSOM.CSSRule.MARGIN_RULE = 9;
314
+ CSSOM.CSSRule.NAMESPACE_RULE = 10;
315
+ CSSOM.CSSRule.COUNTER_STYLE_RULE = 11;
316
+ CSSOM.CSSRule.SUPPORTS_RULE = 12;
317
+ CSSOM.CSSRule.DOCUMENT_RULE = 13;
318
+ CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14;
319
+ CSSOM.CSSRule.VIEWPORT_RULE = 15;
320
+ CSSOM.CSSRule.REGION_STYLE_RULE = 16;
321
+ CSSOM.CSSRule.CONTAINER_RULE = 17;
322
+ CSSOM.CSSRule.LAYER_BLOCK_RULE = 18;
323
+ CSSOM.CSSRule.STARTING_STYLE_RULE = 1002;
324
+
315
325
  Object.defineProperties(CSSOM.CSSRule.prototype, {
316
326
 
317
327
  constructor: { value: CSSOM.CSSRule },
@@ -348,7 +358,7 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
348
358
  CONTAINER_RULE: { value: 17, enumerable: true },
349
359
  LAYER_BLOCK_RULE: { value: 18, enumerable: true },
350
360
  STARTING_STYLE_RULE: { value: 1002, enumerable: true },
351
- })
361
+ });
352
362
 
353
363
 
354
364
 
@@ -422,10 +432,41 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
422
432
  * @return {number} The index within the grouping rule's collection of the newly inserted rule.
423
433
  */
424
434
  CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule, index) {
425
- if (index < 0 || index > this.cssRules.length) {
426
- errorUtils.throwIndexSizeError(this);
435
+ if (rule === undefined && index === undefined) {
436
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
427
437
  }
428
- var cssRule = CSSOM.parse(rule).cssRules[0];
438
+ if (index === void 0) {
439
+ index = 0;
440
+ }
441
+ index = Number(index);
442
+ if (index < 0) {
443
+ index = 4294967296 + index;
444
+ }
445
+ if (index > this.cssRules.length) {
446
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
447
+ }
448
+
449
+ var ruleToParse = String(rule);
450
+ var parsedSheet = CSSOM.parse(ruleToParse);
451
+ if (parsedSheet.cssRules.length !== 1) {
452
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
453
+ }
454
+ var cssRule = parsedSheet.cssRules[0];
455
+
456
+ // Check for rules that cannot be inserted inside a CSSGroupingRule
457
+ if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
458
+ var ruleKeyword = cssRule.constructor.name === 'CSSImportRule' ? '@import' : '@namespace';
459
+ errorUtils.throwError(this, 'DOMException',
460
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
461
+ "'" + ruleKeyword + "' rules cannot be inserted inside a group rule.",
462
+ 'HierarchyRequestError');
463
+ }
464
+
465
+ // Check for CSSLayerStatementRule (@layer statement rules)
466
+ if (cssRule.constructor.name === 'CSSLayerStatementRule') {
467
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
468
+ }
469
+
429
470
  cssRule.__parentRule = this;
430
471
  this.cssRules.splice(index, 0, cssRule);
431
472
  return index;
@@ -444,8 +485,15 @@ CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
444
485
  * @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
445
486
  */
446
487
  CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
447
- if (index < 0 || index >= this.cssRules.length) {
448
- errorUtils.throwIndexSizeError(this);
488
+ if (index === undefined) {
489
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
490
+ }
491
+ index = Number(index);
492
+ if (index < 0) {
493
+ index = 4294967296 + index;
494
+ }
495
+ if (index >= this.cssRules.length) {
496
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
449
497
  }
450
498
  this.cssRules.splice(index, 1)[0].__parentRule = null;
451
499
  };
@@ -542,7 +590,7 @@ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
542
590
  valuesArr.push(this.cssRules.map(function(rule){ return rule.cssText }).join("\n "));
543
591
  values = valuesArr.join("\n ") + "\n}"
544
592
  } else {
545
- values = " { " + this.style.cssText + " }";
593
+ values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
546
594
  }
547
595
  text = this.selectorText + values;
548
596
  } else {
@@ -899,7 +947,7 @@ CSSOM.CSSImportRule.prototype.type = 3;
899
947
  Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
900
948
  get: function() {
901
949
  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 : "") + ";";
950
+ return "@import url(\"" + this.href + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
903
951
  },
904
952
  set: function(cssText) {
905
953
  var i = 0;
@@ -917,7 +965,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
917
965
  var index;
918
966
 
919
967
  var layerRegExp = /layer\(([^)]*)\)/;
920
- var layerRuleNameRegExp = /^(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)$/;
968
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
921
969
  var supportsRegExp = /supports\(([^)]+)\)/;
922
970
  var doubleOrMoreSpacesRegExp = /\s{2,}/g;
923
971
 
@@ -1000,12 +1048,12 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
1000
1048
 
1001
1049
  if (layerMatch) {
1002
1050
  var layerName = layerMatch[1].trim();
1003
- bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
1004
- .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1005
- .trim();
1006
1051
 
1007
1052
  if (layerName.match(layerRuleNameRegExp) !== null) {
1008
1053
  this.layerName = layerMatch[1].trim();
1054
+ bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
1055
+ .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
1056
+ .trim();
1009
1057
  } else {
1010
1058
  // REVIEW: In the browser, an empty layer() is not processed as a unamed layer
1011
1059
  // and treats the rest of the string as mediaText, ignoring the parse of supports()
@@ -1156,7 +1204,7 @@ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "style", {
1156
1204
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
1157
1205
  Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
1158
1206
  get: function() {
1159
- return "@font-face { " + this.style.cssText + " }";
1207
+ return "@font-face {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
1160
1208
  }
1161
1209
  });
1162
1210
 
@@ -1279,24 +1327,112 @@ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
1279
1327
  */
1280
1328
  CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1281
1329
  if (rule === undefined && index === undefined) {
1282
- errorUtils.throwMissingArguments(this, 'insertRule', 'CSSStyleSheet');
1330
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
1283
1331
  }
1284
1332
  if (index === void 0) {
1285
1333
  index = 0;
1286
1334
  }
1287
- if (index < 0 || index > this.cssRules.length) {
1288
- errorUtils.throwIndexSizeError(this);
1335
+ index = Number(index);
1336
+ if (index < 0) {
1337
+ index = 4294967296 + index;
1289
1338
  }
1339
+ if (index > this.cssRules.length) {
1340
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
1341
+ }
1342
+
1290
1343
  var ruleToParse = String(rule);
1291
1344
  var parsedSheet = CSSOM.parse(ruleToParse);
1292
1345
  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);
1346
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
1298
1347
  }
1299
1348
  var cssRule = parsedSheet.cssRules[0];
1349
+
1350
+ // Helper function to find the last index of a specific rule constructor
1351
+ function findLastIndexOfConstructor(rules, constructorName) {
1352
+ for (var i = rules.length - 1; i >= 0; i--) {
1353
+ if (rules[i].constructor.name === constructorName) {
1354
+ return i;
1355
+ }
1356
+ }
1357
+ return -1;
1358
+ }
1359
+
1360
+ // Helper function to find the first index of a rule that's NOT of specified constructors
1361
+ function findFirstNonConstructorIndex(rules, constructorNames) {
1362
+ for (var i = 0; i < rules.length; i++) {
1363
+ if (constructorNames.indexOf(rules[i].constructor.name) === -1) {
1364
+ return i;
1365
+ }
1366
+ }
1367
+ return rules.length;
1368
+ }
1369
+
1370
+ // Validate rule ordering based on CSS specification
1371
+ if (cssRule.constructor.name === 'CSSImportRule') {
1372
+ // @import rules cannot be inserted after @layer rules that already exist
1373
+ // They can only be inserted at the beginning or after other @import rules
1374
+ var firstLayerIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
1375
+ if (firstLayerIndex < this.cssRules.length && this.cssRules[firstLayerIndex].constructor.name === 'CSSLayerStatementRule' && index > firstLayerIndex) {
1376
+ errorUtils.throwError(this, 'DOMException',
1377
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1378
+ 'HierarchyRequestError');
1379
+ }
1380
+
1381
+ // Also cannot insert after @namespace or other rules
1382
+ var firstNonImportIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
1383
+ if (index > firstNonImportIndex && firstNonImportIndex < this.cssRules.length &&
1384
+ this.cssRules[firstNonImportIndex].constructor.name !== 'CSSLayerStatementRule') {
1385
+ errorUtils.throwError(this, 'DOMException',
1386
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1387
+ 'HierarchyRequestError');
1388
+ }
1389
+ } else if (cssRule.constructor.name === 'CSSNamespaceRule') {
1390
+ // @namespace rules can come after @layer and @import, but before any other rules
1391
+ // They cannot come before @import rules
1392
+ var firstImportIndex = -1;
1393
+ for (var i = 0; i < this.cssRules.length; i++) {
1394
+ if (this.cssRules[i].constructor.name === 'CSSImportRule') {
1395
+ firstImportIndex = i;
1396
+ break;
1397
+ }
1398
+ }
1399
+ var firstNonImportNamespaceIndex = findFirstNonConstructorIndex(this.cssRules, [
1400
+ 'CSSLayerStatementRule',
1401
+ 'CSSImportRule',
1402
+ 'CSSNamespaceRule'
1403
+ ]);
1404
+
1405
+ // Cannot insert before @import rules
1406
+ if (firstImportIndex !== -1 && index <= firstImportIndex) {
1407
+ errorUtils.throwError(this, 'DOMException',
1408
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1409
+ 'HierarchyRequestError');
1410
+ }
1411
+
1412
+ // Cannot insert after other types of rules
1413
+ if (index > firstNonImportNamespaceIndex) {
1414
+ errorUtils.throwError(this, 'DOMException',
1415
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1416
+ 'HierarchyRequestError');
1417
+ }
1418
+ } else if (cssRule.constructor.name === 'CSSLayerStatementRule') {
1419
+ // @layer statement rules can be inserted anywhere before @import and @namespace
1420
+ // No additional restrictions beyond what's already handled
1421
+ } else {
1422
+ // Any other rule cannot be inserted before @import and @namespace
1423
+ var firstNonSpecialRuleIndex = findFirstNonConstructorIndex(this.cssRules, [
1424
+ 'CSSLayerStatementRule',
1425
+ 'CSSImportRule',
1426
+ 'CSSNamespaceRule'
1427
+ ]);
1428
+
1429
+ if (index < firstNonSpecialRuleIndex) {
1430
+ errorUtils.throwError(this, 'DOMException',
1431
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
1432
+ 'HierarchyRequestError');
1433
+ }
1434
+ }
1435
+
1300
1436
  cssRule.__parentStyleSheet = this;
1301
1437
  this.cssRules.splice(index, 0, cssRule);
1302
1438
  return index;
@@ -1318,21 +1454,21 @@ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
1318
1454
  */
1319
1455
  CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
1320
1456
  if (index === undefined) {
1321
- errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSStyleSheet');
1457
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
1322
1458
  }
1323
1459
  index = Number(index);
1324
1460
  if (index < 0) {
1325
1461
  index = 4294967296 + index;
1326
1462
  }
1327
1463
  if (index >= this.cssRules.length) {
1328
- errorUtils.throwIndexError(this, 'deleteRule', 'CSSStyleSheet', index, this.cssRules.length);
1464
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
1329
1465
  }
1330
1466
  if (this.cssRules[index] && this.cssRules[index].constructor.name == "CSSNamespaceRule") {
1331
1467
  var shouldContinue = this.cssRules.every(function (rule) {
1332
1468
  return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
1333
1469
  });
1334
1470
  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");
1471
+ errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
1336
1472
  }
1337
1473
  }
1338
1474
  this.cssRules.splice(index, 1);
@@ -1616,7 +1752,7 @@ Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "style", {
1616
1752
  // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
1617
1753
  Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", {
1618
1754
  get: function() {
1619
- return this.keyText + " { " + this.style.cssText + " }";
1755
+ return this.keyText + " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
1620
1756
  }
1621
1757
  });
1622
1758
 
@@ -2150,10 +2286,16 @@ Object.defineProperties(CSSOM.CSSLayerStatementRule.prototype, {
2150
2286
 
2151
2287
 
2152
2288
  /**
2153
- * @param {string} token
2289
+ * Parses a CSS string and returns a CSSOM.CSSStyleSheet object representing the parsed stylesheet.
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.
2154
2296
  */
2155
- CSSOM.parse = function parse(token, errorHandler) {
2156
- errorHandler = errorHandler === undefined ? (console && console.error) : errorHandler;
2297
+ CSSOM.parse = function parse(token, opts, errorHandler) {
2298
+ errorHandler = errorHandler === true ? (console && console.error) : errorHandler;
2157
2299
 
2158
2300
  var i = 0;
2159
2301
 
@@ -2195,6 +2337,10 @@ CSSOM.parse = function parse(token, errorHandler) {
2195
2337
 
2196
2338
  var styleSheet = new CSSOM.CSSStyleSheet();
2197
2339
 
2340
+ if (opts && opts.globalObject) {
2341
+ styleSheet.__globalObject = opts.globalObject;
2342
+ }
2343
+
2198
2344
  // @type CSSStyleSheet|CSSMediaRule|CSSContainerRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
2199
2345
  var currentScope = styleSheet;
2200
2346
 
@@ -2892,7 +3038,7 @@ CSSOM.parse = function parse(token, errorHandler) {
2892
3038
  i += "font-face".length;
2893
3039
  fontFaceRule = new CSSOM.CSSFontFaceRule();
2894
3040
  fontFaceRule.__starts = i;
2895
- }, parentRule && parentRule.constructor.name === "CSSStyleRule" );
3041
+ }, true);
2896
3042
  break;
2897
3043
  } else {
2898
3044
  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
 
package/lib/CSSRule.js CHANGED
@@ -12,6 +12,27 @@ CSSOM.CSSRule = function CSSRule() {
12
12
  this.__parentStyleSheet = null;
13
13
  };
14
14
 
15
+ CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete
16
+ CSSOM.CSSRule.STYLE_RULE = 1;
17
+ CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete
18
+ CSSOM.CSSRule.IMPORT_RULE = 3;
19
+ CSSOM.CSSRule.MEDIA_RULE = 4;
20
+ CSSOM.CSSRule.FONT_FACE_RULE = 5;
21
+ CSSOM.CSSRule.PAGE_RULE = 6;
22
+ CSSOM.CSSRule.KEYFRAMES_RULE = 7;
23
+ CSSOM.CSSRule.KEYFRAME_RULE = 8;
24
+ CSSOM.CSSRule.MARGIN_RULE = 9;
25
+ CSSOM.CSSRule.NAMESPACE_RULE = 10;
26
+ CSSOM.CSSRule.COUNTER_STYLE_RULE = 11;
27
+ CSSOM.CSSRule.SUPPORTS_RULE = 12;
28
+ CSSOM.CSSRule.DOCUMENT_RULE = 13;
29
+ CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14;
30
+ CSSOM.CSSRule.VIEWPORT_RULE = 15;
31
+ CSSOM.CSSRule.REGION_STYLE_RULE = 16;
32
+ CSSOM.CSSRule.CONTAINER_RULE = 17;
33
+ CSSOM.CSSRule.LAYER_BLOCK_RULE = 18;
34
+ CSSOM.CSSRule.STARTING_STYLE_RULE = 1002;
35
+
15
36
  Object.defineProperties(CSSOM.CSSRule.prototype, {
16
37
 
17
38
  constructor: { value: CSSOM.CSSRule },
@@ -48,7 +69,7 @@ Object.defineProperties(CSSOM.CSSRule.prototype, {
48
69
  CONTAINER_RULE: { value: 17, enumerable: true },
49
70
  LAYER_BLOCK_RULE: { value: 18, enumerable: true },
50
71
  STARTING_STYLE_RULE: { value: 1002, enumerable: true },
51
- })
72
+ });
52
73
 
53
74
  //.CommonJS
54
75
  exports.CSSRule = CSSOM.CSSRule;
@@ -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.19",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"