123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview CSS Object Model helper functions.
- * References:
- * - W3C: http://dev.w3.org/csswg/cssom/
- * - MSDN: http://msdn.microsoft.com/en-us/library/ms531209(VS.85).aspx.
- * TODO(user): Consider hacking page, media, etc.. to work.
- * This would be pretty challenging. IE returns the text for any rule
- * regardless of whether or not the media is correct or not. Firefox at
- * least supports CSSRule.type to figure out if it's a media type and then
- * we could do something interesting, but IE offers no way for us to tell.
- */
- goog.provide('goog.cssom');
- goog.provide('goog.cssom.CssRuleType');
- goog.require('goog.array');
- goog.require('goog.dom');
- goog.require('goog.dom.TagName');
- /**
- * Enumeration of {@code CSSRule} types.
- * @enum {number}
- */
- goog.cssom.CssRuleType = {
- STYLE: 1,
- IMPORT: 3,
- MEDIA: 4,
- FONT_FACE: 5,
- PAGE: 6,
- NAMESPACE: 7
- };
- /**
- * Recursively gets all CSS as text, optionally starting from a given
- * CSSStyleSheet.
- * @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.
- * @return {string} css text.
- */
- goog.cssom.getAllCssText = function(opt_styleSheet) {
- var styleSheet = opt_styleSheet || document.styleSheets;
- return /** @type {string} */ (goog.cssom.getAllCss_(styleSheet, true));
- };
- /**
- * Recursively gets all CSSStyleRules, optionally starting from a given
- * CSSStyleSheet.
- * Note that this excludes any CSSImportRules, CSSMediaRules, etc..
- * @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.
- * @return {Array<CSSStyleRule>} A list of CSSStyleRules.
- */
- goog.cssom.getAllCssStyleRules = function(opt_styleSheet) {
- var styleSheet = opt_styleSheet || document.styleSheets;
- return /** @type {!Array<CSSStyleRule>} */ (
- goog.cssom.getAllCss_(styleSheet, false));
- };
- /**
- * Returns the CSSRules from a styleSheet.
- * Worth noting here is that IE and FF differ in terms of what they will return.
- * Firefox will return styleSheet.cssRules, which includes ImportRules and
- * anything which implements the CSSRules interface. IE returns simply a list of
- * CSSRules.
- * @param {CSSStyleSheet} styleSheet The CSSStyleSheet.
- * @throws {Error} If we cannot access the rules on a stylesheet object - this
- * can happen if a stylesheet object's rules are accessed before the rules
- * have been downloaded and parsed and are "ready".
- * @return {CSSRuleList} An array of CSSRules or null.
- */
- goog.cssom.getCssRulesFromStyleSheet = function(styleSheet) {
- var cssRuleList = null;
- try {
- // Select cssRules unless it isn't present. For pre-IE9 IE, use the rules
- // collection instead.
- // It's important to be consistent in using only the W3C or IE apis on
- // IE9+ where both are present to ensure that there is no indexing
- // mismatches - the collections are subtly different in what the include or
- // exclude which can lead to one collection being longer than the other
- // depending on the page's construction.
- cssRuleList = styleSheet.cssRules /* W3C */ || styleSheet.rules /* IE */;
- } catch (e) {
- // This can happen if we try to access the CSSOM before it's "ready".
- if (e.code == 15) {
- // Firefox throws an NS_ERROR_DOM_INVALID_ACCESS_ERR error if a stylesheet
- // is read before it has been fully parsed. Let the caller know which
- // stylesheet failed.
- e.styleSheet = styleSheet;
- throw e;
- }
- }
- return cssRuleList;
- };
- /**
- * Gets all CSSStyleSheet objects starting from some CSSStyleSheet. Note that we
- * want to return the sheets in the order of the cascade, therefore if we
- * encounter an import, we will splice that CSSStyleSheet object in front of
- * the CSSStyleSheet that contains it in the returned array of CSSStyleSheets.
- * @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet A CSSStyleSheet.
- * @param {boolean=} opt_includeDisabled If true, includes disabled stylesheets,
- * defaults to false.
- * @return {!Array<CSSStyleSheet>} A list of CSSStyleSheet objects.
- */
- goog.cssom.getAllCssStyleSheets = function(
- opt_styleSheet, opt_includeDisabled) {
- var styleSheetsOutput = [];
- var styleSheet = opt_styleSheet || document.styleSheets;
- var includeDisabled =
- goog.isDef(opt_includeDisabled) ? opt_includeDisabled : false;
- // Imports need to go first.
- if (styleSheet.imports && styleSheet.imports.length) {
- for (var i = 0, n = styleSheet.imports.length; i < n; i++) {
- goog.array.extend(
- styleSheetsOutput,
- goog.cssom.getAllCssStyleSheets(
- /** @type {CSSStyleSheet} */ (styleSheet.imports[i])));
- }
- } else if (styleSheet.length) {
- // In case we get a StyleSheetList object.
- // http://dev.w3.org/csswg/cssom/#the-stylesheetlist
- for (var i = 0, n = styleSheet.length; i < n; i++) {
- goog.array.extend(
- styleSheetsOutput, goog.cssom.getAllCssStyleSheets(
- /** @type {!CSSStyleSheet} */ (styleSheet[i])));
- }
- } else {
- // We need to walk through rules in browsers which implement .cssRules
- // to see if there are styleSheets buried in there.
- // If we have a CSSStyleSheet within CssRules.
- var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(
- /** @type {!CSSStyleSheet} */ (styleSheet));
- if (cssRuleList && cssRuleList.length) {
- // Chrome does not evaluate cssRuleList[i] to undefined when i >=n;
- // so we use a (i < n) check instead of cssRuleList[i] in the loop below
- // and in other places where we iterate over a rules list.
- // See issue # 5917 in Chromium.
- for (var i = 0, n = cssRuleList.length, cssRule; i < n; i++) {
- cssRule = cssRuleList[i];
- // There are more stylesheets to get on this object..
- if (cssRule.styleSheet) {
- goog.array.extend(
- styleSheetsOutput,
- goog.cssom.getAllCssStyleSheets(cssRule.styleSheet));
- }
- }
- }
- }
- // This is a CSSStyleSheet. (IE uses .rules, W3c and Opera cssRules.)
- if ((styleSheet.type || styleSheet.rules || styleSheet.cssRules) &&
- (!styleSheet.disabled || includeDisabled)) {
- styleSheetsOutput.push(styleSheet);
- }
- return styleSheetsOutput;
- };
- /**
- * Gets the cssText from a CSSRule object cross-browserly.
- * @param {CSSRule} cssRule A CSSRule.
- * @return {string} cssText The text for the rule, including the selector.
- */
- goog.cssom.getCssTextFromCssRule = function(cssRule) {
- var cssText = '';
- if (cssRule.cssText) {
- // W3C.
- cssText = cssRule.cssText;
- } else if (cssRule.style && cssRule.style.cssText && cssRule.selectorText) {
- // IE: The spacing here is intended to make the result consistent with
- // FF and Webkit.
- // We also remove the special properties that we may have added in
- // getAllCssStyleRules since IE includes those in the cssText.
- var styleCssText =
- cssRule.style.cssText
- .replace(/\s*-closure-parent-stylesheet:\s*\[object\];?\s*/gi, '')
- .replace(/\s*-closure-rule-index:\s*[\d]+;?\s*/gi, '');
- var thisCssText = cssRule.selectorText + ' { ' + styleCssText + ' }';
- cssText = thisCssText;
- }
- return cssText;
- };
- /**
- * Get the index of the CSSRule in it's CSSStyleSheet.
- * @param {CSSRule} cssRule A CSSRule.
- * @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
- * object this cssRule belongs to.
- * @throws {Error} When we cannot get the parentStyleSheet.
- * @return {number} The index of the CSSRule, or -1.
- */
- goog.cssom.getCssRuleIndexInParentStyleSheet = function(
- cssRule, opt_parentStyleSheet) {
- // Look for our special style.ruleIndex property from getAllCss.
- if (cssRule.style && /** @type {!Object} */ (cssRule.style)['-closure-rule-index']) {
- return (/** @type {!Object} */ (cssRule.style))['-closure-rule-index'];
- }
- var parentStyleSheet =
- opt_parentStyleSheet || goog.cssom.getParentStyleSheet(cssRule);
- if (!parentStyleSheet) {
- // We could call getAllCssStyleRules() here to get our special indexes on
- // the style object, but that seems like it could be wasteful.
- throw Error('Cannot find a parentStyleSheet.');
- }
- var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(parentStyleSheet);
- if (cssRuleList && cssRuleList.length) {
- for (var i = 0, n = cssRuleList.length, thisCssRule; i < n; i++) {
- thisCssRule = cssRuleList[i];
- if (thisCssRule == cssRule) {
- return i;
- }
- }
- }
- return -1;
- };
- /**
- * We do some trickery in getAllCssStyleRules that hacks this in for IE.
- * If the cssRule object isn't coming from a result of that function call, this
- * method will return undefined in IE.
- * @param {CSSRule} cssRule The CSSRule.
- * @return {CSSStyleSheet} A styleSheet object.
- */
- goog.cssom.getParentStyleSheet = function(cssRule) {
- return cssRule.parentStyleSheet ||
- cssRule.style &&
- (/** @type {!Object} */ (cssRule.style))['-closure-parent-stylesheet'];
- };
- /**
- * Replace a cssRule with some cssText for a new rule.
- * If the cssRule object is not one of objects returned by
- * getAllCssStyleRules, then you'll need to provide both the styleSheet and
- * possibly the index, since we can't infer them from the standard cssRule
- * object in IE. We do some trickery in getAllCssStyleRules to hack this in.
- * @param {CSSRule} cssRule A CSSRule.
- * @param {string} cssText The text for the new CSSRule.
- * @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet
- * object this cssRule belongs to.
- * @param {number=} opt_index The index of the cssRule in its parentStylesheet.
- * @throws {Error} If we cannot find a parentStyleSheet.
- * @throws {Error} If we cannot find a css rule index.
- */
- goog.cssom.replaceCssRule = function(
- cssRule, cssText, opt_parentStyleSheet, opt_index) {
- var parentStyleSheet =
- opt_parentStyleSheet || goog.cssom.getParentStyleSheet(cssRule);
- if (parentStyleSheet) {
- var index = Number(opt_index) >= 0 ?
- Number(opt_index) :
- goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule, parentStyleSheet);
- if (index >= 0) {
- goog.cssom.removeCssRule(parentStyleSheet, index);
- goog.cssom.addCssRule(parentStyleSheet, cssText, index);
- } else {
- throw Error('Cannot proceed without the index of the cssRule.');
- }
- } else {
- throw Error('Cannot proceed without the parentStyleSheet.');
- }
- };
- /**
- * Cross browser function to add a CSSRule into a CSSStyleSheet, optionally
- * at a given index.
- * @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
- * @param {string} cssText The text for the new CSSRule.
- * @param {number=} opt_index The index of the cssRule in its parentStylesheet.
- * @throws {Error} If the css rule text appears to be ill-formatted.
- * TODO(bowdidge): Inserting at index 0 fails on Firefox 2 and 3 with an
- * exception warning "Node cannot be inserted at the specified point in
- * the hierarchy."
- */
- goog.cssom.addCssRule = function(cssStyleSheet, cssText, opt_index) {
- var index = opt_index;
- if (index == undefined || index < 0) {
- // If no index specified, insert at the end of the current list
- // of rules.
- var rules = goog.cssom.getCssRulesFromStyleSheet(cssStyleSheet);
- index = rules.length;
- }
- if (cssStyleSheet.insertRule) {
- // W3C (including IE9+).
- cssStyleSheet.insertRule(cssText, index);
- } else {
- // IE, pre 9: We have to parse the cssRule text to get the selector
- // separated from the style text.
- // aka Everything that isn't a colon, followed by a colon, then
- // the rest is the style part.
- var matches = /^([^\{]+)\{([^\{]+)\}/.exec(cssText);
- if (matches.length == 3) {
- var selector = matches[1];
- var style = matches[2];
- cssStyleSheet.addRule(selector, style, index);
- } else {
- throw Error('Your CSSRule appears to be ill-formatted.');
- }
- }
- };
- /**
- * Cross browser function to remove a CSSRule in a CSSStyleSheet at an index.
- * @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.
- * @param {number} index The CSSRule's index in the parentStyleSheet.
- */
- goog.cssom.removeCssRule = function(cssStyleSheet, index) {
- if (cssStyleSheet.deleteRule) {
- // W3C.
- cssStyleSheet.deleteRule(index);
- } else {
- // IE.
- cssStyleSheet.removeRule(index);
- }
- };
- /**
- * Appends a DOM node to HEAD containing the css text that's passed in.
- * @param {string} cssText CSS to add to the end of the document.
- * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper user for
- * document interactions.
- * @return {!Element} The newly created STYLE element.
- */
- goog.cssom.addCssText = function(cssText, opt_domHelper) {
- var domHelper = opt_domHelper || goog.dom.getDomHelper();
- var document = domHelper.getDocument();
- var cssNode = domHelper.createElement(goog.dom.TagName.STYLE);
- cssNode.type = 'text/css';
- var head = domHelper.getElementsByTagName(goog.dom.TagName.HEAD)[0];
- head.appendChild(cssNode);
- if (cssNode.styleSheet) {
- // IE.
- cssNode.styleSheet.cssText = cssText;
- } else {
- // W3C.
- var cssTextNode = document.createTextNode(cssText);
- cssNode.appendChild(cssTextNode);
- }
- return cssNode;
- };
- /**
- * Cross browser method to get the filename from the StyleSheet's href.
- * Explorer only returns the filename in the href, while other agents return
- * the full path.
- * @param {!StyleSheet} styleSheet Any valid StyleSheet object with an href.
- * @throws {Error} When there's no href property found.
- * @return {?string} filename The filename, or null if not an external
- * styleSheet.
- */
- goog.cssom.getFileNameFromStyleSheet = function(styleSheet) {
- var href = styleSheet.href;
- // Another IE/FF difference. IE returns an empty string, while FF and others
- // return null for CSSStyleSheets not from an external file.
- if (!href) {
- return null;
- }
- // We need the regexp to ensure we get the filename minus any query params.
- var matches = /([^\/\?]+)[^\/]*$/.exec(href);
- var filename = matches[1];
- return filename;
- };
- /**
- * Recursively gets all CSS text or rules.
- * @param {CSSStyleSheet|StyleSheetList} styleSheet The CSSStyleSheet.
- * @param {boolean} isTextOutput If true, output is cssText, otherwise cssRules.
- * @return {string|!Array<CSSRule>} cssText or cssRules.
- * @private
- */
- goog.cssom.getAllCss_ = function(styleSheet, isTextOutput) {
- var cssOut = [];
- var styleSheets = goog.cssom.getAllCssStyleSheets(styleSheet);
- for (var i = 0; styleSheet = styleSheets[i]; i++) {
- var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(styleSheet);
- if (cssRuleList && cssRuleList.length) {
- var ruleIndex = 0;
- for (var j = 0, n = cssRuleList.length, cssRule; j < n; j++) {
- cssRule = cssRuleList[j];
- // Gets cssText output, ignoring CSSImportRules.
- if (isTextOutput && !cssRule.href) {
- var res = goog.cssom.getCssTextFromCssRule(cssRule);
- cssOut.push(res);
- } else if (!cssRule.href) {
- // Gets cssRules output, ignoring CSSImportRules.
- if (cssRule.style) {
- // This is a fun little hack to get parentStyleSheet into the rule
- // object for IE since it failed to implement rule.parentStyleSheet.
- // We can later read this property when doing things like hunting
- // for indexes in order to delete a given CSSRule.
- // Unfortunately we have to use the style object to store these
- // pieces of info since the rule object is read-only.
- if (!cssRule.parentStyleSheet) {
- (/** @type {!Object} */ (cssRule.style))[
- '-closure-parent-stylesheet'] = styleSheet;
- }
- // This is a hack to help with possible removal of the rule later,
- // where we just append the rule's index in its parentStyleSheet
- // onto the style object as a property.
- // Unfortunately we have to use the style object to store these
- // pieces of info since the rule object is read-only.
- (/** @type {!Object} */ (cssRule.style))['-closure-rule-index'] =
- isTextOutput ? undefined : ruleIndex;
- }
- cssOut.push(cssRule);
- }
- if (!isTextOutput) {
- ruleIndex++;
- }
- }
- }
- }
- return isTextOutput ? cssOut.join(' ') : cssOut;
- };
|