diff --git a/cjs/html/style-element.js b/cjs/html/style-element.js
index 131e7372..78cf8a0f 100644
--- a/cjs/html/style-element.js
+++ b/cjs/html/style-element.js
@@ -1,5 +1,5 @@
'use strict';
-const {parse} = require('cssom');
+const {parse} = require('@acemir/cssom');
const {registerHTMLClass} = require('../shared/register-html-class.js');
const {SHEET} = require('../shared/symbols.js');
diff --git a/esm/html/style-element.js b/esm/html/style-element.js
index ce4111e9..9f23dde0 100644
--- a/esm/html/style-element.js
+++ b/esm/html/style-element.js
@@ -1,4 +1,4 @@
-import {parse} from 'cssom';
+import {parse} from '@acemir/cssom';
import {registerHTMLClass} from '../shared/register-html-class.js';
import {SHEET} from '../shared/symbols.js';
diff --git a/package-lock.json b/package-lock.json
index 837cac8b..53324613 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.18.13",
"license": "ISC",
"dependencies": {
+ "@acemir/cssom": "^0.9.32",
"css-select": "^7.0.0",
- "cssom": "^0.5.0",
"html-escaper": "^3.0.3",
"htmlparser2": "^10.1.0",
"uhyphen": "^0.2.0"
@@ -37,6 +37,12 @@
}
}
},
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.32",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.32.tgz",
+ "integrity": "sha512-OijrAUu7rrJhSIoHNg6LMPS5e344nwUHA+1EFYYjrh64D+GmytUsfBQjci45TjT+rd5Spsp8B5xsZDT+TgyXXQ==",
+ "license": "MIT"
+ },
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
@@ -894,9 +900,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
- "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1098,12 +1104,6 @@
"url": "https://github.com/sponsors/fb55"
}
},
- "node_modules/cssom": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
- "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
- "license": "MIT"
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1953,10 +1953,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -2605,16 +2615,16 @@
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
- "node": "18 || 20 || >=22"
+ "node": "20 || >=22"
}
},
"node_modules/test-exclude/node_modules/glob": {
diff --git a/package.json b/package.json
index 9d106714..d240c77b 100644
--- a/package.json
+++ b/package.json
@@ -70,8 +70,8 @@
}
},
"dependencies": {
+ "@acemir/cssom": "^0.9.32",
"css-select": "^7.0.0",
- "cssom": "^0.5.0",
"html-escaper": "^3.0.3",
"htmlparser2": "^10.1.0",
"uhyphen": "^0.2.0"
diff --git a/test/html/style-element.js b/test/html/style-element.js
index 0822d388..f50259b5 100644
--- a/test/html/style-element.js
+++ b/test/html/style-element.js
@@ -27,4 +27,14 @@ assert(style.sheet.cssRules.length === 1, true, 'style.sheet.cssRules.length');
assert(style.sheet.cssRules[0].selectorText, '.test');
assert(style.sheet.cssRules[0].style.color, 'white');
-assert(style.sheet.cssRules[0].style['font-size'], '12px');
\ No newline at end of file
+assert(style.sheet.cssRules[0].style['font-size'], '12px');
+
+document = parseHTML('').document;
+
+({firstElementChild: style} = document);
+assert(style.sheet.cssRules.length, 1);
+assert(style.sheet.cssRules[0].selectorText, '.parent');
+assert(style.sheet.cssRules[0].style.color, 'blue');
+assert(style.sheet.cssRules[0].cssRules.length, 1);
+assert(style.sheet.cssRules[0].cssRules[0].selectorText, '& .child');
+assert(style.sheet.cssRules[0].cssRules[0].style.color, 'red');
diff --git a/worker.js b/worker.js
index 2671c7f5..31bb78a1 100644
--- a/worker.js
+++ b/worker.js
@@ -6631,7 +6631,7 @@ const existsOne = (test, elements) => elements.some(
);
const getAttributeValue = (element, name) => name === 'class' ?
- element.classList.value : element.getAttribute(name);
+ (element.getAttributeNode('class')?.value ?? null) : element.getAttribute(name);
const getChildren = ({childNodes}) => childNodes;
@@ -8631,391 +8631,414 @@ class HTMLBodyElement extends HTMLElement {
}
}
-var lib = {};
-
-var CSSStyleDeclaration = {};
-
-var parse$1 = {};
-
-var CSSStyleSheet = {};
-
-var StyleSheet = {};
-
-var hasRequiredStyleSheet;
+var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
-function requireStyleSheet () {
- if (hasRequiredStyleSheet) return StyleSheet;
- hasRequiredStyleSheet = 1;
- //.CommonJS
- var CSSOM = {};
- ///CommonJS
+var lib = {};
+var CSSOM_1;
+var hasRequiredCSSOM;
- /**
- * @constructor
- * @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
- */
- CSSOM.StyleSheet = function StyleSheet() {
- this.parentStyleSheet = null;
+function requireCSSOM () {
+ if (hasRequiredCSSOM) return CSSOM_1;
+ hasRequiredCSSOM = 1;
+ var CSSOM = {
+ /**
+ * Creates and configures a new CSSOM instance with the specified options.
+ *
+ * @param {Object} opts - Configuration options for the CSSOM instance
+ * @param {Object} [opts.globalObject] - Optional global object to be assigned to CSSOM objects prototype
+ * @returns {Object} A new CSSOM instance with the applied configuration
+ * @description
+ * This method creates a new instance of CSSOM and optionally
+ * configures CSSStyleSheet with a global object reference. When a globalObject is provided
+ * and CSSStyleSheet exists on the instance, it creates a new CSSStyleSheet constructor
+ * using a factory function and assigns the globalObject to its prototype's __globalObject property.
+ */
+ setup: function (opts) {
+ var instance = Object.create(this);
+ if (opts.globalObject) {
+ if (instance.CSSStyleSheet) {
+ var factoryCSSStyleSheet = createFunctionFactory(instance.CSSStyleSheet);
+ var CSSStyleSheet = factoryCSSStyleSheet();
+ CSSStyleSheet.prototype.__globalObject = opts.globalObject;
+
+ instance.CSSStyleSheet = CSSStyleSheet;
+ }
+ }
+ return instance;
+ }
};
+ function createFunctionFactory(fn) {
+ return function() {
+ // Create a new function that delegates to the original
+ var newFn = function() {
+ return fn.apply(this, arguments);
+ };
- //.CommonJS
- StyleSheet.StyleSheet = CSSOM.StyleSheet;
- ///CommonJS
- return StyleSheet;
-}
-
-var CSSStyleRule = {};
-
-var CSSRule = {};
-
-var hasRequiredCSSRule;
-
-function requireCSSRule () {
- if (hasRequiredCSSRule) return CSSRule;
- hasRequiredCSSRule = 1;
- //.CommonJS
- var CSSOM = {};
- ///CommonJS
-
-
- /**
- * @constructor
- * @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule
- */
- CSSOM.CSSRule = function CSSRule() {
- this.parentRule = null;
- this.parentStyleSheet = null;
- };
-
- CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete
- CSSOM.CSSRule.STYLE_RULE = 1;
- CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete
- CSSOM.CSSRule.IMPORT_RULE = 3;
- CSSOM.CSSRule.MEDIA_RULE = 4;
- CSSOM.CSSRule.FONT_FACE_RULE = 5;
- CSSOM.CSSRule.PAGE_RULE = 6;
- CSSOM.CSSRule.KEYFRAMES_RULE = 7;
- CSSOM.CSSRule.KEYFRAME_RULE = 8;
- CSSOM.CSSRule.MARGIN_RULE = 9;
- CSSOM.CSSRule.NAMESPACE_RULE = 10;
- CSSOM.CSSRule.COUNTER_STYLE_RULE = 11;
- CSSOM.CSSRule.SUPPORTS_RULE = 12;
- CSSOM.CSSRule.DOCUMENT_RULE = 13;
- CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14;
- CSSOM.CSSRule.VIEWPORT_RULE = 15;
- CSSOM.CSSRule.REGION_STYLE_RULE = 16;
+ // Copy prototype chain
+ Object.setPrototypeOf(newFn, Object.getPrototypeOf(fn));
+ // Copy own properties
+ for (var key in fn) {
+ if (Object.prototype.hasOwnProperty.call(fn, key)) {
+ newFn[key] = fn[key];
+ }
+ }
- CSSOM.CSSRule.prototype = {
- constructor: CSSOM.CSSRule
- //FIXME
- };
+ // Clone the .prototype object for constructor-like behavior
+ if (fn.prototype) {
+ newFn.prototype = Object.create(fn.prototype);
+ }
+ return newFn;
+ };
+ }
//.CommonJS
- CSSRule.CSSRule = CSSOM.CSSRule;
+ CSSOM_1 = CSSOM;
///CommonJS
- return CSSRule;
+ return CSSOM_1;
}
-var hasRequiredCSSStyleRule;
+var errorUtils = {};
-function requireCSSStyleRule () {
- if (hasRequiredCSSStyleRule) return CSSStyleRule;
- hasRequiredCSSStyleRule = 1;
- //.CommonJS
- var CSSOM = {
- CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
- CSSRule: requireCSSRule().CSSRule
- };
- ///CommonJS
+var hasRequiredErrorUtils;
+function requireErrorUtils () {
+ if (hasRequiredErrorUtils) return errorUtils;
+ hasRequiredErrorUtils = 1;
+ // Utility functions for CSSOM error handling
/**
- * @constructor
- * @see http://dev.w3.org/csswg/cssom/#cssstylerule
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
+ * Gets the appropriate error constructor from the global object context.
+ * Tries to find the error constructor from parentStyleSheet.__globalObject,
+ * then from __globalObject, then falls back to the native constructor.
+ *
+ * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
+ * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
+ * @return {Function} The error constructor
*/
- CSSOM.CSSStyleRule = function CSSStyleRule() {
- CSSOM.CSSRule.call(this);
- this.selectorText = "";
- this.style = new CSSOM.CSSStyleDeclaration();
- this.style.parentRule = this;
- };
-
- CSSOM.CSSStyleRule.prototype = new CSSOM.CSSRule();
- CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
- CSSOM.CSSStyleRule.prototype.type = 1;
-
- Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
- get: function() {
- var text;
- if (this.selectorText) {
- text = this.selectorText + " {" + this.style.cssText + "}";
- } else {
- text = "";
- }
- return text;
- },
- set: function(cssText) {
- var rule = CSSOM.CSSStyleRule.parse(cssText);
- this.style = rule.style;
- this.selectorText = rule.selectorText;
+ function getErrorConstructor(context, errorType) {
+ // Try parentStyleSheet.__globalObject first
+ if (context.parentStyleSheet && context.parentStyleSheet.__globalObject && context.parentStyleSheet.__globalObject[errorType]) {
+ return context.parentStyleSheet.__globalObject[errorType];
}
- });
-
+
+ // Try __parentStyleSheet (alternative naming)
+ if (context.__parentStyleSheet && context.__parentStyleSheet.__globalObject && context.__parentStyleSheet.__globalObject[errorType]) {
+ return context.__parentStyleSheet.__globalObject[errorType];
+ }
+
+ // Try __globalObject on the context itself
+ if (context.__globalObject && context.__globalObject[errorType]) {
+ return context.__globalObject[errorType];
+ }
+
+ // Fall back to native constructor
+ return (typeof commonjsGlobal !== 'undefined' && commonjsGlobal[errorType]) ||
+ (typeof window !== 'undefined' && window[errorType]) ||
+ eval(errorType);
+ }
/**
- * NON-STANDARD
- * lightweight version of parse.js.
- * @param {string} ruleText
- * @return CSSStyleRule
+ * Creates an appropriate error with context-aware constructor.
+ *
+ * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
+ * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
+ * @param {string} message - The error message
+ * @param {string} [name] - Optional name for DOMException
*/
- CSSOM.CSSStyleRule.parse = function(ruleText) {
- var i = 0;
- var state = "selector";
- var index;
- var j = i;
- var buffer = "";
-
- var SIGNIFICANT_WHITESPACE = {
- "selector": true,
- "value": true
- };
-
- var styleRule = new CSSOM.CSSStyleRule();
- var name, priority="";
-
- for (var character; (character = ruleText.charAt(i)); i++) {
-
- switch (character) {
-
- case " ":
- case "\t":
- case "\r":
- case "\n":
- case "\f":
- if (SIGNIFICANT_WHITESPACE[state]) {
- // Squash 2 or more white-spaces in the row into 1
- switch (ruleText.charAt(i - 1)) {
- case " ":
- case "\t":
- case "\r":
- case "\n":
- case "\f":
- break;
- default:
- buffer += " ";
- break;
- }
- }
- break;
+ function createError(context, errorType, message, name) {
+ var ErrorConstructor = getErrorConstructor(context, errorType);
+ return new ErrorConstructor(message, name);
+ }
- // String
- case '"':
- j = i + 1;
- index = ruleText.indexOf('"', j) + 1;
- if (!index) {
- throw '" is missing';
- }
- buffer += ruleText.slice(i, index);
- i = index - 1;
- break;
-
- case "'":
- j = i + 1;
- index = ruleText.indexOf("'", j) + 1;
- if (!index) {
- throw "' is missing";
- }
- buffer += ruleText.slice(i, index);
- i = index - 1;
- break;
-
- // Comment
- case "/":
- if (ruleText.charAt(i + 1) === "*") {
- i += 2;
- index = ruleText.indexOf("*/", i);
- if (index === -1) {
- throw new SyntaxError("Missing */");
- } else {
- i = index + 1;
- }
- } else {
- buffer += character;
- }
- break;
-
- case "{":
- if (state === "selector") {
- styleRule.selectorText = buffer.trim();
- buffer = "";
- state = "name";
- }
- break;
-
- case ":":
- if (state === "name") {
- name = buffer.trim();
- buffer = "";
- state = "value";
- } else {
- buffer += character;
- }
- break;
-
- case "!":
- if (state === "value" && ruleText.indexOf("!important", i) === i) {
- priority = "important";
- i += "important".length;
- } else {
- buffer += character;
- }
- break;
-
- case ";":
- if (state === "value") {
- styleRule.style.setProperty(name, buffer.trim(), priority);
- priority = "";
- buffer = "";
- state = "name";
- } else {
- buffer += character;
- }
- break;
-
- case "}":
- if (state === "value") {
- styleRule.style.setProperty(name, buffer.trim(), priority);
- priority = "";
- buffer = "";
- } else if (state === "name") {
- break;
- } else {
- buffer += character;
- }
- state = "selector";
- break;
-
- default:
- buffer += character;
- break;
+ /**
+ * Creates and throws an appropriate error with context-aware constructor.
+ *
+ * @param {Object} context - The CSSOM object (rule, stylesheet, etc.)
+ * @param {string} errorType - The error type ('TypeError', 'RangeError', 'DOMException', etc.)
+ * @param {string} message - The error message
+ * @param {string} [name] - Optional name for DOMException
+ */
+ function throwError(context, errorType, message, name) {
+ throw createError(context, errorType, message, name);
+ }
- }
- }
+ /**
+ * Throws a TypeError for missing required arguments.
+ *
+ * @param {Object} context - The CSSOM object
+ * @param {string} methodName - The method name (e.g., 'appendRule')
+ * @param {string} objectName - The object name (e.g., 'CSSKeyframesRule')
+ * @param {number} [required=1] - Number of required arguments
+ * @param {number} [provided=0] - Number of provided arguments
+ */
+ function throwMissingArguments(context, methodName, objectName, required, provided) {
+ required = required || 1;
+ provided = provided || 0;
+ var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
+ required + " argument" + (required > 1 ? "s" : "") + " required, but only " +
+ provided + " present.";
+ throwError(context, 'TypeError', message);
+ }
- return styleRule;
+ /**
+ * Throws a DOMException for parse errors.
+ *
+ * @param {Object} context - The CSSOM object
+ * @param {string} methodName - The method name
+ * @param {string} objectName - The object name
+ * @param {string} rule - The rule that failed to parse
+ * @param {string} [name='SyntaxError'] - The DOMException name
+ */
+ function throwParseError(context, methodName, objectName, rule, name) {
+ var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
+ "Failed to parse the rule '" + rule + "'.";
+ throwError(context, 'DOMException', message, name || 'SyntaxError');
+ }
+ /**
+ * Throws a DOMException for index errors.
+ *
+ * @param {Object} context - The CSSOM object
+ * @param {string} methodName - The method name
+ * @param {string} objectName - The object name
+ * @param {number} index - The invalid index
+ * @param {number} maxIndex - The maximum valid index
+ * @param {string} [name='IndexSizeError'] - The DOMException name
+ */
+ function throwIndexError(context, methodName, objectName, index, maxIndex, name) {
+ var message = "Failed to execute '" + methodName + "' on '" + objectName + "': " +
+ "The index provided (" + index + ") is larger than the maximum index (" + maxIndex + ").";
+ throwError(context, 'DOMException', message, name || 'IndexSizeError');
+ }
+
+ var errorUtils$1 = {
+ createError: createError,
+ getErrorConstructor: getErrorConstructor,
+ throwError: throwError,
+ throwMissingArguments: throwMissingArguments,
+ throwParseError: throwParseError,
+ throwIndexError: throwIndexError
};
-
//.CommonJS
- CSSStyleRule.CSSStyleRule = CSSOM.CSSStyleRule;
+ errorUtils.errorUtils = errorUtils$1;
///CommonJS
- return CSSStyleRule;
+ return errorUtils;
}
-var hasRequiredCSSStyleSheet;
+var regexPatterns = {};
-function requireCSSStyleSheet () {
- if (hasRequiredCSSStyleSheet) return CSSStyleSheet;
- hasRequiredCSSStyleSheet = 1;
- //.CommonJS
- var CSSOM = {
- StyleSheet: requireStyleSheet().StyleSheet,
- CSSStyleRule: requireCSSStyleRule().CSSStyleRule
- };
- ///CommonJS
+var hasRequiredRegexPatterns;
+function requireRegexPatterns () {
+ if (hasRequiredRegexPatterns) return regexPatterns;
+ hasRequiredRegexPatterns = 1;
+ // Shared regex patterns for CSS parsing and validation
+ // These patterns are compiled once and reused across multiple files for better performance
+
+ // Regex patterns for CSS parsing
+ var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g; // Match @keyframes and vendor-prefixed @keyframes
+ var beforeRulePortionRegExp = /{(?!.*{)|}(?!.*})|;(?!.*;)|\*\/(?!.*\*\/)/g; // Match the closest allowed character (a opening or closing brace, a semicolon or a comment ending) before the rule
+ var beforeRuleValidationRegExp = /^[\s{};]*(\*\/\s*)?$/; // Match that the portion before the rule is empty or contains only whitespace, semicolons, opening/closing braces, and optionally a comment ending (*/) followed by whitespace
+ var forwardRuleValidationRegExp = /(?:\s|\/\*|\{|\()/; // Match that the rule is followed by any whitespace, a opening comment, a condition opening parenthesis or a opening brace
+ var forwardImportRuleValidationRegExp = /(?:\s|\/\*|'|")/; // Match that the rule is followed by any whitespace, an opening comment, a single quote or double quote
+ var forwardRuleClosingBraceRegExp = /{[^{}]*}|}/; // Finds the next closing brace of a rule block
+ var forwardRuleSemicolonAndOpeningBraceRegExp = /^.*?({|;)/; // Finds the next semicolon or opening brace after the at-rule
+
+ // Regex patterns for CSS selector validation and parsing
+ var cssCustomIdentifierRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates a css custom identifier
+ var startsWithCombinatorRegExp = /^\s*[>+~]/; // Checks if a selector starts with a CSS combinator (>, +, ~)
/**
- * @constructor
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
+ * Parse `@page` selectorText for page name and pseudo-pages
+ * Valid formats:
+ * - (empty - no name, no pseudo-page)
+ * - `:left`, `:right`, `:first`, `:blank` (pseudo-page only)
+ * - `named` (named page only)
+ * - `named:first` (named page with single pseudo-page)
+ * - `named:first:left` (named page with multiple pseudo-pages)
*/
- CSSOM.CSSStyleSheet = function CSSStyleSheet() {
- CSSOM.StyleSheet.call(this);
- this.cssRules = [];
- };
+ var atPageRuleSelectorRegExp = /^([^\s:]+)?((?::\w+)*)$/; // Validates @page rule selectors
+ // Regex patterns for CSSImportRule parsing
+ var layerRegExp = /layer\(([^)]*)\)/; // Matches layer() function in @import
+ var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/; // Validates layer name (same as custom identifier)
+ var doubleOrMoreSpacesRegExp = /\s{2,}/g; // Matches two or more consecutive whitespace characters
- CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
- CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
+ // Regex patterns for CSS escape sequences and identifiers
+ var startsWithHexEscapeRegExp = /^\\[0-9a-fA-F]/; // Checks if escape sequence starts with hex escape
+ var identStartCharRegExp = /[a-zA-Z_\u00A0-\uFFFF]/; // Valid identifier start character
+ var identCharRegExp = /^[a-zA-Z0-9_\-\u00A0-\uFFFF\\]/; // Valid identifier character
+ var specialCharsNeedEscapeRegExp = /[!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~\s]/; // Characters that need escaping
+ var combinatorOrSeparatorRegExp = /[\s>+~,()]/; // Selector boundaries and combinators
+ var afterHexEscapeSeparatorRegExp = /[\s>+~,(){}\[\]]/; // Characters that separate after hex escape
+ var trailingSpaceSeparatorRegExp = /[\s>+~,(){}]/; // Characters that allow trailing space
+ var endsWithHexEscapeRegExp = /\\[0-9a-fA-F]{1,6}\s+$/; // Matches selector ending with hex escape + space(s)
/**
- * Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
+ * Regular expression to detect invalid characters in the value portion of a CSS style declaration.
*
- * sheet = new Sheet("body {margin: 0}")
- * sheet.toString()
- * -> "body{margin:0;}"
- * sheet.insertRule("img {border: none}", 0)
- * -> 0
- * sheet.toString()
- * -> "img{border:none;}body{margin:0;}"
+ * This regex matches a colon (:) that is not inside parentheses and not inside single or double quotes.
+ * It is used to ensure that the value part of a CSS property does not contain unexpected colons,
+ * which would indicate a malformed declaration (e.g., "color: foo:bar;" is invalid).
*
- * @param {string} rule
- * @param {number} index
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
- * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
- */
- CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
- if (index < 0 || index > this.cssRules.length) {
- throw new RangeError("INDEX_SIZE_ERR");
- }
- var cssRule = CSSOM.parse(rule).cssRules[0];
- cssRule.parentStyleSheet = this;
- this.cssRules.splice(index, 0, cssRule);
- return index;
- };
-
-
- /**
- * Used to delete a rule from the style sheet.
+ * The negative lookahead `(?![^(]*\))` ensures that the colon is not followed by a closing
+ * parenthesis without encountering an opening parenthesis, effectively ignoring colons inside
+ * function-like values (e.g., `url(data:image/png;base64,...)`).
*
- * sheet = new Sheet("img{border:none} body{margin:0}")
- * sheet.toString()
- * -> "img{border:none;}body{margin:0;}"
- * sheet.deleteRule(0)
- * sheet.toString()
- * -> "body{margin:0;}"
+ * The lookahead `(?=(?:[^'"]|'[^']*'|"[^"]*")*$)` ensures that the colon is not inside single or double quotes,
+ * allowing colons within quoted strings (e.g., `content: ":";` or `background: url("foo:bar.png");`).
*
- * @param {number} index within the style sheet's rule list of the rule to remove.
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
- */
- CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
- if (index < 0 || index >= this.cssRules.length) {
- throw new RangeError("INDEX_SIZE_ERR");
- }
- this.cssRules.splice(index, 1);
- };
-
-
- /**
- * NON-STANDARD
- * @return {string} serialize stylesheet
+ * Example:
+ * - `color: red;` // valid, does not match
+ * - `background: url(data:image/png;base64,...);` // valid, does not match
+ * - `content: ':';` // valid, does not match
+ * - `color: foo:bar;` // invalid, matches
*/
- CSSOM.CSSStyleSheet.prototype.toString = function() {
- var result = "";
- var rules = this.cssRules;
- for (var i=0; i/; // Invalid <> combinator
+ var invalidCombinatorDoubleGtRegExp = />>/; // Invalid >> combinator
+ var consecutiveCombinatorsRegExp = /[>+~]\s*[>+~]/; // Invalid consecutive combinators
+ var invalidSlottedRegExp = /(?:^|[\s>+~,\[])slotted\s*\(/i; // Invalid slotted() without ::
+ var invalidPartRegExp = /(?:^|[\s>+~,\[])part\s*\(/i; // Invalid part() without ::
+ var invalidCueRegExp = /(?:^|[\s>+~,\[])cue\s*\(/i; // Invalid cue() without ::
+ var invalidCueRegionRegExp = /(?:^|[\s>+~,\[])cue-region\s*\(/i; // Invalid cue-region() without ::
+ var invalidNestingPattern = /&(?![.\#\[:>\+~\s])[a-zA-Z]/; // Invalid & followed by type selector
+ var emptyPseudoClassRegExp = /:(?:is|not|where|has)\(\s*\)/; // Empty pseudo-class like :is()
+ var whitespaceNormalizationRegExp = /(['"])(?:\\.|[^\\])*?\1|(\r\n|\r|\n)/g; // Normalize newlines outside quotes
+ var newlineRemovalRegExp = /\n/g; // Remove all newlines
+ var whitespaceAndDotRegExp = /[\s.]/; // Matches whitespace or dot
+ var declarationOrOpenBraceRegExp = /[{;}]/; // Matches declaration separator or open brace
+ var ampersandRegExp = /&/; // Matches nesting selector
+ var hexEscapeSequenceRegExp = /^([0-9a-fA-F]{1,6})[ \t\r\n\f]?/; // Matches hex escape sequence (1-6 hex digits optionally followed by whitespace)
+ var attributeCaseFlagRegExp = /^(.+?)\s+([is])$/i; // Matches case-sensitivity flag at end of attribute value
+ var prependedAmpersandRegExp = /^&\s+[:\\.]/; // Matches prepended ampersand pattern (& followed by space and : or .)
+ var openBraceGlobalRegExp = /{/g; // Matches opening braces (global)
+ var closeBraceGlobalRegExp = /}/g; // Matches closing braces (global)
+ var scopePreludeSplitRegExp = /\s*\)\s*to\s+\(/; // Splits scope prelude by ") to ("
+ var leadingWhitespaceRegExp = /^\s+/; // Matches leading whitespace (used to implement a ES5-compliant alternative to trimStart())
+ var doubleQuoteRegExp = /"/g; // Match all double quotes (for escaping in attribute values)
+ var backslashRegExp = /\\/g; // Match all backslashes (for escaping in attribute values)
+
+ // Media query validation patterns
+ var invalidMediaOperatorWithoutSpaceRegExp = /\)(and|or)\(/i; // )and( or )or( - missing space after operator
+ var invalidMediaNotAfterOperatorRegExp = /\b(and|or)\s+not\b/i; // and not or or not - not after operator
+ var invalidMediaNotIdentifierRegExp = /\)\s+not\(/i; // ) not( - not() identifier after media feature
+ var invalidMediaNotAfterFeatureRegExp = /\)\s+not\s+/i; // ) not - not operator after media feature (only valid at start or after comma)
+ var invalidMediaOperatorBetweenParensRegExp = /\)\s+(?!and\b|or\b)[a-z]+\s+\(/i; // ) xx ( - invalid operator between ) and (
+ var invalidMediaNotWithMediaTypeRegExp = /\bnot\s+[a-z][a-z0-9_-]*\s+\(/i; // not ( - missing 'and' between media type and feature
+
+ // Media query normalization patterns
+ var mediaFeatureColonRegExp = /(\(\s*)([^():]+):(\s*)([^()]*\))/g; // Match colon in media features
+ var identifierBeforeParenRegExp = /[a-z0-9_-]/i; // Match identifier character before opening paren
+ var mediaAndOperatorRegExp = /\s*\band\b\s*/gi; // Match 'and' operator with word boundaries
+ var mediaOrOperatorRegExp = /\s*\bor\b\s*/gi; // Match 'or' operator with word boundaries
+ var mediaNotOperatorStartRegExp = /^\s*not\s+(?=\S)/i; // Match 'not' operator at start
+ var mediaNotOperatorRegExp = /\s+\bnot\s+(?=\S)/gi; // Match 'not' operator after space
+
+ var regexPatterns$1 = {
+ // Parsing patterns
+ atKeyframesRegExp: atKeyframesRegExp,
+ beforeRulePortionRegExp: beforeRulePortionRegExp,
+ beforeRuleValidationRegExp: beforeRuleValidationRegExp,
+ forwardRuleValidationRegExp: forwardRuleValidationRegExp,
+ forwardImportRuleValidationRegExp: forwardImportRuleValidationRegExp,
+ forwardRuleClosingBraceRegExp: forwardRuleClosingBraceRegExp,
+ forwardRuleSemicolonAndOpeningBraceRegExp: forwardRuleSemicolonAndOpeningBraceRegExp,
+
+ // Selector validation patterns
+ cssCustomIdentifierRegExp: cssCustomIdentifierRegExp,
+ startsWithCombinatorRegExp: startsWithCombinatorRegExp,
+ atPageRuleSelectorRegExp: atPageRuleSelectorRegExp,
+
+ // Parsing patterns used in CSSImportRule
+ layerRegExp: layerRegExp,
+ layerRuleNameRegExp: layerRuleNameRegExp,
+ doubleOrMoreSpacesRegExp: doubleOrMoreSpacesRegExp,
+
+ // Escape sequence and identifier patterns
+ startsWithHexEscapeRegExp: startsWithHexEscapeRegExp,
+ identStartCharRegExp: identStartCharRegExp,
+ identCharRegExp: identCharRegExp,
+ specialCharsNeedEscapeRegExp: specialCharsNeedEscapeRegExp,
+ combinatorOrSeparatorRegExp: combinatorOrSeparatorRegExp,
+ afterHexEscapeSeparatorRegExp: afterHexEscapeSeparatorRegExp,
+ trailingSpaceSeparatorRegExp: trailingSpaceSeparatorRegExp,
+ endsWithHexEscapeRegExp: endsWithHexEscapeRegExp,
+
+ // Basic style property value validation
+ basicStylePropertyValueValidationRegExp: basicStylePropertyValueValidationRegExp,
+
+ // Attribute selector patterns
+ attributeSelectorContentRegExp: attributeSelectorContentRegExp,
+
+ // Selector validation patterns
+ pseudoElementRegExp: pseudoElementRegExp,
+ invalidCombinatorLtGtRegExp: invalidCombinatorLtGtRegExp,
+ invalidCombinatorDoubleGtRegExp: invalidCombinatorDoubleGtRegExp,
+ consecutiveCombinatorsRegExp: consecutiveCombinatorsRegExp,
+ invalidSlottedRegExp: invalidSlottedRegExp,
+ invalidPartRegExp: invalidPartRegExp,
+ invalidCueRegExp: invalidCueRegExp,
+ invalidCueRegionRegExp: invalidCueRegionRegExp,
+ invalidNestingPattern: invalidNestingPattern,
+ emptyPseudoClassRegExp: emptyPseudoClassRegExp,
+ whitespaceNormalizationRegExp: whitespaceNormalizationRegExp,
+ newlineRemovalRegExp: newlineRemovalRegExp,
+ whitespaceAndDotRegExp: whitespaceAndDotRegExp,
+ declarationOrOpenBraceRegExp: declarationOrOpenBraceRegExp,
+ ampersandRegExp: ampersandRegExp,
+ hexEscapeSequenceRegExp: hexEscapeSequenceRegExp,
+ attributeCaseFlagRegExp: attributeCaseFlagRegExp,
+ prependedAmpersandRegExp: prependedAmpersandRegExp,
+ openBraceGlobalRegExp: openBraceGlobalRegExp,
+ closeBraceGlobalRegExp: closeBraceGlobalRegExp,
+ scopePreludeSplitRegExp: scopePreludeSplitRegExp,
+ leadingWhitespaceRegExp: leadingWhitespaceRegExp,
+ doubleQuoteRegExp: doubleQuoteRegExp,
+ backslashRegExp: backslashRegExp,
+
+ // Media query validation patterns
+ invalidMediaOperatorWithoutSpaceRegExp: invalidMediaOperatorWithoutSpaceRegExp,
+ invalidMediaNotAfterOperatorRegExp: invalidMediaNotAfterOperatorRegExp,
+ invalidMediaNotIdentifierRegExp: invalidMediaNotIdentifierRegExp,
+ invalidMediaNotAfterFeatureRegExp: invalidMediaNotAfterFeatureRegExp,
+ invalidMediaOperatorBetweenParensRegExp: invalidMediaOperatorBetweenParensRegExp,
+ invalidMediaNotWithMediaTypeRegExp: invalidMediaNotWithMediaTypeRegExp,
+
+ // Media query normalization patterns
+ mediaFeatureColonRegExp: mediaFeatureColonRegExp,
+ identifierBeforeParenRegExp: identifierBeforeParenRegExp,
+ mediaAndOperatorRegExp: mediaAndOperatorRegExp,
+ mediaOrOperatorRegExp: mediaOrOperatorRegExp,
+ mediaNotOperatorStartRegExp: mediaNotOperatorStartRegExp,
+ mediaNotOperatorRegExp: mediaNotOperatorRegExp
};
-
//.CommonJS
- CSSStyleSheet.CSSStyleSheet = CSSOM.CSSStyleSheet;
- CSSOM.parse = requireParse().parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleSheet.js
+ regexPatterns.regexPatterns = regexPatterns$1;
///CommonJS
- return CSSStyleSheet;
+ return regexPatterns;
}
-var CSSImportRule = {};
+var CSSStyleDeclaration = {};
+
+var parse$1 = {};
+
+var CSSStyleSheet = {};
var MediaList = {};
@@ -9052,10 +9075,20 @@ function requireMediaList () {
* @param {string} value
*/
set mediaText(value) {
- var values = value.split(",");
- var length = this.length = values.length;
- for (var i=0; i this.cssRules.length) {
- throw new RangeError("INDEX_SIZE_ERR");
+ if (rule === undefined && index === undefined) {
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
+ }
+ if (index === void 0) {
+ index = 0;
+ }
+ index = Number(index);
+ if (index < 0) {
+ index = 4294967296 + index;
+ }
+ if (index > this.cssRules.length) {
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
+ }
+ var ruleToParse = processedRuleToParse = String(rule);
+ ruleToParse = ruleToParse.trim().replace(/^\/\*[\s\S]*?\*\/\s*/, "");
+ var isNestedSelector = this.constructor.name === "CSSStyleRule";
+ if (isNestedSelector === false) {
+ var currentRule = this;
+ while (currentRule.parentRule) {
+ currentRule = currentRule.parentRule;
+ if (currentRule.constructor.name === "CSSStyleRule") {
+ isNestedSelector = true;
+ break;
+ }
+ }
+ }
+ if (isNestedSelector) {
+ processedRuleToParse = 's { n { } ' + ruleToParse + '}';
+ }
+ var isScopeRule = this.constructor.name === "CSSScopeRule";
+ if (isScopeRule) {
+ if (isNestedSelector) {
+ processedRuleToParse = 's { ' + '@scope {' + ruleToParse + '}}';
+ } else {
+ processedRuleToParse = '@scope {' + ruleToParse + '}';
+ }
+ }
+ var parsedRules = new CSSOM.CSSRuleList();
+ CSSOM.parse(processedRuleToParse, {
+ styleSheet: this.parentStyleSheet,
+ cssRules: parsedRules
+ });
+ if (isScopeRule) {
+ if (isNestedSelector) {
+ parsedRules = parsedRules[0].cssRules[0].cssRules;
+ } else {
+ parsedRules = parsedRules[0].cssRules;
+ }
+ }
+ if (isNestedSelector) {
+ parsedRules = parsedRules[0].cssRules.slice(1);
+ }
+ if (parsedRules.length !== 1) {
+ if (isNestedSelector && parsedRules.length === 0 && ruleToParse.indexOf('@font-face') === 0) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
+ "Only conditional nested group rules, style rules, @scope rules, @apply rules, and nested declaration rules may be nested.",
+ 'HierarchyRequestError');
+ } else {
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
+ }
+ }
+ var cssRule = parsedRules[0];
+
+ if (cssRule.constructor.name === 'CSSNestedDeclarations' && cssRule.style.length === 0) {
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
+ }
+
+ // Check for rules that cannot be inserted inside a CSSGroupingRule
+ if (cssRule.constructor.name === 'CSSImportRule' || cssRule.constructor.name === 'CSSNamespaceRule') {
+ var ruleKeyword = cssRule.constructor.name === 'CSSImportRule' ? '@import' : '@namespace';
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': " +
+ "'" + ruleKeyword + "' rules cannot be inserted inside a group rule.",
+ 'HierarchyRequestError');
+ }
+
+ // Check for CSSLayerStatementRule (@layer statement rules)
+ if (cssRule.constructor.name === 'CSSLayerStatementRule') {
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
}
- var cssRule = CSSOM.parse(rule).cssRules[0];
- cssRule.parentRule = this;
+
+ cssRule.__parentRule = this;
this.cssRules.splice(index, 0, cssRule);
return index;
};
@@ -9294,10 +9494,19 @@ function requireCSSGroupingRule () {
* @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
*/
CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
- if (index < 0 || index >= this.cssRules.length) {
- throw new RangeError("INDEX_SIZE_ERR");
+ if (index === undefined) {
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
}
- this.cssRules.splice(index, 1)[0].parentRule = null;
+ index = Number(index);
+ if (index < 0) {
+ index = 4294967296 + index;
+ }
+ if (index >= this.cssRules.length) {
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
+ }
+ this.cssRules[index].__parentRule = null;
+ this.cssRules[index].__parentStyleSheet = null;
+ this.cssRules.splice(index, 1);
};
//.CommonJS
@@ -9306,1315 +9515,6159 @@ function requireCSSGroupingRule () {
return CSSGroupingRule;
}
-var CSSMediaRule = {};
-
-var CSSConditionRule = {};
-
-var hasRequiredCSSConditionRule;
+var hasRequiredCSSStyleRule;
-function requireCSSConditionRule () {
- if (hasRequiredCSSConditionRule) return CSSConditionRule;
- hasRequiredCSSConditionRule = 1;
+function requireCSSStyleRule () {
+ if (hasRequiredCSSStyleRule) return CSSStyleRule;
+ hasRequiredCSSStyleRule = 1;
//.CommonJS
var CSSOM = {
- CSSRule: requireCSSRule().CSSRule,
- CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule
+ CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
+ CSSRule: requireCSSRule().CSSRule,
+ CSSRuleList: requireCSSRuleList().CSSRuleList,
+ CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
};
+ var regexPatterns = requireRegexPatterns().regexPatterns;
+ // Use cssstyle if available
+ try {
+ CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
+ } catch (e) {
+ // ignore
+ }
///CommonJS
/**
* @constructor
- * @see https://www.w3.org/TR/css-conditional-3/#the-cssconditionrule-interface
+ * @see http://dev.w3.org/csswg/cssom/#cssstylerule
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
*/
- CSSOM.CSSConditionRule = function CSSConditionRule() {
- CSSOM.CSSGroupingRule.call(this);
- this.cssRules = [];
+ CSSOM.CSSStyleRule = function CSSStyleRule() {
+ CSSOM.CSSGroupingRule.call(this);
+ this.__selectorText = "";
+ this.__style = new CSSOM.CSSStyleDeclaration();
+ this.__style.parentRule = this;
};
- CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
- CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
- CSSOM.CSSConditionRule.prototype.conditionText = '';
- CSSOM.CSSConditionRule.prototype.cssText = '';
+ CSSOM.CSSStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype);
+ CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
- //.CommonJS
- CSSConditionRule.CSSConditionRule = CSSOM.CSSConditionRule;
- ///CommonJS
- return CSSConditionRule;
+ Object.setPrototypeOf(CSSOM.CSSStyleRule, CSSOM.CSSGroupingRule);
+
+ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "type", {
+ value: 1,
+ writable: false
+ });
+
+ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "selectorText", {
+ get: function() {
+ return this.__selectorText;
+ },
+ set: function(value) {
+ if (typeof value === "string") {
+ // Don't trim if the value ends with a hex escape sequence followed by space
+ // (e.g., ".\31 " where the space is part of the escape terminator)
+ var endsWithHexEscapeRegExp = regexPatterns.endsWithHexEscapeRegExp;
+ var endsWithEscape = endsWithHexEscapeRegExp.test(value);
+ var trimmedValue = endsWithEscape ? value.replace(/\s+$/, ' ').trimStart() : value.trim();
+
+ if (trimmedValue === '') {
+ return;
+ }
+
+ // TODO: Setting invalid selectorText should be ignored
+ // There are some validations already on lib/parse.js
+ // but the same validations should be applied here.
+ // Check if we can move these validation logic to a shared function.
+
+ this.__selectorText = trimmedValue;
+ }
+ },
+ configurable: true
+ });
+
+ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "style", {
+ get: function() {
+ return this.__style;
+ },
+ set: function(value) {
+ if (typeof value === "string") {
+ this.__style.cssText = value;
+ } else {
+ this.__style = value;
+ }
+ },
+ configurable: true
+ });
+
+ Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
+ get: function() {
+ var text;
+ if (this.selectorText) {
+ var values = "";
+ if (this.cssRules.length) {
+ var valuesArr = [" {"];
+ this.style.cssText && valuesArr.push(this.style.cssText);
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
+ if (rule.cssText !== "") {
+ acc.push(rule.cssText);
+ }
+ return acc;
+ }, []).join("\n "));
+ values = valuesArr.join("\n ") + "\n}";
+ } else {
+ values = " {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
+ }
+ text = this.selectorText + values;
+ } else {
+ text = "";
+ }
+ return text;
+ }
+ });
+
+ //.CommonJS
+ CSSStyleRule.CSSStyleRule = CSSOM.CSSStyleRule;
+ ///CommonJS
+ return CSSStyleRule;
}
-var hasRequiredCSSMediaRule;
+var hasRequiredCSSStyleSheet;
-function requireCSSMediaRule () {
- if (hasRequiredCSSMediaRule) return CSSMediaRule;
- hasRequiredCSSMediaRule = 1;
+function requireCSSStyleSheet () {
+ if (hasRequiredCSSStyleSheet) return CSSStyleSheet;
+ hasRequiredCSSStyleSheet = 1;
//.CommonJS
var CSSOM = {
- CSSRule: requireCSSRule().CSSRule,
- CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
- CSSConditionRule: requireCSSConditionRule().CSSConditionRule,
- MediaList: requireMediaList().MediaList
+ MediaList: requireMediaList().MediaList,
+ StyleSheet: requireStyleSheet().StyleSheet,
+ CSSRuleList: requireCSSRuleList().CSSRuleList,
+ CSSStyleRule: requireCSSStyleRule().CSSStyleRule,
};
+ var errorUtils = requireErrorUtils().errorUtils;
///CommonJS
/**
* @constructor
- * @see http://dev.w3.org/csswg/cssom/#cssmediarule
- * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule
+ * @param {CSSStyleSheetInit} [opts] - CSSStyleSheetInit options.
+ * @param {string} [opts.baseURL] - The base URL of the stylesheet.
+ * @param {boolean} [opts.disabled] - The disabled attribute of the stylesheet.
+ * @param {MediaList | string} [opts.media] - The media attribute of the stylesheet.
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
*/
- CSSOM.CSSMediaRule = function CSSMediaRule() {
- CSSOM.CSSConditionRule.call(this);
- this.media = new CSSOM.MediaList();
+ CSSOM.CSSStyleSheet = function CSSStyleSheet(opts) {
+ CSSOM.StyleSheet.call(this);
+ this.__constructed = true;
+ this.__cssRules = new CSSOM.CSSRuleList();
+ this.__ownerRule = null;
+
+ if (opts && typeof opts === "object") {
+ if (opts.baseURL && typeof opts.baseURL === "string") {
+ this.__baseURL = opts.baseURL;
+ }
+ if (opts.media && typeof opts.media === "string") {
+ this.media.mediaText = opts.media;
+ }
+ if (typeof opts.disabled === "boolean") {
+ this.disabled = opts.disabled;
+ }
+ }
};
- CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
- CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
- CSSOM.CSSMediaRule.prototype.type = 4;
- // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
- Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
- "conditionText": {
- get: function() {
- return this.media.mediaText;
- },
- set: function(value) {
- this.media.mediaText = value;
- },
- configurable: true,
- enumerable: true
- },
- "cssText": {
- get: function() {
- var cssTexts = [];
- for (var i=0, length=this.cssRules.length; i < length; i++) {
- cssTexts.push(this.cssRules[i].cssText);
- }
- return "@media " + this.media.mediaText + " {" + cssTexts.join("") + "}";
- },
- configurable: true,
- enumerable: true
- }
+ CSSOM.CSSStyleSheet.prototype = Object.create(CSSOM.StyleSheet.prototype);
+ CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
+
+ Object.setPrototypeOf(CSSOM.CSSStyleSheet, CSSOM.StyleSheet);
+
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "cssRules", {
+ get: function() {
+ return this.__cssRules;
+ }
+ });
+
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "rules", {
+ get: function() {
+ return this.__cssRules;
+ }
+ });
+
+ Object.defineProperty(CSSOM.CSSStyleSheet.prototype, "ownerRule", {
+ get: function() {
+ return this.__ownerRule;
+ }
});
+ /**
+ * Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
+ *
+ * sheet = new Sheet("body {margin: 0}")
+ * sheet.toString()
+ * -> "body{margin:0;}"
+ * sheet.insertRule("img {border: none}", 0)
+ * -> 0
+ * sheet.toString()
+ * -> "img{border:none;}body{margin:0;}"
+ *
+ * @param {string} rule
+ * @param {number} [index=0]
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
+ * @return {number} The index within the style sheet's rule collection of the newly inserted rule.
+ */
+ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule, index) {
+ if (rule === undefined && index === undefined) {
+ errorUtils.throwMissingArguments(this, 'insertRule', this.constructor.name);
+ }
+ if (index === void 0) {
+ index = 0;
+ }
+ index = Number(index);
+ if (index < 0) {
+ index = 4294967296 + index;
+ }
+ if (index > this.cssRules.length) {
+ errorUtils.throwIndexError(this, 'insertRule', this.constructor.name, index, this.cssRules.length);
+ }
+
+ var ruleToParse = String(rule);
+ var parseErrors = [];
+ var parsedSheet = CSSOM.parse(ruleToParse, undefined, function(err) {
+ parseErrors.push(err);
+ } );
+ if (parsedSheet.cssRules.length !== 1) {
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
+ }
+ var cssRule = parsedSheet.cssRules[0];
+
+ // Helper function to find the first index of a rule that's NOT of specified constructors
+ function findFirstNonConstructorIndex(rules, constructorNames) {
+ for (var i = 0; i < rules.length; i++) {
+ if (constructorNames.indexOf(rules[i].constructor.name) === -1) {
+ return i;
+ }
+ }
+ return rules.length;
+ }
+
+ // Validate rule ordering based on CSS specification
+ if (cssRule.constructor.name === 'CSSImportRule') {
+ if (this.__constructed === true) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Can't insert @import rules into a constructed stylesheet.",
+ 'SyntaxError');
+ }
+ // @import rules cannot be inserted after @layer rules that already exist
+ // They can only be inserted at the beginning or after other @import rules
+ var firstLayerIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
+ if (firstLayerIndex < this.cssRules.length && this.cssRules[firstLayerIndex].constructor.name === 'CSSLayerStatementRule' && index > firstLayerIndex) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'HierarchyRequestError');
+ }
+
+ // Also cannot insert after @namespace or other rules
+ var firstNonImportIndex = findFirstNonConstructorIndex(this.cssRules, ['CSSImportRule']);
+ if (index > firstNonImportIndex && firstNonImportIndex < this.cssRules.length &&
+ this.cssRules[firstNonImportIndex].constructor.name !== 'CSSLayerStatementRule') {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'HierarchyRequestError');
+ }
+ } else if (cssRule.constructor.name === 'CSSNamespaceRule') {
+ // @namespace rules can come after @layer and @import, but before any other rules
+ // They cannot come before @import rules
+ var firstImportIndex = -1;
+ for (var i = 0; i < this.cssRules.length; i++) {
+ if (this.cssRules[i].constructor.name === 'CSSImportRule') {
+ firstImportIndex = i;
+ break;
+ }
+ }
+ var firstNonImportNamespaceIndex = findFirstNonConstructorIndex(this.cssRules, [
+ 'CSSLayerStatementRule',
+ 'CSSImportRule',
+ 'CSSNamespaceRule'
+ ]);
+
+ // Cannot insert before @import rules
+ if (firstImportIndex !== -1 && index <= firstImportIndex) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'HierarchyRequestError');
+ }
+
+ // Cannot insert if there are already non-special rules
+ if (firstNonImportNamespaceIndex < this.cssRules.length) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'InvalidStateError');
+ }
+
+ // Cannot insert after other types of rules
+ if (index > firstNonImportNamespaceIndex) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'HierarchyRequestError');
+ }
+
+
+ } else if (cssRule.constructor.name === 'CSSLayerStatementRule') ; else {
+ // Any other rule cannot be inserted before @import and @namespace
+ var firstNonSpecialRuleIndex = findFirstNonConstructorIndex(this.cssRules, [
+ 'CSSLayerStatementRule',
+ 'CSSImportRule',
+ 'CSSNamespaceRule'
+ ]);
+
+ if (index < firstNonSpecialRuleIndex) {
+ errorUtils.throwError(this, 'DOMException',
+ "Failed to execute 'insertRule' on '" + this.constructor.name + "': Failed to insert the rule.",
+ 'HierarchyRequestError');
+ }
+
+ if (parseErrors.filter(function(error) { return !error.isNested; }).length !== 0) {
+ errorUtils.throwParseError(this, 'insertRule', this.constructor.name, ruleToParse, 'SyntaxError');
+ }
+ }
+
+ cssRule.__parentStyleSheet = this;
+ this.cssRules.splice(index, 0, cssRule);
+ return index;
+ };
+
+ CSSOM.CSSStyleSheet.prototype.addRule = function(selector, styleBlock, index) {
+ if (index === void 0) {
+ index = this.cssRules.length;
+ }
+ this.insertRule(selector + "{" + styleBlock + "}", index);
+ return -1;
+ };
+
+ /**
+ * Used to delete a rule from the style sheet.
+ *
+ * sheet = new Sheet("img{border:none} body{margin:0}")
+ * sheet.toString()
+ * -> "img{border:none;}body{margin:0;}"
+ * sheet.deleteRule(0)
+ * sheet.toString()
+ * -> "body{margin:0;}"
+ *
+ * @param {number} index within the style sheet's rule list of the rule to remove.
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
+ */
+ CSSOM.CSSStyleSheet.prototype.deleteRule = function(index) {
+ if (index === undefined) {
+ errorUtils.throwMissingArguments(this, 'deleteRule', this.constructor.name);
+ }
+ index = Number(index);
+ if (index < 0) {
+ index = 4294967296 + index;
+ }
+ if (index >= this.cssRules.length) {
+ errorUtils.throwIndexError(this, 'deleteRule', this.constructor.name, index, this.cssRules.length);
+ }
+ if (this.cssRules[index]) {
+ if (this.cssRules[index].constructor.name == "CSSNamespaceRule") {
+ var shouldContinue = this.cssRules.every(function (rule) {
+ return ['CSSImportRule','CSSLayerStatementRule','CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
+ });
+ if (!shouldContinue) {
+ errorUtils.throwError(this, 'DOMException', "Failed to execute 'deleteRule' on '" + this.constructor.name + "': Failed to delete rule.", "InvalidStateError");
+ }
+ }
+ if (this.cssRules[index].constructor.name == "CSSImportRule") {
+ this.cssRules[index].styleSheet.__parentStyleSheet = null;
+ }
+
+ this.cssRules[index].__parentStyleSheet = null;
+ }
+ this.cssRules.splice(index, 1);
+ };
+
+ CSSOM.CSSStyleSheet.prototype.removeRule = function(index) {
+ if (index === void 0) {
+ index = 0;
+ }
+ this.deleteRule(index);
+ };
+
+
+ /**
+ * Replaces the rules of a {@link CSSStyleSheet}
+ *
+ * @returns a promise
+ * @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replace
+ */
+ CSSOM.CSSStyleSheet.prototype.replace = function(text) {
+ var _Promise;
+ if (this.__globalObject && this.__globalObject['Promise']) {
+ _Promise = this.__globalObject['Promise'];
+ } else {
+ _Promise = Promise;
+ }
+ var _setTimeout;
+ if (this.__globalObject && this.__globalObject['setTimeout']) {
+ _setTimeout = this.__globalObject['setTimeout'];
+ } else {
+ _setTimeout = setTimeout;
+ }
+ var sheet = this;
+ return new _Promise(function (resolve, reject) {
+ // If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
+ if (!sheet.__constructed || sheet.__disallowModification) {
+ reject(errorUtils.createError(sheet, 'DOMException',
+ "Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
+ 'NotAllowedError'));
+ }
+ // Set the disallow modification flag.
+ sheet.__disallowModification = true;
+
+ // In parallel, do these steps:
+ _setTimeout(function() {
+ // Let rules be the result of running parse a stylesheet's contents from text.
+ var rules = new CSSOM.CSSRuleList();
+ CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
+ // If rules contains one or more @import rules, remove those rules from rules.
+ var i = 0;
+ while (i < rules.length) {
+ if (rules[i].constructor.name === 'CSSImportRule') {
+ rules.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+ // Set sheet's CSS rules to rules.
+ sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
+ // Unset sheet’s disallow modification flag.
+ delete sheet.__disallowModification;
+ // Resolve promise with sheet.
+ resolve(sheet);
+ });
+ });
+ };
+
+ /**
+ * Synchronously replaces the rules of a {@link CSSStyleSheet}
+ *
+ * @see https://www.w3.org/TR/cssom-1/#dom-cssstylesheet-replacesync
+ */
+ CSSOM.CSSStyleSheet.prototype.replaceSync = function(text) {
+ var sheet = this;
+ // If the constructed flag is not set, or the disallow modification flag is set, throw a NotAllowedError DOMException.
+ if (!sheet.__constructed || sheet.__disallowModification) {
+ errorUtils.throwError(sheet, 'DOMException',
+ "Failed to execute 'replaceSync' on '" + sheet.constructor.name + "': Not allowed.",
+ 'NotAllowedError');
+ }
+ // Let rules be the result of running parse a stylesheet's contents from text.
+ var rules = new CSSOM.CSSRuleList();
+ CSSOM.parse(text, { styleSheet: sheet, cssRules: rules });
+ // If rules contains one or more @import rules, remove those rules from rules.
+ var i = 0;
+ while (i < rules.length) {
+ if (rules[i].constructor.name === 'CSSImportRule') {
+ rules.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+ // Set sheet's CSS rules to rules.
+ sheet.__cssRules.splice.apply(sheet.__cssRules, [0, sheet.__cssRules.length].concat(rules));
+ };
+
+ /**
+ * NON-STANDARD
+ * @return {string} serialize stylesheet
+ */
+ CSSOM.CSSStyleSheet.prototype.toString = function() {
+ var result = "";
+ var rules = this.cssRules;
+ for (var i=0; i 1000 ? '1000px' : 'auto');
- * }
+ * NON-STANDARD
+ * Rule text parser.
+ * @param {string} cssText
*/
- CSSOM.CSSValueExpression.prototype.parse = function() {
- var token = this._token,
- idx = this._idx;
+ Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "parse", {
+ value: function(cssText) {
+ // Extract the name from "@counter-style { ... }"
+ var match = cssText.match(/@counter-style\s+([^\s{]+)\s*\{([^]*)\}/);
+ if (match) {
+ this.name = match[1];
+ // Get the text inside the brackets and clean it up
+ var propsText = match[2];
+ this.__props = propsText.trim().replace(/\n/g, " ").replace(/(['"])(?:\\.|[^\\])*?\1|(\s{2,})/g, function (match, quote) {
+ return quote ? match : ' ';
+ });
+ }
+ }
+ });
- var character = '',
- expression = '',
- error = '',
- info,
- paren = [];
+ //.CommonJS
+ CSSCounterStyleRule.CSSCounterStyleRule = CSSOM.CSSCounterStyleRule;
+ ///CommonJS
+ return CSSCounterStyleRule;
+}
+var CSSPropertyRule = {};
- for (; ; ++idx) {
- character = token.charAt(idx);
+var hasRequiredCSSPropertyRule;
- // end of token
- if (character === '') {
- error = 'css expression error: unfinished expression!';
- break;
- }
+function requireCSSPropertyRule () {
+ if (hasRequiredCSSPropertyRule) return CSSPropertyRule;
+ hasRequiredCSSPropertyRule = 1;
+ //.CommonJS
+ var CSSOM = {
+ CSSRule: requireCSSRule().CSSRule
+ };
+ ///CommonJS
- switch(character) {
- case '(':
- paren.push(character);
- expression += character;
- break;
- case ')':
- paren.pop(character);
- expression += character;
- break;
+ /**
+ * @constructor
+ * @see https://drafts.css-houdini.org/css-properties-values-api/#the-css-property-rule-interface
+ */
+ CSSOM.CSSPropertyRule = function CSSPropertyRule() {
+ CSSOM.CSSRule.call(this);
+ this.__name = "";
+ this.__syntax = "";
+ this.__inherits = false;
+ this.__initialValue = null;
+ };
- case '/':
- if ((info = this._parseJSComment(token, idx))) { // comment?
- if (info.error) {
- error = 'css expression error: unfinished comment in expression!';
- } else {
- idx = info.idx;
- // ignore the comment
- }
- } else if ((info = this._parseJSRexExp(token, idx))) { // regexp
- idx = info.idx;
- expression += info.text;
- } else { // other
- expression += character;
- }
- break;
+ CSSOM.CSSPropertyRule.prototype = Object.create(CSSOM.CSSRule.prototype);
+ CSSOM.CSSPropertyRule.prototype.constructor = CSSOM.CSSPropertyRule;
- case "'":
- case '"':
- info = this._parseJSString(token, idx, character);
- if (info) { // string
- idx = info.idx;
- expression += info.text;
- } else {
- expression += character;
- }
- break;
+ Object.setPrototypeOf(CSSOM.CSSPropertyRule, CSSOM.CSSRule);
- default:
- expression += character;
- break;
- }
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "type", {
+ value: 0,
+ writable: false
+ });
- if (error) {
- break;
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "cssText", {
+ get: function() {
+ var text = "@property " + this.name + " {";
+ if (this.syntax !== "") {
+ text += " syntax: \"" + this.syntax.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\";";
}
-
- // end of expression
- if (paren.length === 0) {
- break;
+ text += " inherits: " + (this.inherits ? "true" : "false") + ";";
+ if (this.initialValue !== null) {
+ text += " initial-value: " + this.initialValue + ";";
}
+ text += " }";
+ return text;
}
+ });
- var ret;
- if (error) {
- ret = {
- error: error
- };
- } else {
- ret = {
- idx: idx,
- expression: expression
- };
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "name", {
+ get: function() {
+ return this.__name;
}
+ });
- return ret;
- };
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "syntax", {
+ get: function() {
+ return this.__syntax;
+ }
+ });
+
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "inherits", {
+ get: function() {
+ return this.__inherits;
+ }
+ });
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "initialValue", {
+ get: function() {
+ return this.__initialValue;
+ }
+ });
/**
- *
- * @return {Object|false}
- * - idx:
- * - text:
- * or
- * - error:
- * or
- * false
- *
+ * NON-STANDARD
+ * Rule text parser.
+ * @param {string} cssText
+ * @returns {boolean} True if the rule is valid and was parsed successfully
*/
- CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) {
- var nextChar = token.charAt(idx + 1),
- text;
-
- if (nextChar === '/' || nextChar === '*') {
- var startIdx = idx,
- endIdx,
- commentEndChar;
-
- if (nextChar === '/') { // line comment
- commentEndChar = '\n';
- } else if (nextChar === '*') { // block comment
- commentEndChar = '*/';
+ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "parse", {
+ value: function(cssText) {
+ // Extract the name from "@property { ... }"
+ var match = cssText.match(/@property\s+(--[^\s{]+)\s*\{([^]*)\}/);
+ if (!match) {
+ return false;
}
-
- endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1);
- if (endIdx !== -1) {
- endIdx = endIdx + commentEndChar.length - 1;
- text = token.substring(idx, endIdx + 1);
- return {
- idx: endIdx,
- text: text
- };
+
+ this.__name = match[1];
+ var bodyText = match[2];
+
+ // Parse syntax descriptor (REQUIRED)
+ var syntaxMatch = bodyText.match(/syntax\s*:\s*(['"])([^]*?)\1\s*;/);
+ if (!syntaxMatch) {
+ return false; // syntax is required
+ }
+ this.__syntax = syntaxMatch[2];
+
+ // Syntax cannot be empty
+ if (this.__syntax === "") {
+ return false;
+ }
+
+ // Parse inherits descriptor (REQUIRED)
+ var inheritsMatch = bodyText.match(/inherits\s*:\s*(true|false)\s*;/);
+ if (!inheritsMatch) {
+ return false; // inherits is required
+ }
+ this.__inherits = inheritsMatch[1] === "true";
+
+ // Parse initial-value descriptor (OPTIONAL, but required if syntax is not "*")
+ var initialValueMatch = bodyText.match(/initial-value\s*:\s*([^;]+);/);
+ if (initialValueMatch) {
+ this.__initialValue = initialValueMatch[1].trim();
} else {
- var error = 'css expression error: unfinished comment in expression!';
- return {
- error: error
- };
+ // If syntax is not "*", initial-value is required
+ if (this.__syntax !== "*") {
+ return false;
+ }
}
- } else {
- return false;
+
+ return true; // Successfully parsed
}
- };
+ });
+ //.CommonJS
+ CSSPropertyRule.CSSPropertyRule = CSSOM.CSSPropertyRule;
+ ///CommonJS
+ return CSSPropertyRule;
+}
- /**
- *
- * @return {Object|false}
- * - idx:
- * - text:
- * or
- * false
- *
- */
- CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) {
- var endIdx = this._findMatchedIdx(token, idx, sep),
- text;
+var CSSContainerRule = {};
- if (endIdx === -1) {
- return false;
- } else {
- text = token.substring(idx, endIdx + sep.length);
+var hasRequiredCSSContainerRule;
- return {
- idx: endIdx,
- text: text
- };
- }
+function requireCSSContainerRule () {
+ if (hasRequiredCSSContainerRule) return CSSContainerRule;
+ hasRequiredCSSContainerRule = 1;
+ //.CommonJS
+ var CSSOM = {
+ CSSRule: requireCSSRule().CSSRule,
+ CSSRuleList: requireCSSRuleList().CSSRuleList,
+ CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
+ CSSConditionRule: requireCSSConditionRule().CSSConditionRule,
};
+ ///CommonJS
/**
- * parse regexp in css expression
- *
- * @return {Object|false}
- * - idx:
- * - regExp:
- * or
- * false
+ * @constructor
+ * @see https://drafts.csswg.org/css-contain-3/
+ * @see https://www.w3.org/TR/css-contain-3/
*/
+ CSSOM.CSSContainerRule = function CSSContainerRule() {
+ CSSOM.CSSConditionRule.call(this);
+ };
- /*
+ CSSOM.CSSContainerRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
+ CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
- all legal RegExp
-
- /a/
- (/a/)
- [/a/]
- [12, /a/]
+ Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule);
- !/a/
+ Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", {
+ value: 17,
+ writable: false
+ });
- +/a/
- -/a/
- * /a/
- / /a/
- %/a/
+ Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
+ "cssText": {
+ get: function() {
+ var values = "";
+ var valuesArr = [" {"];
+ if (this.cssRules.length) {
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
+ if (rule.cssText !== "") {
+ acc.push(rule.cssText);
+ }
+ return acc;
+ }, []).join("\n "));
+ }
+ values = valuesArr.join("\n ") + "\n}";
+ return "@container " + this.conditionText + values;
+ }
+ },
+ "containerName": {
+ get: function() {
+ var parts = this.conditionText.trim().split(/\s+/);
+ if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
+ return parts[0];
+ }
+ return "";
+ }
+ },
+ "containerQuery": {
+ get: function() {
+ var parts = this.conditionText.trim().split(/\s+/);
+ if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
+ return parts.slice(1).join(' ');
+ }
+ return this.conditionText;
+ }
+ },
+ });
- ===/a/
- !==/a/
- ==/a/
- !=/a/
- >/a/
- >=/a/
- >/a/
- >>>/a/
+ //.CommonJS
+ CSSContainerRule.CSSContainerRule = CSSOM.CSSContainerRule;
+ ///CommonJS
+ return CSSContainerRule;
+}
- &&/a/
- ||/a/
- ?/a/
- =/a/
- ,/a/
+var CSSSupportsRule = {};
- delete /a/
- in /a/
- instanceof /a/
- new /a/
- typeof /a/
- void /a/
+var hasRequiredCSSSupportsRule;
- */
- CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) {
- var before = token.substring(0, idx).replace(/\s+$/, ""),
- legalRegx = [
- /^$/,
- /\($/,
- /\[$/,
- /\!$/,
- /\+$/,
- /\-$/,
- /\*$/,
- /\/\s+/,
- /\%$/,
- /\=$/,
- /\>$/,
- /<$/,
- /\&$/,
- /\|$/,
- /\^$/,
- /\~$/,
- /\?$/,
- /\,$/,
- /delete$/,
- /in$/,
- /instanceof$/,
- /new$/,
- /typeof$/,
- /void$/
- ];
+function requireCSSSupportsRule () {
+ if (hasRequiredCSSSupportsRule) return CSSSupportsRule;
+ hasRequiredCSSSupportsRule = 1;
+ //.CommonJS
+ var CSSOM = {
+ CSSRule: requireCSSRule().CSSRule,
+ CSSRuleList: requireCSSRuleList().CSSRuleList,
+ CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
+ CSSConditionRule: requireCSSConditionRule().CSSConditionRule
+ };
+ ///CommonJS
- var isLegal = legalRegx.some(function(reg) {
- return reg.test(before);
- });
- if (!isLegal) {
- return false;
- } else {
- var sep = '/';
+ /**
+ * @constructor
+ * @see https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface
+ */
+ CSSOM.CSSSupportsRule = function CSSSupportsRule() {
+ CSSOM.CSSConditionRule.call(this);
+ };
- // same logic as string
- return this._parseJSString(token, idx, sep);
- }
+ CSSOM.CSSSupportsRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype);
+ CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
+
+ Object.setPrototypeOf(CSSOM.CSSSupportsRule, CSSOM.CSSConditionRule);
+
+ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "type", {
+ value: 12,
+ writable: false
+ });
+
+ Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
+ get: function() {
+ var values = "";
+ var valuesArr = [" {"];
+ if (this.cssRules.length) {
+ valuesArr.push(this.cssRules.reduce(function(acc, rule){
+ if (rule.cssText !== "") {
+ acc.push(rule.cssText);
+ }
+ return acc;
+ }, []).join("\n "));
+ }
+ values = valuesArr.join("\n ") + "\n}";
+ return "@supports " + this.conditionText + values;
+ }
+ });
+
+ //.CommonJS
+ CSSSupportsRule.CSSSupportsRule = CSSOM.CSSSupportsRule;
+ ///CommonJS
+ return CSSSupportsRule;
+}
+
+var CSSFontFaceRule = {};
+
+var hasRequiredCSSFontFaceRule;
+
+function requireCSSFontFaceRule () {
+ if (hasRequiredCSSFontFaceRule) return CSSFontFaceRule;
+ hasRequiredCSSFontFaceRule = 1;
+ //.CommonJS
+ var CSSOM = {
+ CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
+ CSSRule: requireCSSRule().CSSRule
};
+ // Use cssstyle if available
+ try {
+ CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
+ } catch (e) {
+ // ignore
+ }
+ ///CommonJS
/**
- *
- * find next sep(same line) index in `token`
- *
- * @return {Number}
- *
+ * @constructor
+ * @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
*/
- CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
- var startIdx = idx,
- endIdx;
+ CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
+ CSSOM.CSSRule.call(this);
+ this.__style = new CSSOM.CSSStyleDeclaration();
+ this.__style.parentRule = this;
+ };
- var NOT_FOUND = -1;
+ CSSOM.CSSFontFaceRule.prototype = Object.create(CSSOM.CSSRule.prototype);
+ CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
- while(true) {
- endIdx = token.indexOf(sep, startIdx + 1);
+ Object.setPrototypeOf(CSSOM.CSSFontFaceRule, CSSOM.CSSRule);
- if (endIdx === -1) { // not found
- endIdx = NOT_FOUND;
- break;
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "type", {
+ value: 5,
+ writable: false
+ });
+
+ //FIXME
+ //CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
+ //CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
+
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "style", {
+ get: function() {
+ return this.__style;
+ },
+ set: function(value) {
+ if (typeof value === "string") {
+ this.__style.cssText = value;
} else {
- var text = token.substring(idx + 1, endIdx),
- matched = text.match(/\\+$/);
- if (!matched || matched[0] % 2 === 0) { // not escaped
- break;
- } else {
- startIdx = endIdx;
- }
+ this.__style = value;
}
}
+ });
- // boundary must be in the same line(js sting or regexp)
- var nextNewLineIdx = token.indexOf('\n', idx + 1);
- if (nextNewLineIdx < endIdx) {
- endIdx = NOT_FOUND;
- }
-
-
- return endIdx;
- };
-
-
+ // http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
+ Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
+ get: function() {
+ return "@font-face {" + (this.style.cssText ? " " + this.style.cssText : "") + " }";
+ }
+ });
//.CommonJS
- CSSValueExpression.CSSValueExpression = CSSOM.CSSValueExpression;
+ CSSFontFaceRule.CSSFontFaceRule = CSSOM.CSSFontFaceRule;
///CommonJS
- return CSSValueExpression;
+ return CSSFontFaceRule;
}
-var CSSDocumentRule = {};
-
-var MatcherList = {};
+var CSSHostRule = {};
-var hasRequiredMatcherList;
+var hasRequiredCSSHostRule;
-function requireMatcherList () {
- if (hasRequiredMatcherList) return MatcherList;
- hasRequiredMatcherList = 1;
+function requireCSSHostRule () {
+ if (hasRequiredCSSHostRule) return CSSHostRule;
+ hasRequiredCSSHostRule = 1;
//.CommonJS
- var CSSOM = {};
+ var CSSOM = {
+ CSSRule: requireCSSRule().CSSRule,
+ CSSRuleList: requireCSSRuleList().CSSRuleList
+ };
///CommonJS
/**
* @constructor
- * @see https://developer.mozilla.org/en/CSS/@-moz-document
+ * @see http://www.w3.org/TR/shadow-dom/#host-at-rule
+ * @see http://html5index.org/Shadow%20DOM%20-%20CSSHostRule.html
+ * @deprecated This rule was part of early Shadow DOM drafts but was removed in favor of the more flexible :host and :host-context() pseudo-classes in modern CSS for Web Components.
*/
- CSSOM.MatcherList = function MatcherList(){
- this.length = 0;
+ CSSOM.CSSHostRule = function CSSHostRule() {
+ CSSOM.CSSRule.call(this);
+ this.cssRules = new CSSOM.CSSRuleList();
};
- CSSOM.MatcherList.prototype = {
+ CSSOM.CSSHostRule.prototype = Object.create(CSSOM.CSSRule.prototype);
+ CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
- constructor: CSSOM.MatcherList,
+ Object.setPrototypeOf(CSSOM.CSSHostRule, CSSOM.CSSRule);
- /**
- * @return {string}
- */
- get matcherText() {
- return Array.prototype.join.call(this, ", ");
- },
+ Object.defineProperty(CSSOM.CSSHostRule.prototype, "type", {
+ value: 1001,
+ writable: false
+ });
- /**
- * @param {string} value
- */
- set matcherText(value) {
- // just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','.
- var values = value.split(",");
- var length = this.length = values.length;
- for (var i=0; i 0 && tempStyleSheet.cssRules[0].cssRules.length > 0) {
+ parsedRule = tempStyleSheet.cssRules[0].cssRules[0];
+ } else {
+ throw new Error("Failed to parse keyframe rule");
+ }
+ } catch (e) {
+ errorUtils.throwParseError(this, 'appendRule', 'CSSKeyframesRule', rule);
+ }
+
+ parsedRule.__parentRule = this;
+ this.cssRules.push(parsedRule);
+ };
+
+ /**
+ * Deletes a keyframe rule that matches the specified key.
+ *
+ * @param {string} select - The keyframe selector to delete (e.g., "50%", "from", "to")
+ * @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-deleterule
+ */
+ CSSOM.CSSKeyframesRule.prototype.deleteRule = function deleteRule(select) {
+ if (arguments.length === 0) {
+ errorUtils.throwMissingArguments(this, 'deleteRule', 'CSSKeyframesRule');
+ }
+
+ var normalizedSelect = this._normalizeKeyText(select);
+
+ for (var i = 0; i < this.cssRules.length; i++) {
+ var rule = this.cssRules[i];
+ if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
+ rule.__parentRule = null;
+ this.cssRules.splice(i, 1);
+ return;
+ }
+ }
+ };
+
+ /**
+ * Finds and returns the keyframe rule that matches the specified key.
+ * When multiple rules have the same key, returns the last one.
+ *
+ * @param {string} select - The keyframe selector to find (e.g., "50%", "from", "to")
+ * @return {CSSKeyframeRule|null} The matching keyframe rule, or null if not found
+ * @see https://www.w3.org/TR/css-animations-1/#dom-csskeyframesrule-findrule
+ */
+ CSSOM.CSSKeyframesRule.prototype.findRule = function findRule(select) {
+ if (arguments.length === 0) {
+ errorUtils.throwMissingArguments(this, 'findRule', 'CSSKeyframesRule');
+ }
+
+ var normalizedSelect = this._normalizeKeyText(select);
+
+ // Iterate backwards to find the last matching rule
+ for (var i = this.cssRules.length - 1; i >= 0; i--) {
+ var rule = this.cssRules[i];
+ if (this._normalizeKeyText(rule.keyText) === normalizedSelect) {
+ return rule;
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ * Normalizes keyframe selector text for comparison.
+ * Handles "from" -> "0%" and "to" -> "100%" conversions and trims whitespace.
+ *
+ * @private
+ * @param {string} keyText - The keyframe selector text to normalize
+ * @return {string} The normalized keyframe selector text
+ */
+ CSSOM.CSSKeyframesRule.prototype._normalizeKeyText = function _normalizeKeyText(keyText) {
+ if (!keyText) return '';
+
+ var normalized = keyText.toString().trim().toLowerCase();
+
+ // Convert keywords to percentages for comparison
+ if (normalized === 'from') {
+ return '0%';
+ } else if (normalized === 'to') {
+ return '100%';
+ }
+
+ return normalized;
+ };
+
+ /**
+ * Makes CSSKeyframesRule iterable over its cssRules.
+ * Allows for...of loops and other iterable methods.
+ */
+ if (typeof Symbol !== 'undefined' && Symbol.iterator) {
+ CSSOM.CSSKeyframesRule.prototype[Symbol.iterator] = function() {
+ var index = 0;
+ var cssRules = this.cssRules;
+
+ return {
+ next: function() {
+ if (index < cssRules.length) {
+ return { value: cssRules[index++], done: false };
+ } else {
+ return { done: true };
+ }
+ }
+ };
};
+ }
+
+ /**
+ * Adds indexed getters for direct access to cssRules by index.
+ * This enables rule[0], rule[1], etc. access patterns.
+ * Works in environments where Proxy is not available (like jsdom).
+ */
+ CSSOM.CSSKeyframesRule.prototype._setupIndexedAccess = function() {
+ // Remove any existing indexed properties
+ for (var i = 0; i < 1000; i++) { // reasonable upper limit
+ if (this.hasOwnProperty(i)) {
+ delete this[i];
+ } else {
+ break;
+ }
+ }
+
+ // Add indexed getters for current cssRules
+ for (var i = 0; i < this.cssRules.length; i++) {
+ (function(index) {
+ Object.defineProperty(this, index, {
+ get: function() {
+ return this.cssRules[index];
+ },
+ enumerable: false,
+ configurable: true
+ });
+ }.call(this, i));
+ }
+
+ // Update length property
+ Object.defineProperty(this, 'length', {
+ get: function() {
+ return this.cssRules.length;
+ },
+ enumerable: false,
+ configurable: true
+ });
+ };
- var styleSheet = new CSSOM.CSSStyleSheet();
- // @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
- var currentScope = styleSheet;
- // @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
- var parentRule;
- var ancestorRules = [];
- var hasAncestors = false;
- var prevScope;
- var name, priority="", styleRule, mediaRule, supportsRule, importRule, fontFaceRule, keyframesRule, documentRule, hostRule;
+ //.CommonJS
+ CSSKeyframesRule.CSSKeyframesRule = CSSOM.CSSKeyframesRule;
+ ///CommonJS
+ return CSSKeyframesRule;
+}
+
+var CSSValueExpression = {};
+
+var CSSValue = {};
+
+var hasRequiredCSSValue;
+
+function requireCSSValue () {
+ if (hasRequiredCSSValue) return CSSValue;
+ hasRequiredCSSValue = 1;
+ //.CommonJS
+ var CSSOM = {};
+ ///CommonJS
+
+
+ /**
+ * @constructor
+ * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
+ *
+ * TODO: add if needed
+ */
+ CSSOM.CSSValue = function CSSValue() {
+ };
+
+ CSSOM.CSSValue.prototype = {
+ constructor: CSSOM.CSSValue,
+
+ // @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
+ set cssText(text) {
+ var name = this._getConstructorName();
+
+ throw new Error('DOMException: property "cssText" of "' + name + '" is readonly and can not be replaced with "' + text + '"!');
+ },
+
+ get cssText() {
+ var name = this._getConstructorName();
+
+ throw new Error('getter "cssText" of "' + name + '" is not implemented!');
+ },
+
+ _getConstructorName: function() {
+ var s = this.constructor.toString(),
+ c = s.match(/function\s([^\(]+)/),
+ name = c[1];
+
+ return name;
+ }
+ };
+
+
+ //.CommonJS
+ CSSValue.CSSValue = CSSOM.CSSValue;
+ ///CommonJS
+ return CSSValue;
+}
+
+var hasRequiredCSSValueExpression;
+
+function requireCSSValueExpression () {
+ if (hasRequiredCSSValueExpression) return CSSValueExpression;
+ hasRequiredCSSValueExpression = 1;
+ //.CommonJS
+ var CSSOM = {
+ CSSValue: requireCSSValue().CSSValue
+ };
+ ///CommonJS
+
+
+ /**
+ * @constructor
+ * @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
+ *
+ */
+ CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
+ this._token = token;
+ this._idx = idx;
+ };
+
+ CSSOM.CSSValueExpression.prototype = Object.create(CSSOM.CSSValue.prototype);
+ CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
+
+ Object.setPrototypeOf(CSSOM.CSSValueExpression, CSSOM.CSSValue);
+
+ /**
+ * parse css expression() value
+ *
+ * @return {Object}
+ * - error:
+ * or
+ * - idx:
+ * - expression:
+ *
+ * Example:
+ *
+ * .selector {
+ * zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto');
+ * }
+ */
+ CSSOM.CSSValueExpression.prototype.parse = function() {
+ var token = this._token,
+ idx = this._idx;
+
+ var character = '',
+ expression = '',
+ error = '',
+ info,
+ paren = [];
+
+
+ for (; ; ++idx) {
+ character = token.charAt(idx);
+
+ // end of token
+ if (character === '') {
+ error = 'css expression error: unfinished expression!';
+ break;
+ }
+
+ switch(character) {
+ case '(':
+ paren.push(character);
+ expression += character;
+ break;
+
+ case ')':
+ paren.pop(character);
+ expression += character;
+ break;
+
+ case '/':
+ if ((info = this._parseJSComment(token, idx))) { // comment?
+ if (info.error) {
+ error = 'css expression error: unfinished comment in expression!';
+ } else {
+ idx = info.idx;
+ // ignore the comment
+ }
+ } else if ((info = this._parseJSRexExp(token, idx))) { // regexp
+ idx = info.idx;
+ expression += info.text;
+ } else { // other
+ expression += character;
+ }
+ break;
+
+ case "'":
+ case '"':
+ info = this._parseJSString(token, idx, character);
+ if (info) { // string
+ idx = info.idx;
+ expression += info.text;
+ } else {
+ expression += character;
+ }
+ break;
+
+ default:
+ expression += character;
+ break;
+ }
+
+ if (error) {
+ break;
+ }
+
+ // end of expression
+ if (paren.length === 0) {
+ break;
+ }
+ }
+
+ var ret;
+ if (error) {
+ ret = {
+ error: error
+ };
+ } else {
+ ret = {
+ idx: idx,
+ expression: expression
+ };
+ }
+
+ return ret;
+ };
+
+
+ /**
+ *
+ * @return {Object|false}
+ * - idx:
+ * - text:
+ * or
+ * - error:
+ * or
+ * false
+ *
+ */
+ CSSOM.CSSValueExpression.prototype._parseJSComment = function(token, idx) {
+ var nextChar = token.charAt(idx + 1),
+ text;
+
+ if (nextChar === '/' || nextChar === '*') {
+ var startIdx = idx,
+ endIdx,
+ commentEndChar;
+
+ if (nextChar === '/') { // line comment
+ commentEndChar = '\n';
+ } else if (nextChar === '*') { // block comment
+ commentEndChar = '*/';
+ }
+
+ endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1);
+ if (endIdx !== -1) {
+ endIdx = endIdx + commentEndChar.length - 1;
+ text = token.substring(idx, endIdx + 1);
+ return {
+ idx: endIdx,
+ text: text
+ };
+ } else {
+ var error = 'css expression error: unfinished comment in expression!';
+ return {
+ error: error
+ };
+ }
+ } else {
+ return false;
+ }
+ };
+
+
+ /**
+ *
+ * @return {Object|false}
+ * - idx:
+ * - text:
+ * or
+ * false
+ *
+ */
+ CSSOM.CSSValueExpression.prototype._parseJSString = function(token, idx, sep) {
+ var endIdx = this._findMatchedIdx(token, idx, sep),
+ text;
+
+ if (endIdx === -1) {
+ return false;
+ } else {
+ text = token.substring(idx, endIdx + sep.length);
+
+ return {
+ idx: endIdx,
+ text: text
+ };
+ }
+ };
+
+
+ /**
+ * parse regexp in css expression
+ *
+ * @return {Object|false}
+ * - idx:
+ * - regExp:
+ * or
+ * false
+ */
+
+ /*
+
+ all legal RegExp
+
+ /a/
+ (/a/)
+ [/a/]
+ [12, /a/]
+
+ !/a/
+
+ +/a/
+ -/a/
+ * /a/
+ / /a/
+ %/a/
+
+ ===/a/
+ !==/a/
+ ==/a/
+ !=/a/
+ >/a/
+ >=/a/
+ >/a/
+ >>>/a/
+
+ &&/a/
+ ||/a/
+ ?/a/
+ =/a/
+ ,/a/
+
+ delete /a/
+ in /a/
+ instanceof /a/
+ new /a/
+ typeof /a/
+ void /a/
+
+ */
+ CSSOM.CSSValueExpression.prototype._parseJSRexExp = function(token, idx) {
+ var before = token.substring(0, idx).replace(/\s+$/, ""),
+ legalRegx = [
+ /^$/,
+ /\($/,
+ /\[$/,
+ /\!$/,
+ /\+$/,
+ /\-$/,
+ /\*$/,
+ /\/\s+/,
+ /\%$/,
+ /\=$/,
+ /\>$/,
+ /<$/,
+ /\&$/,
+ /\|$/,
+ /\^$/,
+ /\~$/,
+ /\?$/,
+ /\,$/,
+ /delete$/,
+ /in$/,
+ /instanceof$/,
+ /new$/,
+ /typeof$/,
+ /void$/
+ ];
+
+ var isLegal = legalRegx.some(function(reg) {
+ return reg.test(before);
+ });
+
+ if (!isLegal) {
+ return false;
+ } else {
+ var sep = '/';
+
+ // same logic as string
+ return this._parseJSString(token, idx, sep);
+ }
+ };
+
+
+ /**
+ *
+ * find next sep(same line) index in `token`
+ *
+ * @return {Number}
+ *
+ */
+ CSSOM.CSSValueExpression.prototype._findMatchedIdx = function(token, idx, sep) {
+ var startIdx = idx,
+ endIdx;
+
+ var NOT_FOUND = -1;
+
+ while(true) {
+ endIdx = token.indexOf(sep, startIdx + 1);
+
+ if (endIdx === -1) { // not found
+ endIdx = NOT_FOUND;
+ break;
+ } else {
+ var text = token.substring(idx + 1, endIdx),
+ matched = text.match(/\\+$/);
+ if (!matched || matched[0] % 2 === 0) { // not escaped
+ break;
+ } else {
+ startIdx = endIdx;
+ }
+ }
+ }
+
+ // boundary must be in the same line(js sting or regexp)
+ var nextNewLineIdx = token.indexOf('\n', idx + 1);
+ if (nextNewLineIdx < endIdx) {
+ endIdx = NOT_FOUND;
+ }
+
+
+ return endIdx;
+ };
+
+
+
+
+ //.CommonJS
+ CSSValueExpression.CSSValueExpression = CSSOM.CSSValueExpression;
+ ///CommonJS
+ return CSSValueExpression;
+}
+
+var CSSDocumentRule = {};
+
+var MatcherList = {};
+
+var hasRequiredMatcherList;
+
+function requireMatcherList () {
+ if (hasRequiredMatcherList) return MatcherList;
+ hasRequiredMatcherList = 1;
+ //.CommonJS
+ var CSSOM = {};
+ ///CommonJS
+
+
+ /**
+ * @constructor
+ * @see https://developer.mozilla.org/en/CSS/@-moz-document
+ */
+ CSSOM.MatcherList = function MatcherList(){
+ this.length = 0;
+ };
+
+ CSSOM.MatcherList.prototype = {
+
+ constructor: CSSOM.MatcherList,
+
+ /**
+ * @return {string}
+ */
+ get matcherText() {
+ return Array.prototype.join.call(this, ", ");
+ },
+
+ /**
+ * @param {string} value
+ */
+ set matcherText(value) {
+ // just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','.
+ var values = value.split(",");
+ var length = this.length = values.length;
+ for (var i=0; i)
+ var hasOnlyEnd = !hasStart &&
+ !hasEnd &&
+ parts[0].indexOf('to (') === 0 &&
+ parts[0].charAt(parts[0].length - 1) === ')';
+
+ var startSelector = '';
+ var endSelector = '';
+
+ if (hasStart) {
+ startSelector = parts[0].slice(1, -1).trim();
+ }
+ if (hasEnd) {
+ endSelector = parts[1].slice(1, -1).trim();
+ }
+ if (hasOnlyEnd) {
+ endSelector = parts[0].slice(4, -1).trim();
+ }
+
+ return {
+ startSelector: startSelector,
+ endSelector: endSelector,
+ hasStart: hasStart,
+ hasEnd: hasEnd,
+ hasOnlyEnd: hasOnlyEnd
+ };
+ }
+ /**
+ * Checks if a selector contains pseudo-elements.
+ * @param {string} selector - The CSS selector to check
+ * @returns {boolean} True if the selector contains pseudo-elements
+ */
+ function hasPseudoElement(selector) {
+ // Match only double-colon (::) pseudo-elements
+ // Also match legacy single-colon pseudo-elements: :before, :after, :first-line, :first-letter
+ // These must NOT be followed by alphanumeric characters (to avoid matching :before-x or similar)
+ return pseudoElementRegExp.test(selector);
+ }
+ /**
+ * Validates balanced parentheses, brackets, and quotes in a selector.
+ *
+ * @param {string} selector - The CSS selector to validate
+ * @param {boolean} trackAttributes - Whether to track attribute selector context
+ * @param {boolean} useStack - Whether to use a stack for parentheses (needed for nested validation)
+ * @returns {boolean} True if the syntax is valid (all brackets, parentheses, and quotes are balanced)
+ */
+ function validateBalancedSyntax(selector, trackAttributes, useStack) {
+ var parenDepth = 0;
+ var bracketDepth = 0;
+ var inSingleQuote = false;
+ var inDoubleQuote = false;
+ var inAttr = false;
+ var stack = useStack ? [] : null;
+
+ for (var i = 0; i < selector.length; i++) {
+ var char = selector[i];
+
+ // Handle escape sequences - skip hex escapes or simple escapes
+ if (char === '\\') {
+ var escapeLen = getEscapeSequenceLength(selector, i);
+ if (escapeLen > 0) {
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+
+ if (inSingleQuote) {
+ if (char === "'") {
+ inSingleQuote = false;
+ }
+ } else if (inDoubleQuote) {
+ if (char === '"') {
+ inDoubleQuote = false;
+ }
+ } else if (trackAttributes && inAttr) {
+ if (char === "]") {
+ inAttr = false;
+ } else if (char === "'") {
+ inSingleQuote = true;
+ } else if (char === '"') {
+ inDoubleQuote = true;
+ }
+ } else {
+ if (trackAttributes && char === "[") {
+ inAttr = true;
+ } else if (char === "'") {
+ inSingleQuote = true;
+ } else if (char === '"') {
+ inDoubleQuote = true;
+ } else if (char === '(') {
+ if (useStack) {
+ stack.push("(");
+ } else {
+ parenDepth++;
+ }
+ } else if (char === ')') {
+ if (useStack) {
+ if (!stack.length || stack.pop() !== "(") {
+ return false;
+ }
+ } else {
+ parenDepth--;
+ if (parenDepth < 0) {
+ return false;
+ }
+ }
+ } else if (char === '[') {
+ bracketDepth++;
+ } else if (char === ']') {
+ bracketDepth--;
+ if (bracketDepth < 0) {
+ return false;
+ }
+ }
+ }
+ }
+
+ // Check if everything is balanced
+ if (useStack) {
+ return stack.length === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote && !inAttr;
+ } else {
+ return parenDepth === 0 && bracketDepth === 0 && !inSingleQuote && !inDoubleQuote;
+ }
+ }
+ /**
+ * Checks for basic syntax errors in selectors (mismatched parentheses, brackets, quotes).
+ * @param {string} selector - The CSS selector to check
+ * @returns {boolean} True if there are syntax errors
+ */
+ function hasBasicSyntaxError(selector) {
+ return !validateBalancedSyntax(selector, false, false);
+ }
+ /**
+ * Checks for invalid combinator patterns in selectors.
+ * @param {string} selector - The CSS selector to check
+ * @returns {boolean} True if the selector contains invalid combinators
+ */
+ function hasInvalidCombinators(selector) {
+ // Check for invalid combinator patterns:
+ // - <> (not a valid combinator)
+ // - >> (deep descendant combinator, deprecated and invalid)
+ // - Multiple consecutive combinators like >>, >~, etc.
+ if (invalidCombinatorLtGtRegExp.test(selector)) return true;
+ if (invalidCombinatorDoubleGtRegExp.test(selector)) return true;
+ // Check for other invalid consecutive combinator patterns
+ if (consecutiveCombinatorsRegExp.test(selector)) return true;
+ return false;
+ }
+ /**
+ * Checks for invalid pseudo-like syntax (function calls without proper pseudo prefix).
+ * @param {string} selector - The CSS selector to check
+ * @returns {boolean} True if the selector contains invalid pseudo-like syntax
+ */
+ function hasInvalidPseudoSyntax(selector) {
+ // Check for specific known pseudo-elements used without : or :: prefix
+ // Examples: slotted(div), part(name), cue(selector)
+ // These are ONLY valid as ::slotted(), ::part(), ::cue()
+ var invalidPatterns = [
+ invalidSlottedRegExp,
+ invalidPartRegExp,
+ invalidCueRegExp,
+ invalidCueRegionRegExp
+ ];
+
+ for (var i = 0; i < invalidPatterns.length; i++) {
+ if (invalidPatterns[i].test(selector)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Checks for invalid nesting selector (&) usage.
+ * The & selector cannot be directly followed by a type selector without a delimiter.
+ * Valid: &.class, id, &[attr], &:hover, &::before, & div, &>div
+ * Invalid: &div, &span
+ * @param {string} selector - The CSS selector to check
+ * @returns {boolean} True if the selector contains invalid & usage
+ */
+ function hasInvalidNestingSelector(selector) {
+ // Check for & followed directly by a letter (type selector) without any delimiter
+ // This regex matches & followed by a letter (start of type selector) that's not preceded by an escape
+ // We need to exclude valid cases like &.class, id, &[attr], &:pseudo, &::pseudo, & (with space), &>
+ return invalidNestingPattern.test(selector);
+ }
+ /**
+ * Checks if an at-rule can be nested based on parent chain validation.
+ * Used for at-rules like `@counter-style`, `@property` and `@font-face` rules that can only be nested inside
+ * `CSSScopeRule` or `CSSConditionRule` without `CSSStyleRule` in parent chain.
+ * @returns {boolean} `true` if nesting is allowed, `false` otherwise
+ */
+ function canAtRuleBeNested() {
+ if (currentScope === topScope) {
+ return true; // Top-level is always allowed
+ }
+
+ var hasStyleRuleInChain = false;
+ var hasValidParent = false;
+
+ // Check currentScope
+ if (currentScope.constructor.name === 'CSSStyleRule') {
+ hasStyleRuleInChain = true;
+ } else if (currentScope instanceof CSSOM.CSSScopeRule || currentScope instanceof CSSOM.CSSConditionRule) {
+ hasValidParent = true;
+ }
+
+ // Check ancestorRules for CSSStyleRule
+ if (!hasStyleRuleInChain) {
+ for (var j = 0; j < ancestorRules.length; j++) {
+ if (ancestorRules[j].constructor.name === 'CSSStyleRule') {
+ hasStyleRuleInChain = true;
+ break;
+ }
+ if (ancestorRules[j] instanceof CSSOM.CSSScopeRule || ancestorRules[j] instanceof CSSOM.CSSConditionRule) {
+ hasValidParent = true;
+ }
+ }
+ }
+
+ // Allow nesting if we have a valid parent and no style rule in the chain
+ return hasValidParent && !hasStyleRuleInChain;
+ }
+
+ function validateAtRule(atRuleKey, validCallback, cannotBeNested) {
+ var isValid = false;
+ // Use cached regex instead of creating new one each time
+ var ruleRegExp = getValidationRegex(atRuleKey);
+ // Only slice what we need for validation (max 100 chars)
+ // since we only check match at position 0
+ var lookAheadLength = Math.min(100, token.length - i);
+ var ruleSlice = token.slice(i, i + lookAheadLength);
+ // Not all rules can be nested, if the rule cannot be nested and is in the root scope, do not perform the check
+ var shouldPerformCheck = cannotBeNested && currentScope !== topScope ? false : true;
+ // First, check if there is no invalid characters just after the at-rule
+ if (shouldPerformCheck && ruleSlice.search(ruleRegExp) === 0) {
+ // Only scan from the last known validation boundary
+ var searchStart = Math.max(0, lastValidationBoundary);
+ var beforeSlice = token.slice(searchStart, i);
+
+ // Use pre-compiled regex instead of creating new one each time
+ var matches = beforeSlice.match(regexBefore);
+ var lastI = matches ? searchStart + beforeSlice.lastIndexOf(matches[matches.length - 1]) : searchStart;
+ var toCheckSlice = token.slice(lastI, i);
+ // Check if we don't have any invalid in the portion before the `at-rule` and the closest allowed character
+ var checkedSlice = toCheckSlice.search(beforeRuleValidationRegExp);
+ if (checkedSlice === 0) {
+ isValid = true;
+ // Update the validation boundary cache to this position
+ lastValidationBoundary = lastI;
+ }
+ }
+
+ // Additional validation for @scope rule
+ if (isValid && atRuleKey === "@scope") {
+ var openBraceIndex = ruleSlice.indexOf('{');
+ if (openBraceIndex !== -1) {
+ // Extract the rule prelude (everything between the at-rule and {)
+ var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
+
+ // Skip past at-rule keyword and whitespace
+ var preludeContent = rulePrelude.slice("@scope".length).trim();
+
+ if (preludeContent.length > 0) {
+ // Parse the scope prelude
+ var parsedScopePrelude = parseScopePrelude(preludeContent);
+ var startSelector = parsedScopePrelude.startSelector;
+ var endSelector = parsedScopePrelude.endSelector;
+ var hasStart = parsedScopePrelude.hasStart;
+ var hasEnd = parsedScopePrelude.hasEnd;
+ var hasOnlyEnd = parsedScopePrelude.hasOnlyEnd;
+
+ // Validation rules for @scope:
+ // 1. Empty selectors in parentheses are invalid: @scope () {} or @scope (.a) to () {}
+ if ((hasStart && startSelector === '') || (hasEnd && endSelector === '') || (hasOnlyEnd && endSelector === '')) {
+ isValid = false;
+ }
+ // 2. Pseudo-elements are invalid in scope selectors
+ else if ((startSelector && hasPseudoElement(startSelector)) || (endSelector && hasPseudoElement(endSelector))) {
+ isValid = false;
+ }
+ // 3. Basic syntax errors (mismatched parens, brackets, quotes)
+ else if ((startSelector && hasBasicSyntaxError(startSelector)) || (endSelector && hasBasicSyntaxError(endSelector))) {
+ isValid = false;
+ }
+ // 4. Invalid combinator patterns
+ else if ((startSelector && hasInvalidCombinators(startSelector)) || (endSelector && hasInvalidCombinators(endSelector))) {
+ isValid = false;
+ }
+ // 5. Invalid pseudo-like syntax (function without : or :: prefix)
+ else if ((startSelector && hasInvalidPseudoSyntax(startSelector)) || (endSelector && hasInvalidPseudoSyntax(endSelector))) {
+ isValid = false;
+ }
+ // 6. Invalid structure (no proper parentheses found when prelude is not empty)
+ else if (!hasStart && !hasOnlyEnd) {
+ isValid = false;
+ }
+ }
+ // Empty prelude (@scope {}) is valid
+ }
+ }
+
+ if (isValid && atRuleKey === "@page") {
+ var openBraceIndex = ruleSlice.indexOf('{');
+ if (openBraceIndex !== -1) {
+ // Extract the rule prelude (everything between the at-rule and {)
+ var rulePrelude = ruleSlice.slice(0, openBraceIndex).trim();
+
+ // Skip past at-rule keyword and whitespace
+ var preludeContent = rulePrelude.slice("@page".length).trim();
+
+ if (preludeContent.length > 0) {
+ var trimmedValue = preludeContent.trim();
+
+ // Empty selector is valid for @page
+ if (trimmedValue !== '') {
+ // Parse @page selectorText for page name and pseudo-pages
+ // Valid formats:
+ // - (empty - no name, no pseudo-page)
+ // - :left, :right, :first, :blank (pseudo-page only)
+ // - named (named page only)
+ // - named:first (named page with single pseudo-page)
+ // - named:first:left (named page with multiple pseudo-pages)
+ var match = trimmedValue.match(atPageRuleSelectorRegExp);
+ if (match) {
+ var pageName = match[1] || '';
+ var pseudoPages = match[2] || '';
+
+ // Validate page name if present
+ if (pageName) {
+ if (!cssCustomIdentifierRegExp.test(pageName)) {
+ isValid = false;
+ }
+ }
+
+ // Validate pseudo-pages if present
+ if (pseudoPages) {
+ var pseudos = pseudoPages.split(':').filter(function (p) { return p; });
+ var validPseudos = ['left', 'right', 'first', 'blank'];
+ var allValid = true;
+ for (var j = 0; j < pseudos.length; j++) {
+ if (validPseudos.indexOf(pseudos[j].toLowerCase()) === -1) {
+ allValid = false;
+ break;
+ }
+ }
+
+ if (!allValid) {
+ isValid = false;
+ }
+ }
+ } else {
+ isValid = false;
+ }
+ }
+
+ }
+ }
+ }
+
+ if (!isValid) {
+ // If it's invalid the browser will simply ignore the entire invalid block
+ // Use regex to find the closing brace of the invalid rule
+
+ // Regex used above is not ES5 compliant. Using alternative.
+ // var ruleStatementMatch = ruleSlice.match(atRulesStatemenRegExp); //
+ var ruleStatementMatch = atRulesStatemenRegExpES5Alternative(ruleSlice);
+
+ // If it's a statement inside a nested rule, ignore only the statement
+ if (ruleStatementMatch && currentScope !== topScope) {
+ var ignoreEnd = ruleStatementMatch[0].indexOf(";");
+ i += ruleStatementMatch.index + ignoreEnd;
+ return;
+ }
+
+ // Check if there's a semicolon before the invalid at-rule and the first opening brace
+ if (atRuleKey === "@layer") {
+ var ruleSemicolonAndOpeningBraceMatch = ruleSlice.match(forwardRuleSemicolonAndOpeningBraceRegExp);
+ if (ruleSemicolonAndOpeningBraceMatch && ruleSemicolonAndOpeningBraceMatch[1] === ";") {
+ // Ignore the rule block until the semicolon
+ i += ruleSemicolonAndOpeningBraceMatch.index + ruleSemicolonAndOpeningBraceMatch[0].length;
+ state = "before-selector";
+ return;
+ }
+ }
+
+ // Ignore the entire rule block (if it's a statement it should ignore the statement plus the next block)
+ i = ignoreBalancedBlock(i, ruleSlice);
+ state = "before-selector";
+ } else {
+ validCallback.call(this);
+ }
+ }
+
+ // Helper functions for looseSelectorValidator
+ // Defined outside to avoid recreation on every validation call
+
+ /**
+ * Check if character is a valid identifier start
+ * @param {string} c - Character to check
+ * @returns {boolean}
+ */
+ function isIdentStart(c) {
+ return /[a-zA-Z_\u00A0-\uFFFF]/.test(c);
+ }
+
+ /**
+ * Check if character is a valid identifier character
+ * @param {string} c - Character to check
+ * @returns {boolean}
+ */
+ function isIdentChar(c) {
+ return /[a-zA-Z0-9_\u00A0-\uFFFF\-]/.test(c);
+ }
+
+ /**
+ * Helper function to validate CSS selector syntax without regex backtracking.
+ * Iteratively parses the selector string to identify valid components.
+ *
+ * Supports:
+ * - Escaped characters (e.g., .class\!, #id\@name)
+ * - Namespace selectors (ns|element, *|element, |element)
+ * - All standard CSS selectors (class, ID, type, attribute, pseudo, etc.)
+ * - Combinators (>, +, ~, whitespace)
+ * - Nesting selector (&)
+ *
+ * This approach eliminates exponential backtracking by using explicit character-by-character
+ * parsing instead of nested quantifiers in regex.
+ *
+ * @param {string} selector - The selector to validate
+ * @returns {boolean} - True if valid selector syntax
+ */
+ function looseSelectorValidator(selector) {
+ if (!selector || selector.length === 0) {
+ return false;
+ }
+
+ var i = 0;
+ var len = selector.length;
+ var hasMatchedComponent = false;
+
+ // Helper: Skip escaped character (backslash + hex escape or any char)
+ function skipEscape() {
+ if (i < len && selector[i] === '\\') {
+ var escapeLen = getEscapeSequenceLength(selector, i);
+ if (escapeLen > 0) {
+ i += escapeLen; // Skip entire escape sequence
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Helper: Parse identifier (with possible escapes)
+ function parseIdentifier() {
+ var start = i;
+ while (i < len) {
+ if (skipEscape()) {
+ continue;
+ } else if (isIdentChar(selector[i])) {
+ i++;
+ } else {
+ break;
+ }
+ }
+ return i > start;
+ }
+
+ // Helper: Parse namespace prefix (optional)
+ function parseNamespace() {
+ var start = i;
+
+ // Match: *| or identifier| or |
+ if (i < len && selector[i] === '*') {
+ i++;
+ } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\')) {
+ parseIdentifier();
+ }
+
+ if (i < len && selector[i] === '|') {
+ i++;
+ return true;
+ }
+
+ // Rollback if no pipe found
+ i = start;
+ return false;
+ }
+
+ // Helper: Parse pseudo-class/element arguments (with balanced parens)
+ function parsePseudoArgs() {
+ if (i >= len || selector[i] !== '(') {
+ return false;
+ }
+
+ i++; // Skip opening paren
+ var depth = 1;
+ var inString = false;
+ var stringChar = '';
+
+ while (i < len && depth > 0) {
+ var c = selector[i];
+
+ if (c === '\\' && i + 1 < len) {
+ i += 2; // Skip escaped character
+ } else if (!inString && (c === '"' || c === '\'')) {
+ inString = true;
+ stringChar = c;
+ i++;
+ } else if (inString && c === stringChar) {
+ inString = false;
+ i++;
+ } else if (!inString && c === '(') {
+ depth++;
+ i++;
+ } else if (!inString && c === ')') {
+ depth--;
+ i++;
+ } else {
+ i++;
+ }
+ }
+
+ return depth === 0;
+ }
+
+ // Main parsing loop
+ while (i < len) {
+ var matched = false;
+ var start = i;
+
+ // Skip whitespace
+ while (i < len && /\s/.test(selector[i])) {
+ i++;
+ }
+ if (i > start) {
+ hasMatchedComponent = true;
+ continue;
+ }
+
+ // Match combinators: >, +, ~
+ if (i < len && /[>+~]/.test(selector[i])) {
+ i++;
+ hasMatchedComponent = true;
+ // Skip trailing whitespace
+ while (i < len && /\s/.test(selector[i])) {
+ i++;
+ }
+ continue;
+ }
+
+ // Match nesting selector: &
+ if (i < len && selector[i] === '&') {
+ i++;
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ // Match class selector: .identifier
+ else if (i < len && selector[i] === '.') {
+ i++;
+ if (parseIdentifier()) {
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ }
+ // Match ID selector: #identifier
+ else if (i < len && selector[i] === '#') {
+ i++;
+ if (parseIdentifier()) {
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ }
+ // Match pseudo-class/element: :identifier or ::identifier
+ else if (i < len && selector[i] === ':') {
+ i++;
+ if (i < len && selector[i] === ':') {
+ i++; // Pseudo-element
+ }
+ if (parseIdentifier()) {
+ parsePseudoArgs(); // Optional arguments
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ }
+ // Match attribute selector: [...]
+ else if (i < len && selector[i] === '[') {
+ i++;
+ var depth = 1;
+ while (i < len && depth > 0) {
+ if (selector[i] === '\\') {
+ i += 2;
+ } else if (selector[i] === '\'') {
+ i++;
+ while (i < len && selector[i] !== '\'') {
+ if (selector[i] === '\\') i += 2;
+ else i++;
+ }
+ if (i < len) i++; // Skip closing quote
+ } else if (selector[i] === '"') {
+ i++;
+ while (i < len && selector[i] !== '"') {
+ if (selector[i] === '\\') i += 2;
+ else i++;
+ }
+ if (i < len) i++; // Skip closing quote
+ } else if (selector[i] === '[') {
+ depth++;
+ i++;
+ } else if (selector[i] === ']') {
+ depth--;
+ i++;
+ } else {
+ i++;
+ }
+ }
+ if (depth === 0) {
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ }
+ // Match type selector with optional namespace: [namespace|]identifier
+ else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '*' || selector[i] === '|' || selector[i] === '-')) {
+ parseNamespace(); // Optional namespace prefix
+
+ if (i < len && selector[i] === '*') {
+ i++; // Universal selector
+ hasMatchedComponent = true;
+ matched = true;
+ } else if (i < len && (isIdentStart(selector[i]) || selector[i] === '\\' || selector[i] === '-')) {
+ if (parseIdentifier()) {
+ hasMatchedComponent = true;
+ matched = true;
+ }
+ }
+ }
+
+ // If no match found, invalid selector
+ if (!matched && i === start) {
+ return false;
+ }
+ }
+
+ return hasMatchedComponent;
+ }
+
+ /**
+ * Validates a basic CSS selector, allowing for deeply nested balanced parentheses in pseudo-classes.
+ * This function replaces the previous basicSelectorRegExp.
+ *
+ * This function matches:
+ * - Type selectors (e.g., `div`, `span`)
+ * - Universal selector (`*`)
+ * - Namespace selectors (e.g., `*|div`, `custom|div`, `|div`)
+ * - ID selectors (e.g., `#header`, `#a\ b`, `#åèiöú`)
+ * - Class selectors (e.g., `.container`, `.a\ b`, `.åèiöú`)
+ * - Attribute selectors (e.g., `[type="text"]`)
+ * - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
+ * - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
+ * such as `:has(.sel:nth-child(3n))`
+ * - The parent selector (`&`)
+ * - Combinators (`>`, `+`, `~`) with optional whitespace
+ * - Whitespace (descendant combinator)
+ *
+ * Unicode and escape sequences are allowed in identifiers.
+ *
+ * @param {string} selector
+ * @returns {boolean}
+ */
+ function basicSelectorValidator(selector) {
+ // Guard against extremely long selectors to prevent potential regex performance issues
+ // Reasonable selectors are typically under 1000 characters
+ if (selector.length > 10000) {
+ return false;
+ }
+
+ // Validate balanced syntax with attribute tracking and stack-based parentheses matching
+ if (!validateBalancedSyntax(selector, true, true)) {
+ return false;
+ }
+
+ // Check for invalid combinator patterns
+ if (hasInvalidCombinators(selector)) {
+ return false;
+ }
+
+ // Check for invalid pseudo-like syntax
+ if (hasInvalidPseudoSyntax(selector)) {
+ return false;
+ }
+
+ // Check for invalid nesting selector (&) usage
+ if (hasInvalidNestingSelector(selector)) {
+ return false;
+ }
+
+ // Check for invalid pseudo-class usage with quoted strings
+ // Pseudo-classes like :lang(), :dir(), :nth-*() should not accept quoted strings
+ // Using iterative parsing instead of regex to avoid exponential backtracking
+ var noQuotesPseudos = ['lang', 'dir', 'nth-child', 'nth-last-child', 'nth-of-type', 'nth-last-of-type'];
+
+ for (var idx = 0; idx < selector.length; idx++) {
+ // Look for pseudo-class/element start
+ if (selector[idx] === ':') {
+ idx++;
+
+ // Skip second colon for pseudo-elements
+ if (idx < selector.length && selector[idx] === ':') {
+ idx++;
+ }
+
+ // Extract pseudo name
+ var nameStart = idx;
+ while (idx < selector.length && /[a-zA-Z0-9\-]/.test(selector[idx])) {
+ idx++;
+ }
+
+ if (idx === nameStart) {
+ continue; // No name found
+ }
+
+ var pseudoName = selector.substring(nameStart, idx).toLowerCase();
+
+ // Check if this pseudo has arguments
+ if (idx < selector.length && selector[idx] === '(') {
+ idx++;
+ var contentStart = idx;
+ var depth = 1;
+
+ // Find matching closing paren (handle nesting)
+ while (idx < selector.length && depth > 0) {
+ if (selector[idx] === '\\') {
+ idx += 2; // Skip escaped character
+ } else if (selector[idx] === '(') {
+ depth++;
+ idx++;
+ } else if (selector[idx] === ')') {
+ depth--;
+ idx++;
+ } else {
+ idx++;
+ }
+ }
+
+ if (depth === 0) {
+ var pseudoContent = selector.substring(contentStart, idx - 1);
+
+ // Check if this pseudo should not have quoted strings
+ for (var j = 0; j < noQuotesPseudos.length; j++) {
+ if (pseudoName === noQuotesPseudos[j] && /['"]/.test(pseudoContent)) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Use the iterative validator to avoid regex backtracking issues
+ return looseSelectorValidator(selector);
+ }
+
+ /**
+ * Regular expression to match CSS pseudo-classes with arguments.
+ *
+ * Matches patterns like `:pseudo-class(argument)`, capturing the pseudo-class name and its argument.
+ *
+ * Capture groups:
+ * 1. The pseudo-class name (letters and hyphens).
+ * 2. The argument inside the parentheses (can contain nested parentheses, quoted strings, and other characters.).
+ *
+ * Global flag (`g`) is used to find all matches in the input string.
+ *
+ * Example matches:
+ * - :nth-child(2n+1)
+ * - :has(.sel:nth-child(3n))
+ * - :not(".foo, .bar")
+ *
+ * REPLACED WITH FUNCTION to avoid exponential backtracking.
+ */
+
+ /**
+ * Extract pseudo-classes with arguments from a selector using iterative parsing.
+ * Replaces the previous globalPseudoClassRegExp to avoid exponential backtracking.
+ *
+ * Handles:
+ * - Regular content without parentheses or quotes
+ * - Single-quoted strings
+ * - Double-quoted strings
+ * - Nested parentheses (arbitrary depth)
+ *
+ * @param {string} selector - The CSS selector to parse
+ * @returns {Array} Array of matches, each with: [fullMatch, pseudoName, pseudoArgs, startIndex]
+ */
+ function extractPseudoClasses(selector) {
+ var matches = [];
+
+ for (var i = 0; i < selector.length; i++) {
+ // Look for pseudo-class start (single or double colon)
+ if (selector[i] === ':') {
+ var pseudoStart = i;
+ i++;
+
+ // Skip second colon for pseudo-elements (::)
+ if (i < selector.length && selector[i] === ':') {
+ i++;
+ }
+
+ // Extract pseudo name
+ var nameStart = i;
+ while (i < selector.length && /[a-zA-Z\-]/.test(selector[i])) {
+ i++;
+ }
+
+ if (i === nameStart) {
+ continue; // No name found
+ }
+
+ var pseudoName = selector.substring(nameStart, i);
+
+ // Check if this pseudo has arguments
+ if (i < selector.length && selector[i] === '(') {
+ i++;
+ var argsStart = i;
+ var depth = 1;
+ var inSingleQuote = false;
+ var inDoubleQuote = false;
+
+ // Find matching closing paren (handle nesting and strings)
+ while (i < selector.length && depth > 0) {
+ var ch = selector[i];
+
+ if (ch === '\\') {
+ i += 2; // Skip escaped character
+ } else if (ch === "'" && !inDoubleQuote) {
+ inSingleQuote = !inSingleQuote;
+ i++;
+ } else if (ch === '"' && !inSingleQuote) {
+ inDoubleQuote = !inDoubleQuote;
+ i++;
+ } else if (ch === '(' && !inSingleQuote && !inDoubleQuote) {
+ depth++;
+ i++;
+ } else if (ch === ')' && !inSingleQuote && !inDoubleQuote) {
+ depth--;
+ i++;
+ } else {
+ i++;
+ }
+ }
+
+ if (depth === 0) {
+ var pseudoArgs = selector.substring(argsStart, i - 1);
+ var fullMatch = selector.substring(pseudoStart, i);
+
+ // Store match in same format as regex: [fullMatch, pseudoName, pseudoArgs, startIndex]
+ matches.push([fullMatch, pseudoName, pseudoArgs, pseudoStart]);
+ }
+
+ // Move back one since loop will increment
+ i--;
+ }
+ }
+ }
+
+ return matches;
+ }
+
+ /**
+ * Parses a CSS selector string and splits it into parts, handling nested parentheses.
+ *
+ * This function is useful for splitting selectors that may contain nested function-like
+ * syntax (e.g., :not(.foo, .bar)), ensuring that commas inside parentheses do not split
+ * the selector.
+ *
+ * @param {string} selector - The CSS selector string to parse.
+ * @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
+ */
+ function parseAndSplitNestedSelectors(selector) {
+ var depth = 0; // Track parenthesis nesting depth
+ var buffer = ""; // Accumulate characters for current selector part
+ var parts = []; // Array of split selector parts
+ var inSingleQuote = false; // Track if we're inside single quotes
+ var inDoubleQuote = false; // Track if we're inside double quotes
+ var i, char;
+
+ for (i = 0; i < selector.length; i++) {
+ char = selector.charAt(i);
+
+ // Handle escape sequences - skip them entirely
+ if (char === '\\' && i + 1 < selector.length) {
+ buffer += char;
+ i++;
+ buffer += selector.charAt(i);
+ continue;
+ }
+
+ // Handle single quote strings
+ if (char === "'" && !inDoubleQuote) {
+ inSingleQuote = !inSingleQuote;
+ buffer += char;
+ }
+ // Handle double quote strings
+ else if (char === '"' && !inSingleQuote) {
+ inDoubleQuote = !inDoubleQuote;
+ buffer += char;
+ }
+ // Process characters outside of quoted strings
+ else if (!inSingleQuote && !inDoubleQuote) {
+ if (char === '(') {
+ // Entering a nested level (e.g., :is(...))
+ depth++;
+ buffer += char;
+ } else if (char === ')') {
+ // Exiting a nested level
+ depth--;
+ buffer += char;
+ } else if (char === ',' && depth === 0) {
+ // Found a top-level comma separator - split here
+ // Note: escaped commas (\,) are already handled above
+ if (buffer.trim()) {
+ parts.push(buffer.trim());
+ }
+ buffer = "";
+ } else {
+ // Regular character - add to buffer
+ buffer += char;
+ }
+ }
+ // Characters inside quoted strings - add to buffer
+ else {
+ buffer += char;
+ }
+ }
+
+ // Add any remaining content in buffer as the last part
+ var trimmed = buffer.trim();
+ if (trimmed) {
+ // Preserve trailing space if selector ends with hex escape
+ var endsWithHexEscape = endsWithHexEscapeRegExp.test(buffer);
+ parts.push(endsWithHexEscape ? buffer.replace(leadingWhitespaceRegExp, '') : trimmed);
+ }
+
+ return parts;
+ }
+
+ /**
+ * Validates a CSS selector string, including handling of nested selectors within certain pseudo-classes.
+ *
+ * This function checks if the provided selector is valid according to the rules defined by
+ * `basicSelectorValidator`. For pseudo-classes that accept selector lists (such as :not, :is, :has, :where),
+ * it recursively validates each nested selector using the same validation logic.
+ *
+ * @param {string} selector - The CSS selector string to validate.
+ * @returns {boolean} Returns `true` if the selector is valid, otherwise `false`.
+ */
+
+ // Cache to store validated selectors (previously a ES6 Map, now an ES5-compliant object)
+ var validatedSelectorsCache = {};
+
+ // Only pseudo-classes that accept selector lists should recurse
+ var selectorListPseudoClasses = {
+ 'not': true,
+ 'is': true,
+ 'has': true,
+ 'where': true
+ };
+
+ function validateSelector(selector) {
+ if (validatedSelectorsCache.hasOwnProperty(selector)) {
+ return validatedSelectorsCache[selector];
+ }
+
+ // Use function-based parsing to extract pseudo-classes (avoids backtracking)
+ var pseudoClassMatches = extractPseudoClasses(selector);
+
+ for (var j = 0; j < pseudoClassMatches.length; j++) {
+ var pseudoClass = pseudoClassMatches[j][1];
+ if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
+ var nestedSelectors = parseAndSplitNestedSelectors(pseudoClassMatches[j][2]);
+
+ // Check if ANY selector in the list contains & (nesting selector)
+ // If so, skip validation for the entire selector list since & will be replaced at runtime
+ var hasAmpersand = false;
+ for (var k = 0; k < nestedSelectors.length; k++) {
+ if (ampersandRegExp.test(nestedSelectors[k])) {
+ hasAmpersand = true;
+ break;
+ }
+ }
+
+ // If any selector has &, skip validation for this entire pseudo-class
+ if (hasAmpersand) {
+ continue;
+ }
+
+ // Otherwise, validate each selector normally
+ for (var i = 0; i < nestedSelectors.length; i++) {
+ var nestedSelector = nestedSelectors[i];
+ if (!validatedSelectorsCache.hasOwnProperty(nestedSelector)) {
+ var nestedSelectorValidation = validateSelector(nestedSelector);
+ validatedSelectorsCache[nestedSelector] = nestedSelectorValidation;
+ if (!nestedSelectorValidation) {
+ validatedSelectorsCache[selector] = false;
+ return false;
+ }
+ } else if (!validatedSelectorsCache[nestedSelector]) {
+ validatedSelectorsCache[selector] = false;
+ return false;
+ }
+ }
+ }
+ }
+
+ var basicSelectorValidation = basicSelectorValidator(selector);
+ validatedSelectorsCache[selector] = basicSelectorValidation;
+
+ return basicSelectorValidation;
+ }
+
+ /**
+ * Validates namespace selectors by checking if the namespace prefix is defined.
+ *
+ * @param {string} selector - The CSS selector to validate
+ * @returns {boolean} Returns true if the namespace is valid, false otherwise
+ */
+ function validateNamespaceSelector(selector) {
+ // Check if selector contains a namespace prefix
+ // We need to ignore pipes inside attribute selectors
+ var pipeIndex = -1;
+ var inAttr = false;
+ var inSingleQuote = false;
+ var inDoubleQuote = false;
+
+ for (var i = 0; i < selector.length; i++) {
+ var char = selector[i];
+
+ // Handle escape sequences - skip hex escapes or simple escapes
+ if (char === '\\') {
+ var escapeLen = getEscapeSequenceLength(selector, i);
+ if (escapeLen > 0) {
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+
+ if (inSingleQuote) {
+ if (char === "'") {
+ inSingleQuote = false;
+ }
+ } else if (inDoubleQuote) {
+ if (char === '"') {
+ inDoubleQuote = false;
+ }
+ } else if (inAttr) {
+ if (char === "]") {
+ inAttr = false;
+ } else if (char === "'") {
+ inSingleQuote = true;
+ } else if (char === '"') {
+ inDoubleQuote = true;
+ }
+ } else {
+ if (char === "[") {
+ inAttr = true;
+ } else if (char === "|" && !inAttr) {
+ // This is a namespace separator, not an attribute operator
+ pipeIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (pipeIndex === -1) {
+ return true; // No namespace, always valid
+ }
+
+ var namespacePrefix = selector.substring(0, pipeIndex);
+
+ // Universal namespace (*|) and default namespace (|) are always valid
+ if (namespacePrefix === '*' || namespacePrefix === '') {
+ return true;
+ }
+
+ // Check if the custom namespace prefix is defined
+ return definedNamespacePrefixes.hasOwnProperty(namespacePrefix);
+ }
+
+ /**
+ * Normalizes escape sequences in a selector to match browser behavior.
+ * Decodes escape sequences and re-encodes them in canonical form.
+ *
+ * @param {string} selector - The selector to normalize
+ * @returns {string} Normalized selector
+ */
+ function normalizeSelectorEscapes(selector) {
+ var result = '';
+ var i = 0;
+ var nextChar = '';
+ var inAttribute = false;
+ var attributeDepth = 0;
+ var needsEscapeForIdent = false;
+ var lastWasHexEscape = false;
+
+ while (i < selector.length) {
+ var char = selector[i];
+
+ // Track attribute selector context
+ if (char === '[' && !inAttribute) {
+ inAttribute = true;
+ attributeDepth = 1;
+ result += char;
+ i++;
+ needsEscapeForIdent = false;
+ lastWasHexEscape = false;
+ continue;
+ }
+
+ if (inAttribute) {
+ if (char === '[') attributeDepth++;
+ if (char === ']') {
+ attributeDepth--;
+ if (attributeDepth === 0) inAttribute = false;
+ }
+ // Don't normalize escapes inside attribute selectors
+ if (char === '\\' && i + 1 < selector.length) {
+ var escapeLen = getEscapeSequenceLength(selector, i);
+ result += selector.substr(i, escapeLen);
+ i += escapeLen;
+ } else {
+ result += char;
+ i++;
+ }
+ lastWasHexEscape = false;
+ continue;
+ }
+
+ // Handle escape sequences
+ if (char === '\\') {
+ var escapeLen = getEscapeSequenceLength(selector, i);
+ if (escapeLen > 0) {
+ var escapeSeq = selector.substr(i, escapeLen);
+ var decoded = decodeEscapeSequence(escapeSeq);
+ var wasHexEscape = startsWithHexEscapeRegExp.test(escapeSeq);
+ var hadTerminatingSpace = wasHexEscape && escapeSeq[escapeLen - 1] === ' ';
+ nextChar = selector[i + escapeLen] || '';
+
+ // Check if this character needs escaping
+ var needsEscape = false;
+ var useHexEscape = false;
+
+ if (needsEscapeForIdent) {
+ // At start of identifier (after . # or -)
+ // Digits must be escaped, letters/underscore/_/- don't need escaping
+ if (isDigit(decoded)) {
+ needsEscape = true;
+ useHexEscape = true;
+ } else if (decoded === '-') {
+ // Dash at identifier start: keep escaped if it's the only character,
+ // otherwise it can be decoded
+ var remainingSelector = selector.substring(i + escapeLen);
+ var hasMoreIdentChars = remainingSelector && identCharRegExp.test(remainingSelector[0]);
+ needsEscape = !hasMoreIdentChars;
+ } else if (!identStartCharRegExp.test(decoded)) {
+ needsEscape = true;
+ }
+ } else {
+ if (specialCharsNeedEscapeRegExp.test(decoded)) {
+ needsEscape = true;
+ }
+ }
+
+ if (needsEscape) {
+ if (useHexEscape) {
+ // Use normalized hex escape
+ var codePoint = decoded.charCodeAt(0);
+ var hex = codePoint.toString(16);
+ result += '\\' + hex;
+ // Add space if next char could continue the hex sequence,
+ // or if at end of selector (to disambiguate the escape)
+ if (isHexDigit(nextChar) || !nextChar || afterHexEscapeSeparatorRegExp.test(nextChar)) {
+ result += ' ';
+ lastWasHexEscape = false;
+ } else {
+ lastWasHexEscape = true;
+ }
+ } else {
+ // Use simple character escape
+ result += '\\' + decoded;
+ lastWasHexEscape = false;
+ }
+ } else {
+ // No escape needed, use the character directly
+ // But if previous was hex escape (without terminating space) and this is alphanumeric, add space
+ if (lastWasHexEscape && !hadTerminatingSpace && isAlphanumeric(decoded)) {
+ result += ' ';
+ }
+ result += decoded;
+ // Preserve terminating space at end of selector (when followed by non-ident char)
+ if (hadTerminatingSpace && (!nextChar || afterHexEscapeSeparatorRegExp.test(nextChar))) {
+ result += ' ';
+ }
+ lastWasHexEscape = false;
+ }
+
+ i += escapeLen;
+ // After processing escape, check if we're still needing ident validation
+ // Only stay in needsEscapeForIdent state if decoded was '-'
+ needsEscapeForIdent = needsEscapeForIdent && decoded === '-';
+ continue;
+ }
+ }
+
+ // Handle regular characters
+ if (char === '.' || char === '#') {
+ result += char;
+ needsEscapeForIdent = true;
+ lastWasHexEscape = false;
+ i++;
+ } else if (char === '-' && needsEscapeForIdent) {
+ // Dash after . or # - next char must be valid ident start or digit (which needs escaping)
+ result += char;
+ needsEscapeForIdent = true;
+ lastWasHexEscape = false;
+ i++;
+ } else if (isDigit(char) && needsEscapeForIdent) {
+ // Digit at identifier start must be hex escaped
+ var codePoint = char.charCodeAt(0);
+ var hex = codePoint.toString(16);
+ result += '\\' + hex;
+ nextChar = selector[i + 1] || '';
+ // Add space if next char could continue the hex sequence,
+ // or if at end of selector (to disambiguate the escape)
+ if (isHexDigit(nextChar) || !nextChar || afterHexEscapeSeparatorRegExp.test(nextChar)) {
+ result += ' ';
+ lastWasHexEscape = false;
+ } else {
+ lastWasHexEscape = true;
+ }
+ needsEscapeForIdent = false;
+ i++;
+ } else if (char === ':' || combinatorOrSeparatorRegExp.test(char)) {
+ // Combinators, separators, and pseudo-class markers reset identifier state
+ // Preserve trailing space from hex escape
+ if (!(char === ' ' && lastWasHexEscape && result[result.length - 1] === ' ')) {
+ result += char;
+ }
+ needsEscapeForIdent = false;
+ lastWasHexEscape = false;
+ i++;
+ } else if (isLetter(char) && lastWasHexEscape) {
+ // Letter after hex escape needs a space separator
+ result += ' ' + char;
+ needsEscapeForIdent = false;
+ lastWasHexEscape = false;
+ i++;
+ } else if (char === ' ' && lastWasHexEscape) {
+ // Trailing space - keep it if at end or before non-ident char
+ nextChar = selector[i + 1] || '';
+ if (!nextChar || trailingSpaceSeparatorRegExp.test(nextChar)) {
+ result += char;
+ }
+ needsEscapeForIdent = false;
+ lastWasHexEscape = false;
+ i++;
+ } else {
+ result += char;
+ needsEscapeForIdent = false;
+ lastWasHexEscape = false;
+ i++;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Helper function to decode all escape sequences in a string.
+ *
+ * @param {string} str - The string to decode
+ * @returns {string} The decoded string
+ */
+ function decodeEscapeSequencesInString(str) {
+ var result = '';
+ for (var i = 0; i < str.length; i++) {
+ if (str[i] === '\\' && i + 1 < str.length) {
+ // Get the escape sequence length
+ var escapeLen = getEscapeSequenceLength(str, i);
+ if (escapeLen > 0) {
+ var escapeSeq = str.substr(i, escapeLen);
+ var decoded = decodeEscapeSequence(escapeSeq);
+ result += decoded;
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+ result += str[i];
+ }
+ return result;
+ }
+
+ /**
+ * Decodes a CSS escape sequence to its character value.
+ *
+ * @param {string} escapeSeq - The escape sequence (including backslash)
+ * @returns {string} The decoded character
+ */
+ function decodeEscapeSequence(escapeSeq) {
+ if (escapeSeq.length < 2 || escapeSeq[0] !== '\\') {
+ return escapeSeq;
+ }
+
+ var content = escapeSeq.substring(1);
+
+ // Check if it's a hex escape
+ var hexMatch = content.match(hexEscapeSequenceRegExp);
+ if (hexMatch) {
+ var codePoint = parseInt(hexMatch[1], 16);
+ // Handle surrogate pairs for code points > 0xFFFF
+ if (codePoint > 0xFFFF) {
+ // Convert to surrogate pair
+ codePoint -= 0x10000;
+ var high = 0xD800 + (codePoint >> 10);
+ var low = 0xDC00 + (codePoint & 0x3FF);
+ return String.fromCharCode(high, low);
+ }
+ return String.fromCharCode(codePoint);
+ }
+
+ // Simple escape - return the character after backslash
+ return content[0] || '';
+ }
+
+ /**
+ * Normalizes attribute selectors by ensuring values are properly quoted with double quotes.
+ * Examples:
+ * [attr=value] -> [attr="value"]
+ * [attr="value"] -> [attr="value"] (unchanged)
+ * [attr='value'] -> [attr="value"] (converted to double quotes)
+ *
+ * @param {string} selector - The selector to normalize
+ * @returns {string|null} Normalized selector, or null if invalid
+ */
+ function normalizeAttributeSelectors(selector) {
+ var result = '';
+ var i = 0;
+
+ while (i < selector.length) {
+ // Look for attribute selector start
+ if (selector[i] === '[') {
+ result += '[';
+ i++;
+
+ var attrContent = '';
+ var depth = 1;
+
+ // Find the closing bracket, handling nested brackets and escapes
+ while (i < selector.length && depth > 0) {
+ if (selector[i] === '\\' && i + 1 < selector.length) {
+ attrContent += selector.substring(i, i + 2);
+ i += 2;
+ continue;
+ }
+ if (selector[i] === '[') depth++;
+ if (selector[i] === ']') {
+ depth--;
+ if (depth === 0) break;
+ }
+ attrContent += selector[i];
+ i++;
+ }
+
+ // Normalize the attribute content
+ var normalized = normalizeAttributeContent(attrContent);
+ if (normalized === null) {
+ // Invalid attribute selector (e.g., unclosed quote)
+ return null;
+ }
+ result += normalized;
+ if (i < selector.length && selector[i] === ']') {
+ result += ']';
+ i++;
+ }
+ } else {
+ result += selector[i];
+ i++;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Processes a quoted attribute value by checking for proper closure and decoding escape sequences.
+ * @param {string} trimmedValue - The quoted value (with quotes)
+ * @param {string} quoteChar - The quote character ('"' or "'")
+ * @param {string} attrName - The attribute name
+ * @param {string} operator - The attribute operator
+ * @param {string} flag - Optional case-sensitivity flag
+ * @returns {string|null} Normalized attribute content, or null if invalid
+ */
+ function processQuotedAttributeValue(trimmedValue, quoteChar, attrName, operator, flag) {
+ // Check if the closing quote is properly closed (not escaped)
+ if (trimmedValue.length < 2) {
+ return null; // Too short
+ }
+ // Find the actual closing quote (not escaped)
+ var i = 1;
+ var foundClose = false;
+ while (i < trimmedValue.length) {
+ if (trimmedValue[i] === '\\' && i + 1 < trimmedValue.length) {
+ // Skip escape sequence
+ var escapeLen = getEscapeSequenceLength(trimmedValue, i);
+ i += escapeLen;
+ continue;
+ }
+ if (trimmedValue[i] === quoteChar) {
+ // Found closing quote
+ foundClose = (i === trimmedValue.length - 1);
+ break;
+ }
+ i++;
+ }
+ if (!foundClose) {
+ return null; // Unclosed quote - invalid
+ }
+ // Extract inner value and decode escape sequences
+ var innerValue = trimmedValue.slice(1, -1);
+ var decodedValue = decodeEscapeSequencesInString(innerValue);
+ // If decoded value contains quotes, we need to escape them
+ var escapedValue = decodedValue.replace(doubleQuoteRegExp, '\\"');
+ return attrName + operator + '"' + escapedValue + '"' + (flag ? ' ' + flag : '');
+ }
+
+ /**
+ * Normalizes the content inside an attribute selector.
+ * @param {string} content - The content between [ and ]
+ * @returns {string} Normalized content, or null if invalid
+ */
+ function normalizeAttributeContent(content) {
+ // Match: attribute-name [operator] [value] [flag]
+ var match = content.match(attributeSelectorContentRegExp);
+
+ if (!match) {
+ // No operator (e.g., [disabled]) or malformed - return as is
+ return content;
+ }
+
+ var attrName = match[1];
+ var operator = match[2];
+ var valueAndFlag = match[3].trim(); // Trim here instead of in regex
+
+ // Check if there's a case-sensitivity flag (i or s) at the end
+ var flagMatch = valueAndFlag.match(attributeCaseFlagRegExp);
+ var value = flagMatch ? flagMatch[1] : valueAndFlag;
+ var flag = flagMatch ? flagMatch[2] : '';
+
+ // Check for unclosed quotes - this makes the selector invalid
+ var trimmedValue = value.trim();
+ var firstChar = trimmedValue[0];
+
+ if (firstChar === '"') {
+ return processQuotedAttributeValue(trimmedValue, '"', attrName, operator, flag);
+ }
+
+ if (firstChar === "'") {
+ return processQuotedAttributeValue(trimmedValue, "'", attrName, operator, flag);
+ }
+
+ // Check for unescaped special characters in unquoted values
+ // Escaped special characters are valid (e.g., \` is valid, but ` is not)
+ var hasUnescapedSpecialChar = false;
+ for (var i = 0; i < trimmedValue.length; i++) {
+ var char = trimmedValue[i];
+ if (char === '\\' && i + 1 < trimmedValue.length) {
+ // Skip the entire escape sequence
+ var escapeLen = getEscapeSequenceLength(trimmedValue, i);
+ if (escapeLen > 0) {
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+ // Check if this is an unescaped special character
+ if (specialCharsNeedEscapeRegExp.test(char)) {
+ hasUnescapedSpecialChar = true;
+ break;
+ }
+ }
+
+ if (hasUnescapedSpecialChar) {
+ return null; // Unescaped special characters not allowed in unquoted attribute values
+ }
+
+ // Decode escape sequences in the value before quoting
+ // Inside quotes, special characters don't need escaping
+ var decodedValue = decodeEscapeSequencesInString(trimmedValue);
+
+ // If the decoded value contains double quotes, escape them for the output
+ // (since we're using double quotes as delimiters)
+ var escapedValue = decodedValue.replace(backslashRegExp, '\\\\').replace(doubleQuoteRegExp, '\\"');
+
+ // Unquoted value - add double quotes with decoded and re-escaped content
+ return attrName + operator + '"' + escapedValue + '"' + (flag ? ' ' + flag : '');
+ }
+
+ /**
+ * Processes a CSS selector text
+ *
+ * @param {string} selectorText - The CSS selector text to process
+ * @returns {string} The processed selector text with normalized whitespace and invalid selectors removed
+ */
+ function processSelectorText(selectorText) {
+ // Normalize whitespace first
+ var normalized = selectorText.replace(whitespaceNormalizationRegExp, function (match, _, newline) {
+ if (newline) return " ";
+ return match;
+ });
+
+ // Normalize escape sequences to match browser behavior
+ normalized = normalizeSelectorEscapes(normalized);
+
+ // Normalize attribute selectors (add quotes to unquoted values)
+ // Returns null if invalid (e.g., unclosed quotes)
+ normalized = normalizeAttributeSelectors(normalized);
+ if (normalized === null) {
+ return ''; // Invalid selector - return empty to trigger validation failure
+ }
+
+ // Recursively process pseudo-classes to handle nesting
+ return processNestedPseudoClasses(normalized);
+ }
+
+ /**
+ * Recursively processes pseudo-classes to filter invalid selectors
+ *
+ * @param {string} selectorText - The CSS selector text to process
+ * @param {number} depth - Current recursion depth (to prevent infinite loops)
+ * @returns {string} The processed selector text with invalid selectors removed
+ */
+ function processNestedPseudoClasses(selectorText, depth) {
+ // Prevent infinite recursion
+ if (typeof depth === 'undefined') {
+ depth = 0;
+ }
+ if (depth > 10) {
+ return selectorText;
+ }
+
+ var pseudoClassMatches = extractPseudoClasses(selectorText);
+
+ // If no pseudo-classes found, return as-is
+ if (pseudoClassMatches.length === 0) {
+ return selectorText;
+ }
+
+ // Build result by processing matches from right to left (to preserve positions)
+ var result = selectorText;
+
+ for (var j = pseudoClassMatches.length - 1; j >= 0; j--) {
+ var pseudoClass = pseudoClassMatches[j][1];
+ if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
+ var fullMatch = pseudoClassMatches[j][0];
+ var pseudoArgs = pseudoClassMatches[j][2];
+ var matchStart = pseudoClassMatches[j][3];
+
+ // Check if ANY selector contains & BEFORE processing
+ var nestedSelectorsRaw = parseAndSplitNestedSelectors(pseudoArgs);
+ var hasAmpersand = false;
+ for (var k = 0; k < nestedSelectorsRaw.length; k++) {
+ if (ampersandRegExp.test(nestedSelectorsRaw[k])) {
+ hasAmpersand = true;
+ break;
+ }
+ }
+
+ // If & is present, skip all processing (keep everything unchanged)
+ if (hasAmpersand) {
+ continue;
+ }
+
+ // Recursively process the arguments
+ var processedArgs = processNestedPseudoClasses(pseudoArgs, depth + 1);
+ var nestedSelectors = parseAndSplitNestedSelectors(processedArgs);
+
+ // Filter out invalid selectors
+ var validSelectors = [];
+ for (var i = 0; i < nestedSelectors.length; i++) {
+ var nestedSelector = nestedSelectors[i];
+ if (basicSelectorValidator(nestedSelector)) {
+ validSelectors.push(nestedSelector);
+ }
+ }
+
+ // Reconstruct the pseudo-class with only valid selectors
+ var newArgs = validSelectors.join(', ');
+ var newPseudoClass = ':' + pseudoClass + '(' + newArgs + ')';
+
+ // Replace in the result string using position (processing right to left preserves positions)
+ result = result.substring(0, matchStart) + newPseudoClass + result.substring(matchStart + fullMatch.length);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Checks if a selector contains newlines inside quoted strings.
+ * Uses iterative parsing to avoid regex backtracking issues.
+ * @param {string} selectorText - The selector to check
+ * @returns {boolean} True if newlines found inside quotes
+ */
+ function hasNewlineInQuotedString(selectorText) {
+ for (var i = 0; i < selectorText.length; i++) {
+ var char = selectorText[i];
+
+ // Start of single-quoted string
+ if (char === "'") {
+ i++;
+ while (i < selectorText.length) {
+ if (selectorText[i] === '\\' && i + 1 < selectorText.length) {
+ // Skip escape sequence
+ i += 2;
+ continue;
+ }
+ if (selectorText[i] === "'") {
+ // End of string
+ break;
+ }
+ if (selectorText[i] === '\r' || selectorText[i] === '\n') {
+ return true;
+ }
+ i++;
+ }
+ }
+ // Start of double-quoted string
+ else if (char === '"') {
+ i++;
+ while (i < selectorText.length) {
+ if (selectorText[i] === '\\' && i + 1 < selectorText.length) {
+ // Skip escape sequence
+ i += 2;
+ continue;
+ }
+ if (selectorText[i] === '"') {
+ // End of string
+ break;
+ }
+ if (selectorText[i] === '\r' || selectorText[i] === '\n') {
+ return true;
+ }
+ i++;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a given CSS selector text is valid by splitting it by commas
+ * and validating each individual selector using the `validateSelector` function.
+ *
+ * @param {string} selectorText - The CSS selector text to validate. Can contain multiple selectors separated by commas.
+ * @returns {boolean} Returns true if all selectors are valid, otherwise false.
+ */
+ function isValidSelectorText(selectorText) {
+ // TODO: The same validations here needs to be reused in CSSStyleRule.selectorText setter
+ // TODO: Move these validation logic to a shared function to be reused in CSSStyleRule.selectorText setter
+
+ // Check for empty or whitespace-only selector
+ if (!selectorText || selectorText.trim() === '') {
+ return false;
+ }
+
+ // Check for empty selector lists in pseudo-classes (e.g., :is(), :not(), :where(), :has())
+ // These are invalid after filtering out invalid selectors
+ if (emptyPseudoClassRegExp.test(selectorText)) {
+ return false;
+ }
+
+ // Check for newlines inside single or double quotes
+ // Uses helper function to avoid regex security issues
+ if (hasNewlineInQuotedString(selectorText)) {
+ return false;
+ }
+
+ // Split selectorText by commas and validate each part
+ var selectors = parseAndSplitNestedSelectors(selectorText);
+ for (var i = 0; i < selectors.length; i++) {
+ var selector = selectors[i].trim();
+ if (!validateSelector(selector) || !validateNamespaceSelector(selector)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function pushToAncestorRules(rule) {
+ ancestorRules.push(rule);
+ }
+
+ function parseError(message, isNested) {
+ var lines = token.substring(0, i).split('\n');
+ var lineCount = lines.length;
+ var charCount = lines.pop().length + 1;
+ var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
+ error.line = lineCount;
+ /* jshint sub : true */
+ error['char'] = charCount;
+ error.styleSheet = styleSheet;
+ error.isNested = !!isNested;
+ // Print the error but continue parsing the sheet
+ try {
+ throw error;
+ } catch (e) {
+ errorHandler && errorHandler(e);
+ }
+ }
+ /**
+ * Handles invalid selectors with unmatched quotes by skipping the entire rule block.
+ * @param {string} nextState - The parser state to transition to after skipping
+ */
+ function handleUnmatchedQuoteInSelector(nextState) {
+ // parseError('Invalid selector with unmatched quote: ' + buffer.trim());
+ // Skip this entire invalid rule including its block
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
+ if (ruleClosingMatch) {
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
+ }
+ styleRule = null;
+ buffer = "";
+ hasUnmatchedQuoteInSelector = false; // Reset flag
+ state = nextState;
+ }
+
+ // Helper functions to check character types
+ function isSelectorStartChar(char) {
+ return '.:#&*[\\'.indexOf(char) !== -1;
+ }
+
+ function isWhitespaceChar(char) {
+ return ' \t\n\r'.indexOf(char) !== -1;
+ }
+
+ // Helper functions for character type checking (faster than regex for single chars)
+ function isDigit(char) {
+ var code = char.charCodeAt(0);
+ return code >= 0x0030 && code <= 0x0039; // 0-9
+ }
+
+ function isHexDigit(char) {
+ if (!char) return false;
+ var code = char.charCodeAt(0);
+ return (code >= 0x0030 && code <= 0x0039) || // 0-9
+ (code >= 0x0041 && code <= 0x0046) || // A-F
+ (code >= 0x0061 && code <= 0x0066); // a-f
+ }
+
+ function isLetter(char) {
+ if (!char) return false;
+ var code = char.charCodeAt(0);
+ return (code >= 0x0041 && code <= 0x005A) || // A-Z
+ (code >= 0x0061 && code <= 0x007A); // a-z
+ }
+
+ function isAlphanumeric(char) {
+ var code = char.charCodeAt(0);
+ return (code >= 0x0030 && code <= 0x0039) || // 0-9
+ (code >= 0x0041 && code <= 0x005A) || // A-Z
+ (code >= 0x0061 && code <= 0x007A); // a-z
+ }
+
+ /**
+ * Get the length of an escape sequence starting at the given position.
+ * CSS escape sequences are:
+ * - Backslash followed by 1-6 hex digits, optionally followed by a whitespace (consumed)
+ * - Backslash followed by any non-hex character
+ * @param {string} str - The string to check
+ * @param {number} pos - Position of the backslash
+ * @returns {number} Number of characters in the escape sequence (including backslash)
+ */
+ function getEscapeSequenceLength(str, pos) {
+ if (str[pos] !== '\\' || pos + 1 >= str.length) {
+ return 0;
+ }
+
+ var nextChar = str[pos + 1];
+
+ // Check if it's a hex escape
+ if (isHexDigit(nextChar)) {
+ var hexLength = 1;
+ // Count up to 6 hex digits
+ while (hexLength < 6 && pos + 1 + hexLength < str.length && isHexDigit(str[pos + 1 + hexLength])) {
+ hexLength++;
+ }
+ // Check if followed by optional whitespace (which gets consumed)
+ if (pos + 1 + hexLength < str.length && isWhitespaceChar(str[pos + 1 + hexLength])) {
+ return 1 + hexLength + 1; // backslash + hex digits + whitespace
+ }
+ return 1 + hexLength; // backslash + hex digits
+ }
+
+ // Simple escape: backslash + any character
+ return 2;
+ }
+
+ /**
+ * Check if a string contains an unescaped occurrence of a specific character
+ * @param {string} str - The string to search
+ * @param {string} char - The character to look for
+ * @returns {boolean} True if the character appears unescaped
+ */
+ function containsUnescaped(str, char) {
+ for (var i = 0; i < str.length; i++) {
+ if (str[i] === '\\') {
+ var escapeLen = getEscapeSequenceLength(str, i);
+ if (escapeLen > 0) {
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+ if (str[i] === char) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Validates media query syntax for invalid patterns.
+ *
+ * @param {string} text - The media/condition text to validate
+ * @returns {boolean} True if invalid, false if valid
+ */
+ function isInvalidConditionText(text) {
+ return invalidMediaOperatorWithoutSpaceRegExp.test(text) ||
+ invalidMediaNotAfterOperatorRegExp.test(text) ||
+ invalidMediaNotIdentifierRegExp.test(text) ||
+ invalidMediaNotAfterFeatureRegExp.test(text) ||
+ invalidMediaOperatorBetweenParensRegExp.test(text) ||
+ invalidMediaNotWithMediaTypeRegExp.test(text);
+ }
+
+ /**
+ * Normalizes media query or condition text to match browser serialization.
+ * Ensures spaces after colons in media features and around logical operators.
+ * Returns "not all" for invalid syntax (e.g., missing spaces after operators).
+ *
+ * @param {string} text - The media/condition text to normalize
+ * @returns {string} Normalized text or "not all" if invalid
+ */
+ function normalizeConditionText(text) {
+ if (isInvalidConditionText(text)) {
+ return 'not all';
+ }
+
+ var result = text;
+
+ // Normalize colons only in media features, not in function calls
+ // Function syntax: identifier( - should be preserved as-is
+ // Media feature: (name: value) - should normalize colon spacing
+ // Strategy: only normalize colons that come after '(' without an identifier before it
+ result = result.replace(mediaFeatureColonRegExp, function(match, openParen, name, spaces, rest) {
+ // Check if there's an identifier immediately before the opening paren
+ var beforeMatch = result.lastIndexOf(match);
+ var beforeParen = beforeMatch > 0 ? result.charAt(beforeMatch - 1) : '';
+
+ // If character before '(' is alphanumeric, it's a function call - keep as-is
+ if (identifierBeforeParenRegExp.test(beforeParen)) {
+ return match;
+ }
+
+ // Otherwise it's a media feature - normalize colon
+ return openParen + name + ': ' + rest;
+ });
+
+ // Normalize 'and' operator with word boundaries: ensure exactly one space before and after
+ result = result.replace(mediaAndOperatorRegExp, ' and ');
+
+ // Normalize 'or' operator with word boundaries: ensure exactly one space before and after
+ result = result.replace(mediaOrOperatorRegExp, ' or ');
+
+ // Normalize 'not' operator only when followed by space (not when followed by '(')
+ // 'not(' is a valid identifier/function, 'not ' is the operator
+ // At start: ensure exactly one space after
+ result = result.replace(mediaNotOperatorStartRegExp, 'not ');
+
+ // After space: ensure exactly one space before and after (but not 'not(')
+ result = result.replace(mediaNotOperatorRegExp, ' not ');
+
+ return result.trim();
+ }
+
+ var endingIndex = token.length - 1;
+ var initialEndingIndex = endingIndex;
+
+ for (var character; (character = token.charAt(i)); i++) {
+ if (i === endingIndex) {
+ switch (state) {
+ case "importRule":
+ case "namespaceRule":
+ case "layerBlock":
+ if (character !== ";") {
+ token += ";";
+ endingIndex += 1;
+ }
+ break;
+ case "value":
+ if (character !== "}") {
+ if (character === ";") {
+ token += "}";
+ } else {
+ token += ";";
+ }
+ endingIndex += 1;
+ break;
+ }
+ case "name":
+ case "before-name":
+ if (character === "}") {
+ token += " ";
+ } else {
+ token += "}";
+ }
+ endingIndex += 1;
+ break;
+ case "before-selector":
+ if (character !== "}" && currentScope !== styleSheet) {
+ token += "}";
+ endingIndex += 1;
+ break;
+ }
+ }
+ }
+
+ // Handle escape sequences before processing special characters
+ // CSS escape sequences: \HHHHHH (1-6 hex digits) optionally followed by whitespace, or \ + any char
+ if (character === '\\' && i + 1 < token.length) {
+ var escapeLen = getEscapeSequenceLength(token, i);
+ if (escapeLen > 0) {
+ buffer += token.substr(i, escapeLen);
+ i += escapeLen - 1; // -1 because loop will increment
+ continue;
+ }
+ }
+
+ switch (character) {
+
+ case " ":
+ case "\t":
+ case "\r":
+ case "\n":
+ case "\f":
+ if (SIGNIFICANT_WHITESPACE[state]) {
+ buffer += character;
+ }
+ break;
+
+ // String
+ case '"':
+ index = i + 1;
+ do {
+ index = token.indexOf('"', index) + 1;
+ if (!index) {
+ parseError('Unmatched "');
+ // If we're parsing a selector, flag it as invalid
+ if (state === "selector" || state === "atRule") {
+ hasUnmatchedQuoteInSelector = true;
+ }
+ }
+ } while (token[index - 2] === '\\');
+ if (index === 0) {
+ break;
+ }
+ buffer += token.slice(i, index);
+ i = index - 1;
+ switch (state) {
+ case 'before-value':
+ state = 'value';
+ break;
+ case 'importRule-begin':
+ state = 'importRule';
+ if (i === endingIndex) {
+ token += ';';
+ }
+ break;
+ case 'namespaceRule-begin':
+ state = 'namespaceRule';
+ if (i === endingIndex) {
+ token += ';';
+ }
+ break;
+ }
+ break;
+
+ case "'":
+ index = i + 1;
+ do {
+ index = token.indexOf("'", index) + 1;
+ if (!index) {
+ parseError("Unmatched '");
+ // If we're parsing a selector, flag it as invalid
+ if (state === "selector" || state === "atRule") {
+ hasUnmatchedQuoteInSelector = true;
+ }
+ }
+ } while (token[index - 2] === '\\');
+ if (index === 0) {
+ break;
+ }
+ buffer += token.slice(i, index);
+ i = index - 1;
+ switch (state) {
+ case 'before-value':
+ state = 'value';
+ break;
+ case 'importRule-begin':
+ state = 'importRule';
+ break;
+ case 'namespaceRule-begin':
+ state = 'namespaceRule';
+ break;
+ }
+ break;
+
+ // Comment
+ case "/":
+ if (token.charAt(i + 1) === "*") {
+ i += 2;
+ index = token.indexOf("*/", i);
+ if (index === -1) {
+ i = token.length - 1;
+ buffer = "";
+ } else {
+ i = index + 1;
+ }
+ } else {
+ buffer += character;
+ }
+ if (state === "importRule-begin") {
+ buffer += " ";
+ state = "importRule";
+ }
+ if (state === "namespaceRule-begin") {
+ buffer += " ";
+ state = "namespaceRule";
+ }
+ break;
+
+ // At-rule
+ case "@":
+ if (nestedSelectorRule) {
+ if (styleRule && styleRule.constructor.name === "CSSNestedDeclarations") {
+ currentScope.cssRules.push(styleRule);
+ }
+ // Only reset styleRule to parent if styleRule is not the nestedSelectorRule itself
+ // This preserves nested selectors when followed immediately by @-rules
+ if (styleRule !== nestedSelectorRule && nestedSelectorRule.parentRule && nestedSelectorRule.parentRule.constructor.name === "CSSStyleRule") {
+ styleRule = nestedSelectorRule.parentRule;
+ }
+ // Don't reset nestedSelectorRule here - preserve it through @-rules
+ }
+ if (token.indexOf("@-moz-document", i) === i) {
+ validateAtRule("@-moz-document", function () {
+ state = "documentRule-begin";
+ documentRule = new CSSOM.CSSDocumentRule();
+ documentRule.__starts = i;
+ i += "-moz-document".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@media", i) === i) {
+ validateAtRule("@media", function () {
+ state = "atBlock";
+ mediaRule = new CSSOM.CSSMediaRule();
+ mediaRule.__starts = i;
+ i += "media".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@container", i) === i) {
+ validateAtRule("@container", function () {
+ state = "containerBlock";
+ containerRule = new CSSOM.CSSContainerRule();
+ containerRule.__starts = i;
+ i += "container".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@counter-style", i) === i) {
+ buffer = "";
+ // @counter-style can be nested only inside CSSScopeRule or CSSConditionRule
+ // and only if there's no CSSStyleRule in the parent chain
+ var cannotBeNested = !canAtRuleBeNested();
+ validateAtRule("@counter-style", function () {
+ state = "counterStyleBlock";
+ counterStyleRule = new CSSOM.CSSCounterStyleRule();
+ counterStyleRule.__starts = i;
+ i += "counter-style".length;
+ }, cannotBeNested);
+ break;
+ } else if (token.indexOf("@property", i) === i) {
+ buffer = "";
+ // @property can be nested only inside CSSScopeRule or CSSConditionRule
+ // and only if there's no CSSStyleRule in the parent chain
+ var cannotBeNested = !canAtRuleBeNested();
+ validateAtRule("@property", function () {
+ state = "propertyBlock";
+ propertyRule = new CSSOM.CSSPropertyRule();
+ propertyRule.__starts = i;
+ i += "property".length;
+ }, cannotBeNested);
+ break;
+ } else if (token.indexOf("@scope", i) === i) {
+ validateAtRule("@scope", function () {
+ state = "scopeBlock";
+ scopeRule = new CSSOM.CSSScopeRule();
+ scopeRule.__starts = i;
+ i += "scope".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@layer", i) === i) {
+ validateAtRule("@layer", function () {
+ state = "layerBlock";
+ layerBlockRule = new CSSOM.CSSLayerBlockRule();
+ layerBlockRule.__starts = i;
+ i += "layer".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@page", i) === i) {
+ validateAtRule("@page", function () {
+ state = "pageBlock";
+ pageRule = new CSSOM.CSSPageRule();
+ pageRule.__starts = i;
+ i += "page".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@supports", i) === i) {
+ validateAtRule("@supports", function () {
+ state = "conditionBlock";
+ supportsRule = new CSSOM.CSSSupportsRule();
+ supportsRule.__starts = i;
+ i += "supports".length;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@host", i) === i) {
+ validateAtRule("@host", function () {
+ state = "hostRule-begin";
+ i += "host".length;
+ hostRule = new CSSOM.CSSHostRule();
+ hostRule.__starts = i;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@starting-style", i) === i) {
+ validateAtRule("@starting-style", function () {
+ state = "startingStyleRule-begin";
+ i += "starting-style".length;
+ startingStyleRule = new CSSOM.CSSStartingStyleRule();
+ startingStyleRule.__starts = i;
+ });
+ buffer = "";
+ break;
+ } else if (token.indexOf("@import", i) === i) {
+ buffer = "";
+ validateAtRule("@import", function () {
+ state = "importRule-begin";
+ i += "import".length;
+ buffer += "@import";
+ }, true);
+ break;
+ } else if (token.indexOf("@namespace", i) === i) {
+ buffer = "";
+ validateAtRule("@namespace", function () {
+ state = "namespaceRule-begin";
+ i += "namespace".length;
+ buffer += "@namespace";
+ }, true);
+ break;
+ } else if (token.indexOf("@font-face", i) === i) {
+ buffer = "";
+ // @font-face can be nested only inside CSSScopeRule or CSSConditionRule
+ // and only if there's no CSSStyleRule in the parent chain
+ var cannotBeNested = !canAtRuleBeNested();
+ validateAtRule("@font-face", function () {
+ state = "fontFaceRule-begin";
+ i += "font-face".length;
+ fontFaceRule = new CSSOM.CSSFontFaceRule();
+ fontFaceRule.__starts = i;
+ }, cannotBeNested);
+ break;
+ } else {
+ // Reset lastIndex before using global regex (shared instance)
+ atKeyframesRegExp.lastIndex = i;
+ var matchKeyframes = atKeyframesRegExp.exec(token);
+ if (matchKeyframes && matchKeyframes.index === i) {
+ state = "keyframesRule-begin";
+ keyframesRule = new CSSOM.CSSKeyframesRule();
+ keyframesRule.__starts = i;
+ keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
+ i += matchKeyframes[0].length - 1;
+ buffer = "";
+ break;
+ } else if (state === "selector") {
+ state = "atRule";
+ }
+ }
+ buffer += character;
+ break;
+
+ case "{":
+ if (currentScope === topScope) {
+ nestedSelectorRule = null;
+ }
+ if (state === 'before-selector') {
+ parseError("Unexpected {");
+ i = ignoreBalancedBlock(i, token.slice(i));
+ break;
+ }
+ if (state === "selector" || state === "atRule") {
+ if (!nestedSelectorRule && containsUnescaped(buffer, ";")) {
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
+ if (ruleClosingMatch) {
+ styleRule = null;
+ buffer = "";
+ state = "before-selector";
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length;
+ break;
+ }
+ }
+
+ // Ensure styleRule exists before trying to set properties on it
+ if (!styleRule) {
+ styleRule = new CSSOM.CSSStyleRule();
+ styleRule.__starts = i;
+ }
+
+ // Check if tokenizer detected an unmatched quote BEFORE setting up the rule
+ if (hasUnmatchedQuoteInSelector) {
+ handleUnmatchedQuoteInSelector("before-selector");
+ break;
+ }
+
+ var originalParentRule = parentRule;
+
+ if (parentRule) {
+ styleRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ }
+
+ currentScope = parentRule = styleRule;
+
+ var processedSelectorText = processSelectorText(buffer.trim());
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
+ if (originalParentRule && originalParentRule.constructor.name === "CSSStyleRule") {
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
+ }).join(', ');
+ } else {
+ // Normalize comma spacing: split by commas and rejoin with ", "
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).join(', ');
+ }
+ styleRule.style.__starts = i;
+ styleRule.__parentStyleSheet = styleSheet;
+ buffer = "";
+ state = "before-name";
+ } else if (state === "atBlock") {
+ mediaRule.media.mediaText = normalizeConditionText(buffer.trim());
+
+ if (parentRule) {
+ mediaRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ // If entering @media from within a CSSStyleRule, set nestedSelectorRule
+ // so that & selectors and declarations work correctly inside
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
+
+ currentScope = parentRule = mediaRule;
+ pushToAncestorRules(mediaRule);
+ mediaRule.__parentStyleSheet = styleSheet;
+
+ // Don't reset styleRule to null if it's a nested CSSStyleRule that will contain this @-rule
+ if (!styleRule || styleRule.constructor.name !== "CSSStyleRule" || !styleRule.__parentRule) {
+ styleRule = null; // Reset styleRule when entering @-rule
+ }
+
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "containerBlock") {
+ containerRule.__conditionText = normalizeConditionText(buffer.trim());
+
+ if (parentRule) {
+ containerRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
+ currentScope = parentRule = containerRule;
+ pushToAncestorRules(containerRule);
+ containerRule.__parentStyleSheet = styleSheet;
+ styleRule = null; // Reset styleRule when entering @-rule
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "counterStyleBlock") {
+ var counterStyleName = buffer.trim().replace(newlineRemovalRegExp, "");
+ // Validate: name cannot be empty, contain whitespace, or contain dots
+ var isValidCounterStyleName = counterStyleName.length > 0 && !whitespaceAndDotRegExp.test(counterStyleName);
+
+ if (isValidCounterStyleName) {
+ counterStyleRule.name = counterStyleName;
+ if (parentRule) {
+ counterStyleRule.__parentRule = parentRule;
+ }
+ counterStyleRule.__parentStyleSheet = styleSheet;
+ styleRule = counterStyleRule;
+ }
+ buffer = "";
+ } else if (state === "propertyBlock") {
+ var propertyName = buffer.trim().replace(newlineRemovalRegExp, "");
+ // Validate: name must start with -- (custom property)
+ var isValidPropertyName = propertyName.indexOf("--") === 0;
+
+ if (isValidPropertyName) {
+ propertyRule.__name = propertyName;
+ if (parentRule) {
+ propertyRule.__parentRule = parentRule;
+ }
+ propertyRule.__parentStyleSheet = styleSheet;
+ styleRule = propertyRule;
+ }
+ buffer = "";
+ } else if (state === "conditionBlock") {
+ supportsRule.__conditionText = normalizeConditionText(buffer.trim());
+
+ if (parentRule) {
+ supportsRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
+
+ currentScope = parentRule = supportsRule;
+ pushToAncestorRules(supportsRule);
+ supportsRule.__parentStyleSheet = styleSheet;
+ styleRule = null; // Reset styleRule when entering @-rule
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "scopeBlock") {
+ var parsedScopePrelude = parseScopePrelude(buffer.trim());
+
+ if (parsedScopePrelude.hasStart) {
+ scopeRule.__start = parsedScopePrelude.startSelector;
+ }
+ if (parsedScopePrelude.hasEnd) {
+ scopeRule.__end = parsedScopePrelude.endSelector;
+ }
+ if (parsedScopePrelude.hasOnlyEnd) {
+ scopeRule.__end = parsedScopePrelude.endSelector;
+ }
+
+ if (parentRule) {
+ scopeRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
+ currentScope = parentRule = scopeRule;
+ pushToAncestorRules(scopeRule);
+ scopeRule.__parentStyleSheet = styleSheet;
+ styleRule = null; // Reset styleRule when entering @-rule
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "layerBlock") {
+ layerBlockRule.name = buffer.trim();
+
+ var isValidName = layerBlockRule.name.length === 0 || layerBlockRule.name.match(cssCustomIdentifierRegExp) !== null;
+
+ if (isValidName) {
+ if (parentRule) {
+ layerBlockRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
+
+ currentScope = parentRule = layerBlockRule;
+ pushToAncestorRules(layerBlockRule);
+ layerBlockRule.__parentStyleSheet = styleSheet;
+ }
+ styleRule = null; // Reset styleRule when entering @-rule
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "pageBlock") {
+ pageRule.selectorText = buffer.trim();
+
+ if (parentRule) {
+ pageRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ }
- var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g;
+ currentScope = parentRule = pageRule;
+ pageRule.__parentStyleSheet = styleSheet;
+ styleRule = pageRule;
+ buffer = "";
+ state = "before-name";
+ } else if (state === "hostRule-begin") {
+ if (parentRule) {
+ pushToAncestorRules(parentRule);
+ }
- var parseError = function(message) {
- var lines = token.substring(0, i).split('\n');
- var lineCount = lines.length;
- var charCount = lines.pop().length + 1;
- var error = new Error(message + ' (line ' + lineCount + ', char ' + charCount + ')');
- error.line = lineCount;
- /* jshint sub : true */
- error['char'] = charCount;
- error.styleSheet = styleSheet;
- throw error;
- };
+ currentScope = parentRule = hostRule;
+ pushToAncestorRules(hostRule);
+ hostRule.__parentStyleSheet = styleSheet;
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "startingStyleRule-begin") {
+ if (parentRule) {
+ startingStyleRule.__parentRule = parentRule;
+ pushToAncestorRules(parentRule);
+ if (parentRule.constructor.name === "CSSStyleRule" && !nestedSelectorRule) {
+ nestedSelectorRule = parentRule;
+ }
+ }
- for (var character; (character = token.charAt(i)); i++) {
+ currentScope = parentRule = startingStyleRule;
+ pushToAncestorRules(startingStyleRule);
+ startingStyleRule.__parentStyleSheet = styleSheet;
+ styleRule = null; // Reset styleRule when entering @-rule
+ buffer = "";
+ state = "before-selector";
- switch (character) {
+ } else if (state === "fontFaceRule-begin") {
+ if (parentRule) {
+ fontFaceRule.__parentRule = parentRule;
+ }
+ fontFaceRule.__parentStyleSheet = styleSheet;
+ styleRule = fontFaceRule;
+ buffer = "";
+ state = "before-name";
+ } else if (state === "keyframesRule-begin") {
+ keyframesRule.name = buffer.trim();
+ if (parentRule) {
+ pushToAncestorRules(parentRule);
+ keyframesRule.__parentRule = parentRule;
+ }
+ keyframesRule.__parentStyleSheet = styleSheet;
+ currentScope = parentRule = keyframesRule;
+ buffer = "";
+ state = "keyframeRule-begin";
+ } else if (state === "keyframeRule-begin") {
+ styleRule = new CSSOM.CSSKeyframeRule();
+ styleRule.keyText = buffer.trim();
+ styleRule.__starts = i;
+ buffer = "";
+ state = "before-name";
+ } else if (state === "documentRule-begin") {
+ // FIXME: what if this '{' is in the url text of the match function?
+ documentRule.matcher.matcherText = buffer.trim();
+ if (parentRule) {
+ pushToAncestorRules(parentRule);
+ documentRule.__parentRule = parentRule;
+ }
+ currentScope = parentRule = documentRule;
+ pushToAncestorRules(documentRule);
+ documentRule.__parentStyleSheet = styleSheet;
+ buffer = "";
+ state = "before-selector";
+ } else if (state === "before-name" || state === "name") {
+ // @font-face and similar rules don't support nested selectors
+ // If we encounter a nested selector block inside them, skip it
+ if (styleRule.constructor.name === "CSSFontFaceRule" ||
+ styleRule.constructor.name === "CSSKeyframeRule" ||
+ (styleRule.constructor.name === "CSSPageRule" && parentRule === styleRule)) {
+ // Skip the nested block
+ var ruleClosingMatch = token.slice(i).match(forwardRuleClosingBraceRegExp);
+ if (ruleClosingMatch) {
+ i += ruleClosingMatch.index + ruleClosingMatch[0].length - 1;
+ buffer = "";
+ state = "before-name";
+ break;
+ }
+ }
- case " ":
- case "\t":
- case "\r":
- case "\n":
- case "\f":
- if (SIGNIFICANT_WHITESPACE[state]) {
- buffer += character;
- }
- break;
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
+ if (styleRule.style.length) {
+ parentRule.cssRules.push(styleRule);
+ styleRule.__parentRule = parentRule;
+ styleRule.__parentStyleSheet = styleSheet;
+ pushToAncestorRules(parentRule);
+ } else {
+ // If the styleRule is empty, we can assume that it's a nested selector
+ pushToAncestorRules(parentRule);
+ }
+ } else {
+ currentScope = parentRule = styleRule;
+ pushToAncestorRules(parentRule);
+ styleRule.__parentStyleSheet = styleSheet;
+ }
- // String
- case '"':
- index = i + 1;
- do {
- index = token.indexOf('"', index) + 1;
- if (!index) {
- parseError('Unmatched "');
- }
- } while (token[index - 2] === '\\');
- buffer += token.slice(i, index);
- i = index - 1;
- switch (state) {
- case 'before-value':
- state = 'value';
- break;
- case 'importRule-begin':
- state = 'importRule';
- break;
- }
- break;
+ styleRule = new CSSOM.CSSStyleRule();
+
+ // Check if tokenizer detected an unmatched quote BEFORE setting up the rule
+ if (hasUnmatchedQuoteInSelector) {
+ handleUnmatchedQuoteInSelector("before-name");
+ break;
+ }
+
+ var processedSelectorText = processSelectorText(buffer.trim());
+ // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
+ if (parentRule.constructor.name === "CSSScopeRule" || (parentRule.constructor.name !== "CSSStyleRule" && parentRule.parentRule === null)) {
+ // Normalize comma spacing: split by commas and rejoin with ", "
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).join(', ');
+ } else {
+ styleRule.selectorText = parseAndSplitNestedSelectors(processedSelectorText).map(function (sel) {
+ // Add & at the beginning if there's no & in the selector, or if it starts with a combinator
+ return (sel.indexOf('&') === -1 || startsWithCombinatorRegExp.test(sel)) ? '& ' + sel : sel;
+ }).join(', ');
+ }
+ styleRule.style.__starts = i - buffer.length;
+ styleRule.__parentRule = parentRule;
+ // Only set nestedSelectorRule if we're directly inside a CSSStyleRule or CSSScopeRule,
+ // not inside other grouping rules like @media/@supports
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
+ nestedSelectorRule = styleRule;
+ }
+
+ // Set __parentStyleSheet for the new nested styleRule
+ styleRule.__parentStyleSheet = styleSheet;
+
+ // Update currentScope and parentRule to the new nested styleRule
+ // so that subsequent content (like @-rules) will be children of this rule
+ currentScope = parentRule = styleRule;
- case "'":
- index = i + 1;
- do {
- index = token.indexOf("'", index) + 1;
- if (!index) {
- parseError("Unmatched '");
+ buffer = "";
+ state = "before-name";
}
- } while (token[index - 2] === '\\');
- buffer += token.slice(i, index);
- i = index - 1;
- switch (state) {
- case 'before-value':
- state = 'value';
- break;
- case 'importRule-begin':
- state = 'importRule';
- break;
- }
- break;
+ break;
- // Comment
- case "/":
- if (token.charAt(i + 1) === "*") {
- i += 2;
- index = token.indexOf("*/", i);
- if (index === -1) {
- parseError("Missing */");
+ case ":":
+ if (state === "name") {
+ // It can be a nested selector, let's check
+ var openBraceBeforeMatch = token.slice(i).match(declarationOrOpenBraceRegExp);
+ var hasOpenBraceBefore = openBraceBeforeMatch && openBraceBeforeMatch[0] === '{';
+ if (hasOpenBraceBefore) {
+ // Is a selector
+ buffer += character;
+ } else {
+ // Is a declaration
+ name = buffer.trim();
+ buffer = "";
+ state = "before-value";
+ }
} else {
- i = index + 1;
+ buffer += character;
}
- } else {
- buffer += character;
- }
- if (state === "importRule-begin") {
- buffer += " ";
- state = "importRule";
- }
- break;
-
- // At-rule
- case "@":
- if (token.indexOf("@-moz-document", i) === i) {
- state = "documentRule-begin";
- documentRule = new CSSOM.CSSDocumentRule();
- documentRule.__starts = i;
- i += "-moz-document".length;
- buffer = "";
- break;
- } else if (token.indexOf("@media", i) === i) {
- state = "atBlock";
- mediaRule = new CSSOM.CSSMediaRule();
- mediaRule.__starts = i;
- i += "media".length;
- buffer = "";
- break;
- } else if (token.indexOf("@supports", i) === i) {
- state = "conditionBlock";
- supportsRule = new CSSOM.CSSSupportsRule();
- supportsRule.__starts = i;
- i += "supports".length;
- buffer = "";
break;
- } else if (token.indexOf("@host", i) === i) {
- state = "hostRule-begin";
- i += "host".length;
- hostRule = new CSSOM.CSSHostRule();
- hostRule.__starts = i;
- buffer = "";
- break;
- } else if (token.indexOf("@import", i) === i) {
- state = "importRule-begin";
- i += "import".length;
- buffer += "@import";
- break;
- } else if (token.indexOf("@font-face", i) === i) {
- state = "fontFaceRule-begin";
- i += "font-face".length;
- fontFaceRule = new CSSOM.CSSFontFaceRule();
- fontFaceRule.__starts = i;
- buffer = "";
- break;
- } else {
- atKeyframesRegExp.lastIndex = i;
- var matchKeyframes = atKeyframesRegExp.exec(token);
- if (matchKeyframes && matchKeyframes.index === i) {
- state = "keyframesRule-begin";
- keyframesRule = new CSSOM.CSSKeyframesRule();
- keyframesRule.__starts = i;
- keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
- i += matchKeyframes[0].length - 1;
- buffer = "";
- break;
- } else if (state === "selector") {
- state = "atRule";
- }
- }
- buffer += character;
- break;
-
- case "{":
- if (state === "selector" || state === "atRule") {
- styleRule.selectorText = buffer.trim();
- styleRule.style.__starts = i;
- buffer = "";
- state = "before-name";
- } else if (state === "atBlock") {
- mediaRule.media.mediaText = buffer.trim();
-
- if (parentRule) {
- ancestorRules.push(parentRule);
- }
-
- currentScope = parentRule = mediaRule;
- mediaRule.parentStyleSheet = styleSheet;
- buffer = "";
- state = "before-selector";
- } else if (state === "conditionBlock") {
- supportsRule.conditionText = buffer.trim();
- if (parentRule) {
- ancestorRules.push(parentRule);
- }
-
- currentScope = parentRule = supportsRule;
- supportsRule.parentStyleSheet = styleSheet;
- buffer = "";
- state = "before-selector";
- } else if (state === "hostRule-begin") {
- if (parentRule) {
- ancestorRules.push(parentRule);
+ case "(":
+ if (state === 'value') {
+ // ie css expression mode
+ if (buffer.trim() === 'expression') {
+ var info = (new CSSOM.CSSValueExpression(token, i)).parse();
+
+ if (info.error) {
+ parseError(info.error);
+ } else {
+ buffer += info.expression;
+ i = info.idx;
+ }
+ } else {
+ state = 'value-parenthesis';
+ //always ensure this is reset to 1 on transition
+ //from value to value-parenthesis
+ valueParenthesisDepth = 1;
+ buffer += character;
+ }
+ } else if (state === 'value-parenthesis') {
+ valueParenthesisDepth++;
+ buffer += character;
+ } else {
+ buffer += character;
}
+ break;
- currentScope = parentRule = hostRule;
- hostRule.parentStyleSheet = styleSheet;
- buffer = "";
- state = "before-selector";
- } else if (state === "fontFaceRule-begin") {
- if (parentRule) {
- fontFaceRule.parentRule = parentRule;
- }
- fontFaceRule.parentStyleSheet = styleSheet;
- styleRule = fontFaceRule;
- buffer = "";
- state = "before-name";
- } else if (state === "keyframesRule-begin") {
- keyframesRule.name = buffer.trim();
- if (parentRule) {
- ancestorRules.push(parentRule);
- keyframesRule.parentRule = parentRule;
+ case ")":
+ if (state === 'value-parenthesis') {
+ valueParenthesisDepth--;
+ if (valueParenthesisDepth === 0) state = 'value';
}
- keyframesRule.parentStyleSheet = styleSheet;
- currentScope = parentRule = keyframesRule;
- buffer = "";
- state = "keyframeRule-begin";
- } else if (state === "keyframeRule-begin") {
- styleRule = new CSSOM.CSSKeyframeRule();
- styleRule.keyText = buffer.trim();
- styleRule.__starts = i;
- buffer = "";
- state = "before-name";
- } else if (state === "documentRule-begin") {
- // FIXME: what if this '{' is in the url text of the match function?
- documentRule.matcher.matcherText = buffer.trim();
- if (parentRule) {
- ancestorRules.push(parentRule);
- documentRule.parentRule = parentRule;
- }
- currentScope = parentRule = documentRule;
- documentRule.parentStyleSheet = styleSheet;
- buffer = "";
- state = "before-selector";
- }
- break;
-
- case ":":
- if (state === "name") {
- name = buffer.trim();
- buffer = "";
- state = "before-value";
- } else {
buffer += character;
- }
- break;
-
- case "(":
- if (state === 'value') {
- // ie css expression mode
- if (buffer.trim() === 'expression') {
- var info = (new CSSOM.CSSValueExpression(token, i)).parse();
+ break;
- if (info.error) {
- parseError(info.error);
- } else {
- buffer += info.expression;
- i = info.idx;
- }
+ case "!":
+ if (state === "value" && token.indexOf("!important", i) === i) {
+ priority = "important";
+ i += "important".length;
} else {
- state = 'value-parenthesis';
- //always ensure this is reset to 1 on transition
- //from value to value-parenthesis
- valueParenthesisDepth = 1;
buffer += character;
}
- } else if (state === 'value-parenthesis') {
- valueParenthesisDepth++;
- buffer += character;
- } else {
- buffer += character;
- }
- break;
+ break;
- case ")":
- if (state === 'value-parenthesis') {
- valueParenthesisDepth--;
- if (valueParenthesisDepth === 0) state = 'value';
- }
- buffer += character;
- break;
+ case ";":
+ switch (state) {
+ case "before-value":
+ case "before-name":
+ parseError("Unexpected ;");
+ buffer = "";
+ state = "before-name";
+ break;
+ case "value":
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
+ priority = "";
+ buffer = "";
+ state = "before-name";
+ break;
+ case "atRule":
+ buffer = "";
+ state = "before-selector";
+ break;
+ case "importRule":
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.some(function (rule) {
+ return ['CSSImportRule', 'CSSLayerStatementRule'].indexOf(rule.constructor.name) !== -1
+ });
+ if (isValid) {
+ importRule = new CSSOM.CSSImportRule();
+ if (opts && opts.globalObject && opts.globalObject.CSSStyleSheet) {
+ importRule.__styleSheet = new opts.globalObject.CSSStyleSheet();
+ }
+ importRule.styleSheet.__constructed = false;
+ importRule.__parentStyleSheet = importRule.styleSheet.__parentStyleSheet = styleSheet;
+ importRule.parse(buffer + character);
+ topScope.cssRules.push(importRule);
+ }
+ buffer = "";
+ state = "before-selector";
+ break;
+ case "namespaceRule":
+ var isValid = topScope.cssRules.length === 0 || topScope.cssRules.every(function (rule) {
+ return ['CSSImportRule', 'CSSLayerStatementRule', 'CSSNamespaceRule'].indexOf(rule.constructor.name) !== -1
+ });
+ if (isValid) {
+ try {
+ // Validate namespace syntax before creating the rule
+ var testNamespaceRule = new CSSOM.CSSNamespaceRule();
+ testNamespaceRule.parse(buffer + character);
+
+ namespaceRule = testNamespaceRule;
+ namespaceRule.__parentStyleSheet = styleSheet;
+ topScope.cssRules.push(namespaceRule);
+
+ // Track the namespace prefix for validation
+ if (namespaceRule.prefix) {
+ definedNamespacePrefixes[namespaceRule.prefix] = namespaceRule.namespaceURI;
+ }
+ } catch (e) {
+ parseError(e.message);
+ }
+ }
+ buffer = "";
+ state = "before-selector";
+ break;
+ case "layerBlock":
+ var nameListStr = buffer.trim().split(",").map(function (name) {
+ return name.trim();
+ });
+ var isInvalid = nameListStr.some(function (name) {
+ return name.trim().match(cssCustomIdentifierRegExp) === null;
+ });
+
+ // Check if there's a CSSStyleRule in the parent chain
+ var hasStyleRuleParent = false;
+ if (parentRule) {
+ var checkParent = parentRule;
+ while (checkParent) {
+ if (checkParent.constructor.name === "CSSStyleRule") {
+ hasStyleRuleParent = true;
+ break;
+ }
+ checkParent = checkParent.__parentRule;
+ }
+ }
- case "!":
- if (state === "value" && token.indexOf("!important", i) === i) {
- priority = "important";
- i += "important".length;
- } else {
- buffer += character;
- }
- break;
+ if (!isInvalid && !hasStyleRuleParent) {
+ layerStatementRule = new CSSOM.CSSLayerStatementRule();
+ layerStatementRule.__parentStyleSheet = styleSheet;
+ layerStatementRule.__starts = layerBlockRule.__starts;
+ layerStatementRule.__ends = i;
+ layerStatementRule.nameList = nameListStr;
+
+ // Add to parent rule if nested, otherwise to top scope
+ if (parentRule) {
+ layerStatementRule.__parentRule = parentRule;
+ parentRule.cssRules.push(layerStatementRule);
+ } else {
+ topScope.cssRules.push(layerStatementRule);
+ }
+ }
+ buffer = "";
+ state = "before-selector";
+ break;
+ default:
+ buffer += character;
+ break;
+ }
+ break;
- case ";":
- switch (state) {
- case "value":
- styleRule.style.setProperty(name, buffer.trim(), priority);
- priority = "";
- buffer = "";
- state = "before-name";
- break;
- case "atRule":
+ case "}":
+ if (state === "counterStyleBlock") {
+ // FIXME : Implement missing properties on CSSCounterStyleRule interface and update parse method
+ // For now it's just assigning entire rule text
+ if (counterStyleRule.name) {
+ // Only process if name was set (valid)
+ counterStyleRule.parse("@counter-style " + counterStyleRule.name + " { " + buffer + " }");
+ counterStyleRule.__ends = i + 1;
+ // Add to parent's cssRules
+ if (counterStyleRule.__parentRule) {
+ counterStyleRule.__parentRule.cssRules.push(counterStyleRule);
+ } else {
+ topScope.cssRules.push(counterStyleRule);
+ }
+ }
+ // Restore currentScope to parent after closing this rule
+ if (counterStyleRule.__parentRule) {
+ currentScope = counterStyleRule.__parentRule;
+ }
+ styleRule = null;
buffer = "";
state = "before-selector";
break;
- case "importRule":
- importRule = new CSSOM.CSSImportRule();
- importRule.parentStyleSheet = importRule.styleSheet.parentStyleSheet = styleSheet;
- importRule.cssText = buffer + character;
- styleSheet.cssRules.push(importRule);
+ }
+ if (state === "propertyBlock") {
+ // Only process if name was set (valid)
+ if (propertyRule.__name) {
+ var parseSuccess = propertyRule.parse("@property " + propertyRule.__name + " { " + buffer + " }");
+ // Only add the rule if parse was successful (syntax, inherits, and initial-value validation passed)
+ if (parseSuccess) {
+ propertyRule.__ends = i + 1;
+ // Add to parent's cssRules
+ if (propertyRule.__parentRule) {
+ propertyRule.__parentRule.cssRules.push(propertyRule);
+ } else {
+ topScope.cssRules.push(propertyRule);
+ }
+ }
+ }
+ // Restore currentScope to parent after closing this rule
+ if (propertyRule.__parentRule) {
+ currentScope = propertyRule.__parentRule;
+ }
+ styleRule = null;
buffer = "";
state = "before-selector";
break;
- default:
- buffer += character;
- break;
- }
- break;
-
- case "}":
- switch (state) {
- case "value":
- styleRule.style.setProperty(name, buffer.trim(), priority);
- priority = "";
+ }
+ switch (state) {
+ case "value":
+ styleRule.style.setProperty(name, buffer.trim(), priority, parseError);
+ priority = "";
/* falls through */
- case "before-name":
- case "name":
- styleRule.__ends = i + 1;
- if (parentRule) {
- styleRule.parentRule = parentRule;
- }
- styleRule.parentStyleSheet = styleSheet;
- currentScope.cssRules.push(styleRule);
- buffer = "";
- if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
- state = "keyframeRule-begin";
- } else {
- state = "before-selector";
- }
- break;
- case "keyframeRule-begin":
- case "before-selector":
- case "selector":
- // End of media/supports/document rule.
- if (!parentRule) {
- parseError("Unexpected }");
- }
+ case "before-value":
+ case "before-name":
+ case "name":
+ styleRule.__ends = i + 1;
- // Handle rules nested in @media or @supports
- hasAncestors = ancestorRules.length > 0;
+ if (parentRule === styleRule) {
+ parentRule = ancestorRules.pop();
+ }
- while (ancestorRules.length > 0) {
- parentRule = ancestorRules.pop();
+ if (parentRule) {
+ styleRule.__parentRule = parentRule;
+ }
+ styleRule.__parentStyleSheet = styleSheet;
- if (
- parentRule.constructor.name === "CSSMediaRule"
- || parentRule.constructor.name === "CSSSupportsRule"
- ) {
- prevScope = currentScope;
- currentScope = parentRule;
- currentScope.cssRules.push(prevScope);
+ if (currentScope === styleRule) {
+ currentScope = parentRule || topScope;
+ }
+
+ if (styleRule.constructor.name === "CSSStyleRule" && !isValidSelectorText(styleRule.selectorText)) {
+ if (styleRule === nestedSelectorRule) {
+ nestedSelectorRule = null;
+ }
+ parseError('Invalid CSSStyleRule (selectorText = "' + styleRule.selectorText + '")', styleRule.parentRule !== null);
+ } else {
+ if (styleRule.parentRule) {
+ styleRule.parentRule.cssRules.push(styleRule);
+ } else {
+ currentScope.cssRules.push(styleRule);
+ }
+ }
+ buffer = "";
+ if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
+ state = "keyframeRule-begin";
+ } else {
+ state = "before-selector";
+ }
+
+ if (styleRule.constructor.name === "CSSNestedDeclarations") {
+ if (currentScope !== topScope) {
+ // Only set nestedSelectorRule if currentScope is CSSStyleRule or CSSScopeRule
+ // Not for other grouping rules like @media/@supports
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
+ nestedSelectorRule = currentScope;
+ }
+ }
+ styleRule = null;
+ } else {
+ // Update nestedSelectorRule when closing a CSSStyleRule
+ if (styleRule === nestedSelectorRule) {
+ var selector = styleRule.selectorText && styleRule.selectorText.trim();
+ // Check if this is proper nesting (&.class, &:pseudo) vs prepended & (& :is, & .class with space)
+ // Prepended & has pattern "& X" where X starts with : or .
+ var isPrependedAmpersand = selector && selector.match(prependedAmpersandRegExp);
+
+ // Check if parent is a grouping rule that can contain nested selectors
+ var isGroupingRule = currentScope && currentScope instanceof CSSOM.CSSGroupingRule;
+
+ if (!isPrependedAmpersand && isGroupingRule) {
+ // Proper nesting - set nestedSelectorRule to parent for more nested selectors
+ // But only if it's a CSSStyleRule or CSSScopeRule, not other grouping rules like @media
+ if (currentScope.constructor.name === "CSSStyleRule" || currentScope.constructor.name === "CSSScopeRule") {
+ nestedSelectorRule = currentScope;
+ }
+ // If currentScope is another type of grouping rule (like @media), keep nestedSelectorRule unchanged
+ } else {
+ // Prepended & or not nested in grouping rule - reset to prevent CSSNestedDeclarations
+ nestedSelectorRule = null;
+ }
+ }
+ styleRule = null;
break;
}
+ case "keyframeRule-begin":
+ case "before-selector":
+ case "selector":
+ // End of media/supports/document rule.
+ if (!parentRule) {
+ parseError("Unexpected }");
+
+ var hasPreviousStyleRule = currentScope.cssRules.length && currentScope.cssRules[currentScope.cssRules.length - 1].constructor.name === "CSSStyleRule";
+ if (hasPreviousStyleRule) {
+ i = ignoreBalancedBlock(i, token.slice(i), 1);
+ }
- if (ancestorRules.length === 0) {
- hasAncestors = false;
+ break;
}
- }
-
- if (!hasAncestors) {
- currentScope.__ends = i + 1;
- styleSheet.cssRules.push(currentScope);
- currentScope = styleSheet;
- parentRule = null;
- }
- buffer = "";
- state = "before-selector";
- break;
- }
- break;
+ // Find the actual parent rule by popping from ancestor stack
+ while (ancestorRules.length > 0) {
+ parentRule = ancestorRules.pop();
+
+ // Skip if we popped the current scope itself (happens because we push both rule and parent)
+ if (parentRule === currentScope) {
+ continue;
+ }
+
+ // Only process valid grouping rules
+ if (!(parentRule instanceof CSSOM.CSSGroupingRule && (parentRule.constructor.name !== 'CSSStyleRule' || parentRule.__parentRule))) {
+ continue;
+ }
+
+ // Determine if we're closing a special nested selector context
+ var isClosingNestedSelectorContext = nestedSelectorRule &&
+ (currentScope === nestedSelectorRule || nestedSelectorRule.__parentRule === currentScope);
+
+ if (isClosingNestedSelectorContext) {
+ // Closing the nestedSelectorRule or its direct container
+ if (nestedSelectorRule.parentRule) {
+ // Add nestedSelectorRule to its parent and update scope
+ prevScope = nestedSelectorRule;
+ currentScope = nestedSelectorRule.parentRule;
+ // Use object lookup instead of O(n) indexOf
+ var scopeId = getRuleId(prevScope);
+ if (!addedToCurrentScope[scopeId]) {
+ currentScope.cssRules.push(prevScope);
+ addedToCurrentScope[scopeId] = true;
+ }
+ nestedSelectorRule = currentScope;
+ // Stop here to preserve context for sibling selectors
+ break;
+ } else {
+ // Top-level CSSStyleRule with nested grouping rule
+ prevScope = currentScope;
+ var actualParent = ancestorRules.length > 0 ? ancestorRules[ancestorRules.length - 1] : nestedSelectorRule;
+ if (actualParent !== prevScope) {
+ actualParent.cssRules.push(prevScope);
+ }
+ currentScope = actualParent;
+ parentRule = actualParent;
+ break;
+ }
+ } else {
+ // Regular case: add currentScope to parentRule
+ prevScope = currentScope;
+ if (parentRule !== prevScope) {
+ parentRule.cssRules.push(prevScope);
+ }
+ break;
+ }
+ }
- default:
- switch (state) {
- case "before-selector":
- state = "selector";
- styleRule = new CSSOM.CSSStyleRule();
- styleRule.__starts = i;
- break;
- case "before-name":
- state = "name";
- break;
- case "before-value":
- state = "value";
- break;
- case "importRule-begin":
- state = "importRule";
- break;
+ // If currentScope has a __parentRule and wasn't added yet, add it
+ if (ancestorRules.length === 0 && currentScope.__parentRule && currentScope.__parentRule.cssRules) {
+ // Use object lookup instead of O(n) findIndex
+ var parentId = getRuleId(currentScope);
+ if (!addedToParent[parentId]) {
+ currentScope.__parentRule.cssRules.push(currentScope);
+ addedToParent[parentId] = true;
+ }
+ }
+
+ // Only handle top-level rule closing if we processed all ancestors
+ if (ancestorRules.length === 0 && currentScope.parentRule == null) {
+ currentScope.__ends = i + 1;
+ // Use object lookup instead of O(n) findIndex
+ var topId = getRuleId(currentScope);
+ if (currentScope !== topScope && !addedToTopScope[topId]) {
+ topScope.cssRules.push(currentScope);
+ addedToTopScope[topId] = true;
+ }
+ currentScope = topScope;
+ if (nestedSelectorRule === parentRule) {
+ // Check if this selector is really starting inside another selector
+ var nestedSelectorTokenToCurrentSelectorToken = token.slice(nestedSelectorRule.__starts, i + 1);
+ var openingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(openBraceGlobalRegExp);
+ var closingBraceMatch = nestedSelectorTokenToCurrentSelectorToken.match(closeBraceGlobalRegExp);
+ var openingBraceLen = openingBraceMatch && openingBraceMatch.length;
+ var closingBraceLen = closingBraceMatch && closingBraceMatch.length;
+
+ if (openingBraceLen === closingBraceLen) {
+ // If the number of opening and closing braces are equal, we can assume that the new selector is starting outside the nestedSelectorRule
+ nestedSelectorRule.__ends = i + 1;
+ nestedSelectorRule = null;
+ parentRule = null;
+ }
+ } else {
+ parentRule = null;
+ }
+ } else {
+ currentScope = parentRule;
+ }
+
+ buffer = "";
+ state = "before-selector";
+ break;
+ }
+ break;
+
+ default:
+ switch (state) {
+ case "before-selector":
+ state = "selector";
+ if ((styleRule || scopeRule) && parentRule) {
+ // Assuming it's a declaration inside Nested Selector OR a Nested Declaration
+ // If Declaration inside Nested Selector let's keep the same styleRule
+ if (!isSelectorStartChar(character) && !isWhitespaceChar(character) && parentRule instanceof CSSOM.CSSGroupingRule) {
+ // parentRule.__parentRule = styleRule;
+ state = "before-name";
+ if (styleRule !== parentRule) {
+ styleRule = new CSSOM.CSSNestedDeclarations();
+ styleRule.__starts = i;
+ }
+ }
+
+ } else if (nestedSelectorRule && parentRule && parentRule instanceof CSSOM.CSSGroupingRule) {
+ if (isSelectorStartChar(character)) {
+ // If starting with a selector character, create CSSStyleRule instead of CSSNestedDeclarations
+ styleRule = new CSSOM.CSSStyleRule();
+ styleRule.__starts = i;
+ } else if (!isWhitespaceChar(character)) {
+ // Starting a declaration (not whitespace, not a selector)
+ state = "before-name";
+ // Check if we should create CSSNestedDeclarations
+ // This happens if: parent has cssRules OR nestedSelectorRule exists (indicating CSSStyleRule in hierarchy)
+ if (parentRule.cssRules.length || nestedSelectorRule) {
+ currentScope = parentRule;
+ // Only set nestedSelectorRule if parentRule is CSSStyleRule or CSSScopeRule
+ if (parentRule.constructor.name === "CSSStyleRule" || parentRule.constructor.name === "CSSScopeRule") {
+ nestedSelectorRule = parentRule;
+ }
+ styleRule = new CSSOM.CSSNestedDeclarations();
+ styleRule.__starts = i;
+ } else {
+ if (parentRule.constructor.name === "CSSStyleRule") {
+ styleRule = parentRule;
+ } else {
+ styleRule = new CSSOM.CSSStyleRule();
+ styleRule.__starts = i;
+ }
+ }
+ }
+ }
+ break;
+ case "before-name":
+ state = "name";
+ break;
+ case "before-value":
+ state = "value";
+ break;
+ case "importRule-begin":
+ state = "importRule";
+ break;
+ case "namespaceRule-begin":
+ state = "namespaceRule";
+ break;
+ }
+ buffer += character;
+ break;
+ }
+
+ // Auto-close all unclosed nested structures
+ // Check AFTER processing the character, at the ORIGINAL ending index
+ // Only add closing braces if CSS is incomplete (not at top scope)
+ if (i === initialEndingIndex && (currentScope !== topScope || ancestorRules.length > 0)) {
+ var needsClosing = ancestorRules.length;
+ if (currentScope !== topScope && ancestorRules.indexOf(currentScope) === -1) {
+ needsClosing += 1;
+ }
+ // Add closing braces for all unclosed structures
+ for (var closeIdx = 0; closeIdx < needsClosing; closeIdx++) {
+ token += "}";
+ endingIndex += 1;
}
- buffer += character;
- break;
}
}
+ if (buffer.trim() !== "") {
+ parseError("Unexpected end of input");
+ }
+
return styleSheet;
};
@@ -10624,18 +15677,30 @@ function requireParse () {
// The following modules cannot be included sooner due to the mutual dependency with parse.js
CSSOM.CSSStyleSheet = requireCSSStyleSheet().CSSStyleSheet;
CSSOM.CSSStyleRule = requireCSSStyleRule().CSSStyleRule;
+ CSSOM.CSSNestedDeclarations = requireCSSNestedDeclarations().CSSNestedDeclarations;
CSSOM.CSSImportRule = requireCSSImportRule().CSSImportRule;
+ CSSOM.CSSNamespaceRule = requireCSSNamespaceRule().CSSNamespaceRule;
CSSOM.CSSGroupingRule = requireCSSGroupingRule().CSSGroupingRule;
CSSOM.CSSMediaRule = requireCSSMediaRule().CSSMediaRule;
+ CSSOM.CSSCounterStyleRule = requireCSSCounterStyleRule().CSSCounterStyleRule;
+ CSSOM.CSSPropertyRule = requireCSSPropertyRule().CSSPropertyRule;
+ CSSOM.CSSContainerRule = requireCSSContainerRule().CSSContainerRule;
CSSOM.CSSConditionRule = requireCSSConditionRule().CSSConditionRule;
CSSOM.CSSSupportsRule = requireCSSSupportsRule().CSSSupportsRule;
CSSOM.CSSFontFaceRule = requireCSSFontFaceRule().CSSFontFaceRule;
CSSOM.CSSHostRule = requireCSSHostRule().CSSHostRule;
+ CSSOM.CSSStartingStyleRule = requireCSSStartingStyleRule().CSSStartingStyleRule;
CSSOM.CSSStyleDeclaration = requireCSSStyleDeclaration().CSSStyleDeclaration;
CSSOM.CSSKeyframeRule = requireCSSKeyframeRule().CSSKeyframeRule;
CSSOM.CSSKeyframesRule = requireCSSKeyframesRule().CSSKeyframesRule;
CSSOM.CSSValueExpression = requireCSSValueExpression().CSSValueExpression;
CSSOM.CSSDocumentRule = requireCSSDocumentRule().CSSDocumentRule;
+ CSSOM.CSSScopeRule = requireCSSScopeRule().CSSScopeRule;
+ CSSOM.CSSLayerBlockRule = requireCSSLayerBlockRule().CSSLayerBlockRule;
+ CSSOM.CSSLayerStatementRule = requireCSSLayerStatementRule().CSSLayerStatementRule;
+ CSSOM.CSSPageRule = requireCSSPageRule().CSSPageRule;
+ // Use cssstyle if available
+ requireCssstyleTryCatchBlock();
///CommonJS
return parse$1;
}
@@ -10647,8 +15712,11 @@ function requireCSSStyleDeclaration () {
hasRequiredCSSStyleDeclaration = 1;
//.CommonJS
var CSSOM = {};
+ var regexPatterns = requireRegexPatterns().regexPatterns;
///CommonJS
+ // Internal-use regular expression for indexed property names like '0', '1', '2', etc.
+ var indexedPropertyNameRegExp = /^(0|[1-9][0-9]*)$/;
/**
* @constructor
@@ -10685,21 +15753,49 @@ function requireCSSStyleDeclaration () {
* @param {string} [priority=null] "important" or null
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
*/
- setProperty: function(name, value, priority) {
- if (this[name]) {
+ setProperty: function(name, value, priority, parseErrorHandler)
+ {
+ // Because values are stored directly on this object, we must ignore CSS declaration
+ // names that would otherwise replace the object's length, parser fields, or methods.
+ // Numeric names would also overwrite entries like this[0], which hold a property name.
+ if (name === "length" ||
+ name === "parentRule" ||
+ name === "_importants" ||
+ name === "__starts" ||
+ name in CSSOM.CSSStyleDeclaration.prototype ||
+ indexedPropertyNameRegExp.test(name)
+ ) {
+ return;
+ }
+ // NOTE: Check viability to add a validation for css values or use a dependency like csstree-validator
+ var basicStylePropertyValueValidationRegExp = regexPatterns.basicStylePropertyValueValidationRegExp;
+ if (basicStylePropertyValueValidationRegExp.test(value)) {
+ parseErrorHandler && parseErrorHandler('Invalid CSSStyleDeclaration property (name = "' + name + '", value = "' + value + '")');
+ } else if (this[name]) {
// Property already exist. Overwrite it.
var index = Array.prototype.indexOf.call(this, name);
if (index < 0) {
this[this.length] = name;
this.length++;
}
+
+ // If the priority value of the incoming property is "important",
+ // or the value of the existing property is not "important",
+ // then remove the existing property and rewrite it.
+ if (priority || !this._importants[name]) {
+ this.removeProperty(name);
+ this[this.length] = name;
+ this.length++;
+ this[name] = value + '';
+ this._importants[name] = priority;
+ }
} else {
// New property.
this[this.length] = name;
this.length++;
+ this[name] = value + '';
+ this._importants[name] = priority;
}
- this[name] = value + "";
- this._importants[name] = priority;
},
/**
@@ -10807,15 +15903,26 @@ function requireClone () {
var CSSOM = {
CSSStyleSheet: requireCSSStyleSheet().CSSStyleSheet,
CSSRule: requireCSSRule().CSSRule,
+ CSSNestedDeclarations: requireCSSNestedDeclarations().CSSNestedDeclarations,
CSSStyleRule: requireCSSStyleRule().CSSStyleRule,
CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
CSSConditionRule: requireCSSConditionRule().CSSConditionRule,
CSSMediaRule: requireCSSMediaRule().CSSMediaRule,
+ CSSContainerRule: requireCSSContainerRule().CSSContainerRule,
CSSSupportsRule: requireCSSSupportsRule().CSSSupportsRule,
CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
CSSKeyframeRule: requireCSSKeyframeRule().CSSKeyframeRule,
- CSSKeyframesRule: requireCSSKeyframesRule().CSSKeyframesRule
+ CSSKeyframesRule: requireCSSKeyframesRule().CSSKeyframesRule,
+ CSSScopeRule: requireCSSScopeRule().CSSScopeRule,
+ CSSLayerBlockRule: requireCSSLayerBlockRule().CSSLayerBlockRule,
+ CSSLayerStatementRule: requireCSSLayerStatementRule().CSSLayerStatementRule
};
+ // Use cssstyle if available
+ try {
+ CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration;
+ } catch (e) {
+ // ignore
+ }
///CommonJS
@@ -10842,11 +15949,11 @@ function requireClone () {
if (style) {
var styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration();
for (var j = 0, styleLength = style.length; j < styleLength; j++) {
- var name = styleClone[j] = style[j];
- styleClone[name] = style[name];
- styleClone._importants[name] = style.getPropertyPriority(name);
+ var name = style[j];
+ // Consumers can change style[j] directly, so we must use setProperty here to ensure
+ // reserved names are ignored before they can overwrite fields on the clone object
+ styleClone.setProperty(name, style.getPropertyValue(name), style.getPropertyPriority(name));
}
- styleClone.length = style.length;
}
if (rule.hasOwnProperty('keyText')) {
@@ -10861,10 +15968,30 @@ function requireClone () {
ruleClone.mediaText = rule.mediaText;
}
+ if (rule.hasOwnProperty('supportsText')) {
+ ruleClone.supports = rule.supports;
+ }
+
if (rule.hasOwnProperty('conditionText')) {
ruleClone.conditionText = rule.conditionText;
}
+ if (rule.hasOwnProperty('layerName')) {
+ ruleClone.layerName = rule.layerName;
+ }
+
+ if (rule.hasOwnProperty('href')) {
+ ruleClone.href = rule.href;
+ }
+
+ if (rule.hasOwnProperty('name')) {
+ ruleClone.name = rule.name;
+ }
+
+ if (rule.hasOwnProperty('nameList')) {
+ ruleClone.nameList = rule.nameList;
+ }
+
if (rule.hasOwnProperty('cssRules')) {
ruleClone.cssRules = clone(rule).cssRules;
}
@@ -10886,17 +16013,32 @@ function requireLib () {
if (hasRequiredLib) return lib;
hasRequiredLib = 1;
+ lib.setup = requireCSSOM().setup;
+
+ requireErrorUtils();
+ requireRegexPatterns();
+
lib.CSSStyleDeclaration = requireCSSStyleDeclaration().CSSStyleDeclaration;
+
+ requireCssstyleTryCatchBlock();
+
lib.CSSRule = requireCSSRule().CSSRule;
+ lib.CSSRuleList = requireCSSRuleList().CSSRuleList;
+ lib.CSSNestedDeclarations = requireCSSNestedDeclarations().CSSNestedDeclarations;
lib.CSSGroupingRule = requireCSSGroupingRule().CSSGroupingRule;
+ lib.CSSCounterStyleRule = requireCSSCounterStyleRule().CSSCounterStyleRule;
+ lib.CSSPropertyRule = requireCSSPropertyRule().CSSPropertyRule;
lib.CSSConditionRule = requireCSSConditionRule().CSSConditionRule;
lib.CSSStyleRule = requireCSSStyleRule().CSSStyleRule;
lib.MediaList = requireMediaList().MediaList;
lib.CSSMediaRule = requireCSSMediaRule().CSSMediaRule;
+ lib.CSSContainerRule = requireCSSContainerRule().CSSContainerRule;
lib.CSSSupportsRule = requireCSSSupportsRule().CSSSupportsRule;
lib.CSSImportRule = requireCSSImportRule().CSSImportRule;
+ lib.CSSNamespaceRule = requireCSSNamespaceRule().CSSNamespaceRule;
lib.CSSFontFaceRule = requireCSSFontFaceRule().CSSFontFaceRule;
lib.CSSHostRule = requireCSSHostRule().CSSHostRule;
+ lib.CSSStartingStyleRule = requireCSSStartingStyleRule().CSSStartingStyleRule;
lib.StyleSheet = requireStyleSheet().StyleSheet;
lib.CSSStyleSheet = requireCSSStyleSheet().CSSStyleSheet;
lib.CSSKeyframesRule = requireCSSKeyframesRule().CSSKeyframesRule;
@@ -10905,6 +16047,10 @@ function requireLib () {
lib.CSSDocumentRule = requireCSSDocumentRule().CSSDocumentRule;
lib.CSSValue = requireCSSValue().CSSValue;
lib.CSSValueExpression = requireCSSValueExpression().CSSValueExpression;
+ lib.CSSScopeRule = requireCSSScopeRule().CSSScopeRule;
+ lib.CSSLayerBlockRule = requireCSSLayerBlockRule().CSSLayerBlockRule;
+ lib.CSSLayerStatementRule = requireCSSLayerStatementRule().CSSLayerStatementRule;
+ lib.CSSPageRule = requireCSSPageRule().CSSPageRule;
lib.parse = requireParse().parse;
lib.clone = requireClone().clone;
return lib;
@@ -12144,7 +17290,7 @@ const globalExports = assign(
}
);
-const window = new WeakMap;
+const window$1 = new WeakMap;
/**
* @implements globalThis.Document
@@ -12167,8 +17313,8 @@ let Document$1 = class Document extends NonElementParentNode {
* @type {globalThis.Document['defaultView']}
*/
get defaultView() {
- if (!window.has(this))
- window.set(this, new Proxy(globalThis, {
+ if (!window$1.has(this))
+ window$1.set(this, new Proxy(globalThis, {
set: (target, name, value) => {
switch (name) {
case 'addEventListener':
@@ -12203,7 +17349,7 @@ let Document$1 = class Document extends NonElementParentNode {
};
/* c8 ignore stop */
case 'window':
- return window.get(this);
+ return window$1.get(this);
case 'customElements':
if (!this[CUSTOM_ELEMENTS].registry)
this[CUSTOM_ELEMENTS] = new CustomElementRegistry(this);
@@ -12226,7 +17372,7 @@ let Document$1 = class Document extends NonElementParentNode {
globalThis[name];
}
}));
- return window.get(this);
+ return window$1.get(this);
}
get doctype() {