UNPKG

@acemir/cssom

Version:

CSS Object Model implementation and CSS parser

1,574 lines (1,340 loc) 204 kB
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); }; // 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]; } } // Clone the .prototype object for constructor-like behavior if (fn.prototype) { newFn.prototype = Object.create(fn.prototype); } return newFn; }; } // Utility functions for CSSOM error handling /** * 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 */ 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 global !== 'undefined' && global[errorType]) || (typeof window !== 'undefined' && window[errorType]) || eval(errorType); } /** * 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 */ function createError(context, errorType, message, name) { var ErrorConstructor = getErrorConstructor(context, errorType); return new ErrorConstructor(message, name); } /** * 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); } /** * 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 = { createError: createError, getErrorConstructor: getErrorConstructor, throwError: throwError, throwMissingArguments: throwMissingArguments, throwParseError: throwParseError, throwIndexError: throwIndexError }; // 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 (>, +, ~) /** * 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 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 // 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) /** * Regular expression to detect invalid characters in the value portion of a CSS style declaration. * * 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). * * 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,...)`). * * 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");`). * * 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 */ var basicStylePropertyValueValidationRegExp = /:(?![^(]*\))(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/; // Attribute selector pattern: matches attribute-name operator value // Operators: =, ~=, |=, ^=, $=, *= // Rewritten to avoid ReDoS by using greedy match and trimming in JavaScript var attributeSelectorContentRegExp = /^([^\s=~|^$*]+)\s*(~=|\|=|\^=|\$=|\*=|=)\s*(.+)$/; // Selector validation patterns var pseudoElementRegExp = /::[a-zA-Z][\w-]*|:(before|after|first-line|first-letter)(?![a-zA-Z0-9_-])/; // Matches pseudo-elements var invalidCombinatorLtGtRegExp = /<>/; // 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) var regexPatterns = { // 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 }; /** * @constructor * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration */ CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration(){ this.length = 0; this.parentRule = null; // NON-STANDARD this._importants = {}; }; CSSOM.CSSStyleDeclaration.prototype = { constructor: CSSOM.CSSStyleDeclaration, /** * * @param {string} name * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set. */ getPropertyValue: function(name) { return this[name] || ""; }, /** * * @param {string} name * @param {string} value * @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, parseErrorHandler) { // 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; } }, /** * * @param {string} name * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. */ removeProperty: function(name) { if (!(name in this)) { return ""; } var index = Array.prototype.indexOf.call(this, name); if (index < 0) { return ""; } var prevValue = this[name]; this[name] = ""; // That's what WebKit and Opera do Array.prototype.splice.call(this, index, 1); // That's what Firefox does //this[index] = "" return prevValue; }, getPropertyCSSValue: function() { //FIXME }, /** * * @param {String} name */ getPropertyPriority: function(name) { return this._importants[name] || ""; }, /** * element.style.overflow = "auto" * element.style.getPropertyShorthand("overflow-x") * -> "overflow" */ getPropertyShorthand: function() { //FIXME }, isPropertyImplicit: function() { //FIXME }, // Doesn't work in IE < 9 get cssText(){ var properties = []; for (var i=0, length=this.length; i < length; ++i) { var name = this[i]; var value = this.getPropertyValue(name); var priority = this.getPropertyPriority(name); if (priority) { priority = " !" + priority; } properties[i] = name + ": " + value + priority + ";"; } return properties.join(" "); }, set cssText(text){ var i, name; for (i = this.length; i--;) { name = this[i]; this[name] = ""; } Array.prototype.splice.call(this, 0, this.length); this._importants = {}; var dummyRule = CSSOM.parse('#bogus{' + text + '}').cssRules[0].style; var length = dummyRule.length; for (i = 0; i < length; ++i) { name = dummyRule[i]; this.setProperty(dummyRule[i], dummyRule.getPropertyValue(name), dummyRule.getPropertyPriority(name)); } } }; try { CSSOM.CSSStyleDeclaration = require("cssstyle").CSSStyleDeclaration; } catch (e) { // ignore } /** * @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; CSSOM.CSSRule.CONTAINER_RULE = 17; CSSOM.CSSRule.LAYER_BLOCK_RULE = 18; CSSOM.CSSRule.STARTING_STYLE_RULE = 1002; Object.defineProperties(CSSOM.CSSRule.prototype, { constructor: { value: CSSOM.CSSRule }, cssRule: { value: "", configurable: true, enumerable: true }, cssText: { get: function() { // Default getter: subclasses should override this return ""; }, set: function(cssText) { return cssText; } }, parentRule: { get: function() { return this.__parentRule } }, parentStyleSheet: { get: function() { return this.__parentStyleSheet } }, UNKNOWN_RULE: { value: 0, enumerable: true }, // obsolet STYLE_RULE: { value: 1, enumerable: true }, CHARSET_RULE: { value: 2, enumerable: true }, // obsolet IMPORT_RULE: { value: 3, enumerable: true }, MEDIA_RULE: { value: 4, enumerable: true }, FONT_FACE_RULE: { value: 5, enumerable: true }, PAGE_RULE: { value: 6, enumerable: true }, KEYFRAMES_RULE: { value: 7, enumerable: true }, KEYFRAME_RULE: { value: 8, enumerable: true }, MARGIN_RULE: { value: 9, enumerable: true }, NAMESPACE_RULE: { value: 10, enumerable: true }, COUNTER_STYLE_RULE: { value: 11, enumerable: true }, SUPPORTS_RULE: { value: 12, enumerable: true }, DOCUMENT_RULE: { value: 13, enumerable: true }, FONT_FEATURE_VALUES_RULE: { value: 14, enumerable: true }, VIEWPORT_RULE: { value: 15, enumerable: true }, REGION_STYLE_RULE: { value: 16, enumerable: true }, CONTAINER_RULE: { value: 17, enumerable: true }, LAYER_BLOCK_RULE: { value: 18, enumerable: true }, STARTING_STYLE_RULE: { value: 1002, enumerable: true }, }); /** * @constructor * @see https://drafts.csswg.org/cssom/#the-cssrulelist-interface */ CSSOM.CSSRuleList = function CSSRuleList(){ var arr = new Array(); Object.setPrototypeOf(arr, CSSOM.CSSRuleList.prototype); return arr; }; CSSOM.CSSRuleList.prototype = Object.create(Array.prototype); CSSOM.CSSRuleList.prototype.constructor = CSSOM.CSSRuleList; CSSOM.CSSRuleList.prototype.item = function(index) { return this[index] || null; }; /** * @constructor * @see https://drafts.csswg.org/css-nesting-1/ */ CSSOM.CSSNestedDeclarations = function CSSNestedDeclarations() { CSSOM.CSSRule.call(this); this.__style = new CSSOM.CSSStyleDeclaration(); this.__style.parentRule = this; }; CSSOM.CSSNestedDeclarations.prototype = Object.create(CSSOM.CSSRule.prototype); CSSOM.CSSNestedDeclarations.prototype.constructor = CSSOM.CSSNestedDeclarations; Object.setPrototypeOf(CSSOM.CSSNestedDeclarations, CSSOM.CSSRule); Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "type", { value: 0, writable: false }); Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "style", { get: function() { return this.__style; }, set: function(value) { if (typeof value === "string") { this.__style.cssText = value; } else { this.__style = value; } } }); Object.defineProperty(CSSOM.CSSNestedDeclarations.prototype, "cssText", { get: function () { return this.style.cssText; } }); /** * @constructor * @see https://drafts.csswg.org/cssom/#the-cssgroupingrule-interface */ CSSOM.CSSGroupingRule = function CSSGroupingRule() { CSSOM.CSSRule.call(this); this.__cssRules = new CSSOM.CSSRuleList(); }; CSSOM.CSSGroupingRule.prototype = Object.create(CSSOM.CSSRule.prototype); CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule; Object.setPrototypeOf(CSSOM.CSSGroupingRule, CSSOM.CSSRule); Object.defineProperty(CSSOM.CSSGroupingRule.prototype, "cssRules", { get: function() { return this.__cssRules; } }); /** * Used to insert a new CSS rule to a list of CSS rules. * * @example * cssGroupingRule.cssText * -> "body{margin:0;}" * cssGroupingRule.insertRule("img{border:none;}", 1) * -> 1 * cssGroupingRule.cssText * -> "body{margin:0;}img{border:none;}" * * @param {string} rule * @param {number} [index] * @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-insertrule * @return {number} The index within the grouping rule's collection of the newly inserted rule. */ CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(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 = 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'); } cssRule.__parentRule = this; this.cssRules.splice(index, 0, cssRule); return index; }; /** * Used to delete a rule from the grouping rule. * * cssGroupingRule.cssText * -> "img{border:none;}body{margin:0;}" * cssGroupingRule.deleteRule(0) * cssGroupingRule.cssText * -> "body{margin:0;}" * * @param {number} index within the grouping rule's rule list of the rule to remove. * @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule */ CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(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); } this.cssRules[index].__parentRule = null; this.cssRules[index].__parentStyleSheet = null; this.cssRules.splice(index, 1); }; /** * @constructor * @see https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface */ CSSOM.CSSCounterStyleRule = function CSSCounterStyleRule() { CSSOM.CSSRule.call(this); this.name = ""; this.__props = ""; }; CSSOM.CSSCounterStyleRule.prototype = Object.create(CSSOM.CSSRule.prototype); CSSOM.CSSCounterStyleRule.prototype.constructor = CSSOM.CSSCounterStyleRule; Object.setPrototypeOf(CSSOM.CSSCounterStyleRule, CSSOM.CSSRule); Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "type", { value: 11, writable: false }); Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "cssText", { get: function() { // FIXME : Implement real cssText generation based on properties return "@counter-style " + this.name + " { " + this.__props + " }"; } }); /** * NON-STANDARD * Rule text parser. * @param {string} cssText */ Object.defineProperty(CSSOM.CSSCounterStyleRule.prototype, "parse", { value: function(cssText) { // Extract the name from "@counter-style <name> { ... }" 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 : ' '; }); } } }); /** * @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; }; CSSOM.CSSPropertyRule.prototype = Object.create(CSSOM.CSSRule.prototype); CSSOM.CSSPropertyRule.prototype.constructor = CSSOM.CSSPropertyRule; Object.setPrototypeOf(CSSOM.CSSPropertyRule, CSSOM.CSSRule); Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "type", { value: 0, writable: false }); Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "cssText", { get: function() { var text = "@property " + this.name + " {"; if (this.syntax !== "") { text += " syntax: \"" + this.syntax.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\";"; } text += " inherits: " + (this.inherits ? "true" : "false") + ";"; if (this.initialValue !== null) { text += " initial-value: " + this.initialValue + ";"; } text += " }"; return text; } }); Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "name", { get: function() { return this.__name; } }); 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; } }); /** * NON-STANDARD * Rule text parser. * @param {string} cssText * @returns {boolean} True if the rule is valid and was parsed successfully */ Object.defineProperty(CSSOM.CSSPropertyRule.prototype, "parse", { value: function(cssText) { // Extract the name from "@property <name> { ... }" var match = cssText.match(/@property\s+(--[^\s{]+)\s*\{([^]*)\}/); if (!match) { return false; } 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 { // If syntax is not "*", initial-value is required if (this.__syntax !== "*") { return false; } } return true; // Successfully parsed } }); /** * @constructor * @see https://www.w3.org/TR/css-conditional-3/#the-cssconditionrule-interface */ CSSOM.CSSConditionRule = function CSSConditionRule() { CSSOM.CSSGroupingRule.call(this); this.__conditionText = ''; }; CSSOM.CSSConditionRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype); CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule; Object.setPrototypeOf(CSSOM.CSSConditionRule, CSSOM.CSSGroupingRule); Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", { get: function () { return this.__conditionText; } }); /** * @constructor * @see http://dev.w3.org/csswg/cssom/#cssstylerule * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule */ CSSOM.CSSStyleRule = function CSSStyleRule() { CSSOM.CSSGroupingRule.call(this); this.__selectorText = ""; this.__style = new CSSOM.CSSStyleDeclaration(); this.__style.parentRule = this; }; CSSOM.CSSStyleRule.prototype = Object.create(CSSOM.CSSGroupingRule.prototype); CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule; 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; } }); /** * @constructor * @see http://dev.w3.org/csswg/cssom/#the-medialist-interface */ CSSOM.MediaList = function MediaList(){ this.length = 0; }; CSSOM.MediaList.prototype = { constructor: CSSOM.MediaList, /** * @return {string} */ get mediaText() { return Array.prototype.join.call(this, ", "); }, /** * @param {string} value */ set mediaText(value) { if (typeof value === "string") { var values = value.split(",").filter(function(text){ return !!text; }); var length = this.length = values.length; for (var i=0; i<length; i++) { this[i] = values[i].trim(); } } else if (value === null) { var length = this.length; for (var i = 0; i < length; i++) { delete this[i]; } this.length = 0; } }, /** * @param {string} medium */ appendMedium: function(medium) { if (Array.prototype.indexOf.call(this, medium) === -1) { this[this.length] = medium; this.length++; } }, /** * @param {string} medium */ deleteMedium: function(medium) { var index = Array.prototype.indexOf.call(this, medium); if (index !== -1) { Array.prototype.splice.call(this, index, 1); } }, item: function(index) { return this[index] || null; }, toString: function() { return this.mediaText; } }; /** * @constructor * @see http://dev.w3.org/csswg/cssom/#cssmediarule * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule */ CSSOM.CSSMediaRule = function CSSMediaRule() { CSSOM.CSSConditionRule.call(this); this.__media = new CSSOM.MediaList(); }; CSSOM.CSSMediaRule.prototype = Object.create(CSSOM.CSSConditionRule.prototype); CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule; Object.setPrototypeOf(CSSOM.CSSMediaRule, CSSOM.CSSConditionRule); Object.defineProperty(CSSOM.CSSMediaRule.prototype, "type", { value: 4, writable: false }); // https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp Object.defineProperties(CSSOM.CSSMediaRule.prototype, { "media": { get: function() { return this.__media; }, set: function(value) { if (typeof value === "string") { this.__media.mediaText = value; } else { this.__media = value; } }, configurable: true, enumerable: true }, "conditionText": { get: function() { return this.media.mediaText; } }, "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 "@media " + this.media.mediaText + values; } } }); /** * @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; Object.setPrototypeOf(CSSOM.CSSContainerRule, CSSOM.CSSConditionRule); Object.defineProperty(CSSOM.CSSContainerRule.prototype, "type", { value: 17, writable: false }); 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; } }, }); /** * @constructor * @see https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface */ CSSOM.CSSSupportsRule = function CSSSupportsRule() { CSSOM.CSSConditionRule.call(this); }; 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; } }); /** * @constructor * @see http://dev.w3.org/csswg/cssom/#cssimportrule * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule */ CSSOM.CSSImportRule = function CSSImportRule() { CSSOM.CSSRule.call(this); this.__href = ""; this.__media = new CSSOM.MediaList(); this.__layerName = null; this.__supportsText = null; this.__styleSheet = new CSSOM.CSSStyleSheet(); }; CSSOM.CSSImportRule.prototype = Object.create(CSSOM.CSSRule.prototype); CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule; Object.setPrototypeOf(CSSOM.CSSImportRule, CSSOM.CSSRule); Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", { value: 3, writable: false }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", { get: function() { var mediaText = this.media.mediaText; return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";"; } }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", { get: function() { return this.__href; } }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", { get: function() { return this.__media; }, set: function(value) { if (typeof value === "string") { this.__media.mediaText = value; } else { this.__media = value; } } }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", { get: function() { return this.__layerName; } }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", { get: function() { return this.__supportsText; } }); Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", { get: function() { return this.__styleSheet; } }); /** * NON-STANDARD * Rule text parser. * @param {string} cssText */ Object.defineProperty(CSSOM.CSSImportRule.prototype, "parse", { value: function(cssText) { var i = 0; /** * @import url(partial.css) screen, handheld; * || | * after-import media * | * url */ var state = ''; var buffer = ''; var index; var layerRegExp = regexPatterns.layerRegExp; var layerRuleNameRegExp = regexPatterns.layerRuleNameRegExp; var doubleOrMoreSpacesRegExp = regexPatterns.doubleOrMoreSpacesRegExp; /** * Extracts the content inside supports() handling nested parentheses. * @param {string} text - The text to parse * @returns {object|null} - {content: string, endIndex: number} or null if not found */ function extractSupportsContent(text) { var supportsIndex = text.indexOf('supports('); if (supportsIndex !== 0) { return null; } var depth = 0; var start = supportsIndex + 'supports('.length; var i = start; for (; i < text.length; i++) { if (text[i] === '(') { depth++; } else if (text[i] === ')') { if (depth === 0) { // Found the closing parenthesis for supports() return { content: text.slice(start, i), endIndex: i }; } depth--; } } return null; // Unbalanced parentheses } for (var character; (character = cssText.charAt(i)); i++) { switch (character) { case ' ': case '\t': case '\r': case '\n': case '\f': if (state === 'after-import') { state = 'url'; } else { buffer += character; } break; case '@': if (!state && cssText.indexOf('@import', i) === i) { state = 'after-import'; i += 'import'.length; buffer = ''; } break; case 'u': if (state === 'media') { buffer += character; } if (state === 'url' && cssText.indexOf('url(', i) === i) { index = cssText.indexOf(')', i + 1); if (index === -1) { throw i + ': ")" not found'; } i += 'url('.length; var url = cssText.slice(i, index); if (url[0] === url[url.length - 1]) { if (url[0] === '"' || url[0] === "'") { url = url.slice(1, -1); } } this.__href = url; i = index; state = 'media'; } break; case '"': if (state === 'after-import' || state === 'url') { index = cssText.indexOf('"', i + 1); if (!index) { throw i + ": '\"' not found"; } this.__href = cssText.slice(i + 1, index); i = index; state = 'media'; } break; case "'": if (state === 'after-import' || state === 'url') { index = cssText.indexOf("'", i + 1); if (!index) { throw i + ': "\'" not found'; } this.__href = cssText.slice(i + 1, index); i = index; state = 'media'; } break; case ';': if (state === 'media') { if (buffer) { var bufferTrimmed = buffer.trim(); if (bufferTrimmed.indexOf('layer') === 0) { var layerMatch = bufferTrimmed.match(layerRegExp); if (layerMatch) { var layerName = layerMatch[1].trim(); if (layerName.match(layerRuleNameRegExp) !== null) { this.__layerName = layerMatch[1].trim(); bufferTrimmed = bufferTrimmed.replace(layerRegExp, '') .replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space .trim(); } else { // REVIEW: In the browser, an empty layer() is not processed as a unamed layer // and treats the rest of the string as mediaText, ignoring the parse of supports() if (bufferTrimmed) { this.media.mediaText = bufferTrimmed; return; } } } else { this.__layerName = ""; bufferTrimmed = bufferTrimmed.substring('layer'.length).trim() } } var supportsResult = extractSupportsContent(bufferTrimmed); if (supportsResult) { // REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule this.__supportsText = supportsResult.content.trim(); // Remove the entire supports(...) from the buffer bufferTrimmed = bufferTrimmed.slice(0, 0) + bufferTrimmed.slice(supportsResult.endIndex + 1); bufferTrimmed = bufferTrimmed.replace(doubleOrMoreSpacesRegExp, ' ').trim(); } // REVIEW: In the browser, any invalid media is replaced with 'not all' if (bufferTrimmed) { this.media.mediaText = bufferTrimmed; } } } break; default: if (state === 'media') { buffer += character;