Path: blob/trunk/third_party/closure/goog/cssom/cssom.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview CSS Object Model helper functions.16* References:17* - W3C: http://dev.w3.org/csswg/cssom/18* - MSDN: http://msdn.microsoft.com/en-us/library/ms531209(VS.85).aspx.19* TODO(user): Consider hacking page, media, etc.. to work.20* This would be pretty challenging. IE returns the text for any rule21* regardless of whether or not the media is correct or not. Firefox at22* least supports CSSRule.type to figure out if it's a media type and then23* we could do something interesting, but IE offers no way for us to tell.24*/2526goog.provide('goog.cssom');27goog.provide('goog.cssom.CssRuleType');2829goog.require('goog.array');30goog.require('goog.dom');31goog.require('goog.dom.TagName');323334/**35* Enumeration of {@code CSSRule} types.36* @enum {number}37*/38goog.cssom.CssRuleType = {39STYLE: 1,40IMPORT: 3,41MEDIA: 4,42FONT_FACE: 5,43PAGE: 6,44NAMESPACE: 745};464748/**49* Recursively gets all CSS as text, optionally starting from a given50* CSSStyleSheet.51* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.52* @return {string} css text.53*/54goog.cssom.getAllCssText = function(opt_styleSheet) {55var styleSheet = opt_styleSheet || document.styleSheets;56return /** @type {string} */ (goog.cssom.getAllCss_(styleSheet, true));57};585960/**61* Recursively gets all CSSStyleRules, optionally starting from a given62* CSSStyleSheet.63* Note that this excludes any CSSImportRules, CSSMediaRules, etc..64* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet The CSSStyleSheet.65* @return {Array<CSSStyleRule>} A list of CSSStyleRules.66*/67goog.cssom.getAllCssStyleRules = function(opt_styleSheet) {68var styleSheet = opt_styleSheet || document.styleSheets;69return /** @type {!Array<CSSStyleRule>} */ (70goog.cssom.getAllCss_(styleSheet, false));71};727374/**75* Returns the CSSRules from a styleSheet.76* Worth noting here is that IE and FF differ in terms of what they will return.77* Firefox will return styleSheet.cssRules, which includes ImportRules and78* anything which implements the CSSRules interface. IE returns simply a list of79* CSSRules.80* @param {CSSStyleSheet} styleSheet The CSSStyleSheet.81* @throws {Error} If we cannot access the rules on a stylesheet object - this82* can happen if a stylesheet object's rules are accessed before the rules83* have been downloaded and parsed and are "ready".84* @return {CSSRuleList} An array of CSSRules or null.85*/86goog.cssom.getCssRulesFromStyleSheet = function(styleSheet) {87var cssRuleList = null;88try {89// Select cssRules unless it isn't present. For pre-IE9 IE, use the rules90// collection instead.91// It's important to be consistent in using only the W3C or IE apis on92// IE9+ where both are present to ensure that there is no indexing93// mismatches - the collections are subtly different in what the include or94// exclude which can lead to one collection being longer than the other95// depending on the page's construction.96cssRuleList = styleSheet.cssRules /* W3C */ || styleSheet.rules /* IE */;97} catch (e) {98// This can happen if we try to access the CSSOM before it's "ready".99if (e.code == 15) {100// Firefox throws an NS_ERROR_DOM_INVALID_ACCESS_ERR error if a stylesheet101// is read before it has been fully parsed. Let the caller know which102// stylesheet failed.103e.styleSheet = styleSheet;104throw e;105}106}107return cssRuleList;108};109110111/**112* Gets all CSSStyleSheet objects starting from some CSSStyleSheet. Note that we113* want to return the sheets in the order of the cascade, therefore if we114* encounter an import, we will splice that CSSStyleSheet object in front of115* the CSSStyleSheet that contains it in the returned array of CSSStyleSheets.116* @param {(CSSStyleSheet|StyleSheetList)=} opt_styleSheet A CSSStyleSheet.117* @param {boolean=} opt_includeDisabled If true, includes disabled stylesheets,118* defaults to false.119* @return {!Array<CSSStyleSheet>} A list of CSSStyleSheet objects.120*/121goog.cssom.getAllCssStyleSheets = function(122opt_styleSheet, opt_includeDisabled) {123var styleSheetsOutput = [];124var styleSheet = opt_styleSheet || document.styleSheets;125var includeDisabled =126goog.isDef(opt_includeDisabled) ? opt_includeDisabled : false;127128// Imports need to go first.129if (styleSheet.imports && styleSheet.imports.length) {130for (var i = 0, n = styleSheet.imports.length; i < n; i++) {131goog.array.extend(132styleSheetsOutput,133goog.cssom.getAllCssStyleSheets(134/** @type {CSSStyleSheet} */ (styleSheet.imports[i])));135}136137} else if (styleSheet.length) {138// In case we get a StyleSheetList object.139// http://dev.w3.org/csswg/cssom/#the-stylesheetlist140for (var i = 0, n = styleSheet.length; i < n; i++) {141goog.array.extend(142styleSheetsOutput, goog.cssom.getAllCssStyleSheets(143/** @type {!CSSStyleSheet} */ (styleSheet[i])));144}145} else {146// We need to walk through rules in browsers which implement .cssRules147// to see if there are styleSheets buried in there.148// If we have a CSSStyleSheet within CssRules.149var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(150/** @type {!CSSStyleSheet} */ (styleSheet));151if (cssRuleList && cssRuleList.length) {152// Chrome does not evaluate cssRuleList[i] to undefined when i >=n;153// so we use a (i < n) check instead of cssRuleList[i] in the loop below154// and in other places where we iterate over a rules list.155// See issue # 5917 in Chromium.156for (var i = 0, n = cssRuleList.length, cssRule; i < n; i++) {157cssRule = cssRuleList[i];158// There are more stylesheets to get on this object..159if (cssRule.styleSheet) {160goog.array.extend(161styleSheetsOutput,162goog.cssom.getAllCssStyleSheets(cssRule.styleSheet));163}164}165}166}167168// This is a CSSStyleSheet. (IE uses .rules, W3c and Opera cssRules.)169if ((styleSheet.type || styleSheet.rules || styleSheet.cssRules) &&170(!styleSheet.disabled || includeDisabled)) {171styleSheetsOutput.push(styleSheet);172}173174return styleSheetsOutput;175};176177178/**179* Gets the cssText from a CSSRule object cross-browserly.180* @param {CSSRule} cssRule A CSSRule.181* @return {string} cssText The text for the rule, including the selector.182*/183goog.cssom.getCssTextFromCssRule = function(cssRule) {184var cssText = '';185186if (cssRule.cssText) {187// W3C.188cssText = cssRule.cssText;189} else if (cssRule.style && cssRule.style.cssText && cssRule.selectorText) {190// IE: The spacing here is intended to make the result consistent with191// FF and Webkit.192// We also remove the special properties that we may have added in193// getAllCssStyleRules since IE includes those in the cssText.194var styleCssText =195cssRule.style.cssText196.replace(/\s*-closure-parent-stylesheet:\s*\[object\];?\s*/gi, '')197.replace(/\s*-closure-rule-index:\s*[\d]+;?\s*/gi, '');198var thisCssText = cssRule.selectorText + ' { ' + styleCssText + ' }';199cssText = thisCssText;200}201202return cssText;203};204205206/**207* Get the index of the CSSRule in it's CSSStyleSheet.208* @param {CSSRule} cssRule A CSSRule.209* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet210* object this cssRule belongs to.211* @throws {Error} When we cannot get the parentStyleSheet.212* @return {number} The index of the CSSRule, or -1.213*/214goog.cssom.getCssRuleIndexInParentStyleSheet = function(215cssRule, opt_parentStyleSheet) {216// Look for our special style.ruleIndex property from getAllCss.217if (cssRule.style && /** @type {!Object} */ (cssRule.style)['-closure-rule-index']) {218return (/** @type {!Object} */ (cssRule.style))['-closure-rule-index'];219}220221var parentStyleSheet =222opt_parentStyleSheet || goog.cssom.getParentStyleSheet(cssRule);223224if (!parentStyleSheet) {225// We could call getAllCssStyleRules() here to get our special indexes on226// the style object, but that seems like it could be wasteful.227throw Error('Cannot find a parentStyleSheet.');228}229230var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(parentStyleSheet);231if (cssRuleList && cssRuleList.length) {232for (var i = 0, n = cssRuleList.length, thisCssRule; i < n; i++) {233thisCssRule = cssRuleList[i];234if (thisCssRule == cssRule) {235return i;236}237}238}239return -1;240};241242243/**244* We do some trickery in getAllCssStyleRules that hacks this in for IE.245* If the cssRule object isn't coming from a result of that function call, this246* method will return undefined in IE.247* @param {CSSRule} cssRule The CSSRule.248* @return {CSSStyleSheet} A styleSheet object.249*/250goog.cssom.getParentStyleSheet = function(cssRule) {251return cssRule.parentStyleSheet ||252cssRule.style &&253(/** @type {!Object} */ (cssRule.style))['-closure-parent-stylesheet'];254};255256257/**258* Replace a cssRule with some cssText for a new rule.259* If the cssRule object is not one of objects returned by260* getAllCssStyleRules, then you'll need to provide both the styleSheet and261* possibly the index, since we can't infer them from the standard cssRule262* object in IE. We do some trickery in getAllCssStyleRules to hack this in.263* @param {CSSRule} cssRule A CSSRule.264* @param {string} cssText The text for the new CSSRule.265* @param {CSSStyleSheet=} opt_parentStyleSheet A reference to the stylesheet266* object this cssRule belongs to.267* @param {number=} opt_index The index of the cssRule in its parentStylesheet.268* @throws {Error} If we cannot find a parentStyleSheet.269* @throws {Error} If we cannot find a css rule index.270*/271goog.cssom.replaceCssRule = function(272cssRule, cssText, opt_parentStyleSheet, opt_index) {273var parentStyleSheet =274opt_parentStyleSheet || goog.cssom.getParentStyleSheet(cssRule);275if (parentStyleSheet) {276var index = Number(opt_index) >= 0 ?277Number(opt_index) :278goog.cssom.getCssRuleIndexInParentStyleSheet(cssRule, parentStyleSheet);279if (index >= 0) {280goog.cssom.removeCssRule(parentStyleSheet, index);281goog.cssom.addCssRule(parentStyleSheet, cssText, index);282} else {283throw Error('Cannot proceed without the index of the cssRule.');284}285} else {286throw Error('Cannot proceed without the parentStyleSheet.');287}288};289290291/**292* Cross browser function to add a CSSRule into a CSSStyleSheet, optionally293* at a given index.294* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.295* @param {string} cssText The text for the new CSSRule.296* @param {number=} opt_index The index of the cssRule in its parentStylesheet.297* @throws {Error} If the css rule text appears to be ill-formatted.298* TODO(bowdidge): Inserting at index 0 fails on Firefox 2 and 3 with an299* exception warning "Node cannot be inserted at the specified point in300* the hierarchy."301*/302goog.cssom.addCssRule = function(cssStyleSheet, cssText, opt_index) {303var index = opt_index;304if (index == undefined || index < 0) {305// If no index specified, insert at the end of the current list306// of rules.307var rules = goog.cssom.getCssRulesFromStyleSheet(cssStyleSheet);308index = rules.length;309}310if (cssStyleSheet.insertRule) {311// W3C (including IE9+).312cssStyleSheet.insertRule(cssText, index);313314} else {315// IE, pre 9: We have to parse the cssRule text to get the selector316// separated from the style text.317// aka Everything that isn't a colon, followed by a colon, then318// the rest is the style part.319var matches = /^([^\{]+)\{([^\{]+)\}/.exec(cssText);320if (matches.length == 3) {321var selector = matches[1];322var style = matches[2];323cssStyleSheet.addRule(selector, style, index);324} else {325throw Error('Your CSSRule appears to be ill-formatted.');326}327}328};329330331/**332* Cross browser function to remove a CSSRule in a CSSStyleSheet at an index.333* @param {CSSStyleSheet} cssStyleSheet The CSSRule's parentStyleSheet.334* @param {number} index The CSSRule's index in the parentStyleSheet.335*/336goog.cssom.removeCssRule = function(cssStyleSheet, index) {337if (cssStyleSheet.deleteRule) {338// W3C.339cssStyleSheet.deleteRule(index);340341} else {342// IE.343cssStyleSheet.removeRule(index);344}345};346347348/**349* Appends a DOM node to HEAD containing the css text that's passed in.350* @param {string} cssText CSS to add to the end of the document.351* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper user for352* document interactions.353* @return {!Element} The newly created STYLE element.354*/355goog.cssom.addCssText = function(cssText, opt_domHelper) {356var domHelper = opt_domHelper || goog.dom.getDomHelper();357var document = domHelper.getDocument();358var cssNode = domHelper.createElement(goog.dom.TagName.STYLE);359cssNode.type = 'text/css';360var head = domHelper.getElementsByTagName(goog.dom.TagName.HEAD)[0];361head.appendChild(cssNode);362if (cssNode.styleSheet) {363// IE.364cssNode.styleSheet.cssText = cssText;365} else {366// W3C.367var cssTextNode = document.createTextNode(cssText);368cssNode.appendChild(cssTextNode);369}370return cssNode;371};372373374/**375* Cross browser method to get the filename from the StyleSheet's href.376* Explorer only returns the filename in the href, while other agents return377* the full path.378* @param {!StyleSheet} styleSheet Any valid StyleSheet object with an href.379* @throws {Error} When there's no href property found.380* @return {?string} filename The filename, or null if not an external381* styleSheet.382*/383goog.cssom.getFileNameFromStyleSheet = function(styleSheet) {384var href = styleSheet.href;385386// Another IE/FF difference. IE returns an empty string, while FF and others387// return null for CSSStyleSheets not from an external file.388if (!href) {389return null;390}391392// We need the regexp to ensure we get the filename minus any query params.393var matches = /([^\/\?]+)[^\/]*$/.exec(href);394var filename = matches[1];395return filename;396};397398399/**400* Recursively gets all CSS text or rules.401* @param {CSSStyleSheet|StyleSheetList} styleSheet The CSSStyleSheet.402* @param {boolean} isTextOutput If true, output is cssText, otherwise cssRules.403* @return {string|!Array<CSSRule>} cssText or cssRules.404* @private405*/406goog.cssom.getAllCss_ = function(styleSheet, isTextOutput) {407var cssOut = [];408var styleSheets = goog.cssom.getAllCssStyleSheets(styleSheet);409410for (var i = 0; styleSheet = styleSheets[i]; i++) {411var cssRuleList = goog.cssom.getCssRulesFromStyleSheet(styleSheet);412413if (cssRuleList && cssRuleList.length) {414var ruleIndex = 0;415for (var j = 0, n = cssRuleList.length, cssRule; j < n; j++) {416cssRule = cssRuleList[j];417// Gets cssText output, ignoring CSSImportRules.418if (isTextOutput && !cssRule.href) {419var res = goog.cssom.getCssTextFromCssRule(cssRule);420cssOut.push(res);421422} else if (!cssRule.href) {423// Gets cssRules output, ignoring CSSImportRules.424if (cssRule.style) {425// This is a fun little hack to get parentStyleSheet into the rule426// object for IE since it failed to implement rule.parentStyleSheet.427// We can later read this property when doing things like hunting428// for indexes in order to delete a given CSSRule.429// Unfortunately we have to use the style object to store these430// pieces of info since the rule object is read-only.431if (!cssRule.parentStyleSheet) {432(/** @type {!Object} */ (cssRule.style))[433'-closure-parent-stylesheet'] = styleSheet;434}435436// This is a hack to help with possible removal of the rule later,437// where we just append the rule's index in its parentStyleSheet438// onto the style object as a property.439// Unfortunately we have to use the style object to store these440// pieces of info since the rule object is read-only.441(/** @type {!Object} */ (cssRule.style))['-closure-rule-index'] =442isTextOutput ? undefined : ruleIndex;443}444cssOut.push(cssRule);445}446if (!isTextOutput) {447ruleIndex++;448}449}450}451}452return isTextOutput ? cssOut.join(' ') : cssOut;453};454455456