| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- //.CommonJS
- var CSSOM = {
- MediaList: require("./MediaList").MediaList,
- StyleSheet: require("./StyleSheet").StyleSheet,
- CSSRuleList: require("./CSSRuleList").CSSRuleList,
- CSSStyleRule: require("./CSSStyleRule").CSSStyleRule,
- };
- var errorUtils = require("./errorUtils").errorUtils;
- ///CommonJS
- /**
- * @constructor
- * @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.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.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 last index of a specific rule constructor
- function findLastIndexOfConstructor(rules, constructorName) {
- for (var i = rules.length - 1; i >= 0; i--) {
- if (rules[i].constructor.name === constructorName) {
- return i;
- }
- }
- return -1;
- }
-
- // 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') {
- // @layer statement rules can be inserted anywhere before @import and @namespace
- // No additional restrictions beyond what's already handled
- } 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<rules.length; i++) {
- result += rules[i].cssText + "\n";
- }
- return result;
- };
- //.CommonJS
- exports.CSSStyleSheet = CSSOM.CSSStyleSheet;
- CSSOM.parse = require('./parse').parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleSheet.js
- ///CommonJS
|