| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2012 The Polymer Authors. All rights reserved. | |
| 3 * Use of this source code is governed by a BSD-style | |
| 4 * license that can be found in the LICENSE file. | |
| 5 */ | |
| 6 | |
| 7 /* | |
| 8 This is a limited shim for ShadowDOM css styling. | |
| 9 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#style
s | |
| 10 | |
| 11 The intention here is to support only the styling features which can be | |
| 12 relatively simply implemented. The goal is to allow users to avoid the | |
| 13 most obvious pitfalls and do so without compromising performance significantly
. | |
| 14 For ShadowDOM styling that's not covered here, a set of best practices | |
| 15 can be provided that should allow users to accomplish more complex styling. | |
| 16 | |
| 17 The following is a list of specific ShadowDOM styling features and a brief | |
| 18 discussion of the approach used to shim. | |
| 19 | |
| 20 Shimmed features: | |
| 21 | |
| 22 * @host: ShadowDOM allows styling of the shadowRoot's host element using the | |
| 23 @host rule. To shim this feature, the @host styles are reformatted and | |
| 24 prefixed with a given scope name and promoted to a document level stylesheet. | |
| 25 For example, given a scope name of .foo, a rule like this: | |
| 26 | |
| 27 @host { | |
| 28 * { | |
| 29 background: red; | |
| 30 } | |
| 31 } | |
| 32 | |
| 33 becomes: | |
| 34 | |
| 35 .foo { | |
| 36 background: red; | |
| 37 } | |
| 38 | |
| 39 * encapsultion: Styles defined within ShadowDOM, apply only to | |
| 40 dom inside the ShadowDOM. Polymer uses one of two techniques to imlement | |
| 41 this feature. | |
| 42 | |
| 43 By default, rules are prefixed with the host element tag name | |
| 44 as a descendant selector. This ensures styling does not leak out of the 'top' | |
| 45 of the element's ShadowDOM. For example, | |
| 46 | |
| 47 div { | |
| 48 font-weight: bold; | |
| 49 } | |
| 50 | |
| 51 becomes: | |
| 52 | |
| 53 x-foo div { | |
| 54 font-weight: bold; | |
| 55 } | |
| 56 | |
| 57 becomes: | |
| 58 | |
| 59 | |
| 60 Alternatively, if Platform.ShadowCSS.strictStyling is set to true then | |
| 61 selectors are scoped by adding an attribute selector suffix to each | |
| 62 simple selector that contains the host element tag name. Each element | |
| 63 in the element's ShadowDOM template is also given the scope attribute. | |
| 64 Thus, these rules match only elements that have the scope attribute. | |
| 65 For example, given a scope name of x-foo, a rule like this: | |
| 66 | |
| 67 div { | |
| 68 font-weight: bold; | |
| 69 } | |
| 70 | |
| 71 becomes: | |
| 72 | |
| 73 div[x-foo] { | |
| 74 font-weight: bold; | |
| 75 } | |
| 76 | |
| 77 Note that elements that are dynamically added to a scope must have the scope | |
| 78 selector added to them manually. | |
| 79 | |
| 80 * ::pseudo: These rules are converted to rules that take advantage of the | |
| 81 pseudo attribute. For example, a shadowRoot like this inside an x-foo | |
| 82 | |
| 83 <div pseudo="x-special">Special</div> | |
| 84 | |
| 85 with a rule like this: | |
| 86 | |
| 87 x-foo::x-special { ... } | |
| 88 | |
| 89 becomes: | |
| 90 | |
| 91 x-foo [pseudo=x-special] { ... } | |
| 92 | |
| 93 * ::part(): These rules are converted to rules that take advantage of the | |
| 94 part attribute. For example, a shadowRoot like this inside an x-foo | |
| 95 | |
| 96 <div part="special">Special</div> | |
| 97 | |
| 98 with a rule like this: | |
| 99 | |
| 100 x-foo::part(special) { ... } | |
| 101 | |
| 102 becomes: | |
| 103 | |
| 104 x-foo [part=special] { ... } | |
| 105 | |
| 106 Unaddressed ShadowDOM styling features: | |
| 107 | |
| 108 * upper/lower bound encapsulation: Styles which are defined outside a | |
| 109 shadowRoot should not cross the ShadowDOM boundary and should not apply | |
| 110 inside a shadowRoot. | |
| 111 | |
| 112 This styling behavior is not emulated. Some possible ways to do this that | |
| 113 were rejected due to complexity and/or performance concerns include: (1) reset | |
| 114 every possible property for every possible selector for a given scope name; | |
| 115 (2) re-implement css in javascript. | |
| 116 | |
| 117 As an alternative, users should make sure to use selectors | |
| 118 specific to the scope in which they are working. | |
| 119 | |
| 120 * ::distributed: This behavior is not emulated. It's often not necessary | |
| 121 to style the contents of a specific insertion point and instead, descendants | |
| 122 of the host element can be styled selectively. Users can also create an | |
| 123 extra node around an insertion point and style that node's contents | |
| 124 via descendent selectors. For example, with a shadowRoot like this: | |
| 125 | |
| 126 <style> | |
| 127 content::-webkit-distributed(div) { | |
| 128 background: red; | |
| 129 } | |
| 130 </style> | |
| 131 <content></content> | |
| 132 | |
| 133 could become: | |
| 134 | |
| 135 <style> | |
| 136 / *@polyfill .content-container div * / | |
| 137 content::-webkit-distributed(div) { | |
| 138 background: red; | |
| 139 } | |
| 140 </style> | |
| 141 <div class="content-container"> | |
| 142 <content></content> | |
| 143 </div> | |
| 144 | |
| 145 Note the use of @polyfill in the comment above a ShadowDOM specific style | |
| 146 declaration. This is a directive to the styling shim to use the selector | |
| 147 in comments in lieu of the next selector when running under polyfill. | |
| 148 */ | |
| 149 (function(scope) { | |
| 150 | |
| 151 var ShadowCSS = { | |
| 152 strictStyling: false, | |
| 153 registry: {}, | |
| 154 // Shim styles for a given root associated with a name and extendsName | |
| 155 // 1. cache root styles by name | |
| 156 // 2. optionally tag root nodes with scope name | |
| 157 // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */ | |
| 158 // 4. shim @host and scoping | |
| 159 shimStyling: function(root, name, extendsName) { | |
| 160 var typeExtension = this.isTypeExtension(extendsName); | |
| 161 // use caching to make working with styles nodes easier and to facilitate | |
| 162 // lookup of extendee | |
| 163 var def = this.registerDefinition(root, name, extendsName); | |
| 164 // find styles and apply shimming... | |
| 165 if (this.strictStyling) { | |
| 166 this.applyScopeToContent(root, name); | |
| 167 } | |
| 168 // insert @polyfill and @polyfill-rule rules into style elements | |
| 169 // scoping process takes care of shimming these | |
| 170 this.insertPolyfillDirectives(def.rootStyles); | |
| 171 this.insertPolyfillRules(def.rootStyles); | |
| 172 var cssText = this.stylesToShimmedCssText(def.scopeStyles, name, | |
| 173 typeExtension); | |
| 174 // note: we only need to do rootStyles since these are unscoped. | |
| 175 cssText += this.extractPolyfillUnscopedRules(def.rootStyles); | |
| 176 // provide shimmedStyle for user extensibility | |
| 177 def.shimmedStyle = cssTextToStyle(cssText); | |
| 178 if (root) { | |
| 179 root.shimmedStyle = def.shimmedStyle; | |
| 180 } | |
| 181 // remove existing style elements | |
| 182 for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]); | |
| 183 i++) { | |
| 184 s.parentNode.removeChild(s); | |
| 185 } | |
| 186 // add style to document | |
| 187 addCssToDocument(cssText); | |
| 188 }, | |
| 189 registerDefinition: function(root, name, extendsName) { | |
| 190 var def = this.registry[name] = { | |
| 191 root: root, | |
| 192 name: name, | |
| 193 extendsName: extendsName | |
| 194 } | |
| 195 var styles = root ? root.querySelectorAll('style') : []; | |
| 196 styles = styles ? Array.prototype.slice.call(styles, 0) : []; | |
| 197 def.rootStyles = styles; | |
| 198 def.scopeStyles = def.rootStyles; | |
| 199 var extendee = this.registry[def.extendsName]; | |
| 200 if (extendee && (!root || root.querySelector('shadow'))) { | |
| 201 def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles); | |
| 202 } | |
| 203 return def; | |
| 204 }, | |
| 205 isTypeExtension: function(extendsName) { | |
| 206 return extendsName && extendsName.indexOf('-') < 0; | |
| 207 }, | |
| 208 applyScopeToContent: function(root, name) { | |
| 209 if (root) { | |
| 210 // add the name attribute to each node in root. | |
| 211 Array.prototype.forEach.call(root.querySelectorAll('*'), | |
| 212 function(node) { | |
| 213 node.setAttribute(name, ''); | |
| 214 }); | |
| 215 // and template contents too | |
| 216 Array.prototype.forEach.call(root.querySelectorAll('template'), | |
| 217 function(template) { | |
| 218 this.applyScopeToContent(template.content, name); | |
| 219 }, | |
| 220 this); | |
| 221 } | |
| 222 }, | |
| 223 /* | |
| 224 * Process styles to convert native ShadowDOM rules that will trip | |
| 225 * up the css parser; we rely on decorating the stylesheet with comments. | |
| 226 * | |
| 227 * For example, we convert this rule: | |
| 228 * | |
| 229 * (comment start) @polyfill :host menu-item (comment end) | |
| 230 * shadow::-webkit-distributed(menu-item) { | |
| 231 * | |
| 232 * to this: | |
| 233 * | |
| 234 * scopeName menu-item { | |
| 235 * | |
| 236 **/ | |
| 237 insertPolyfillDirectives: function(styles) { | |
| 238 if (styles) { | |
| 239 Array.prototype.forEach.call(styles, function(s) { | |
| 240 s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent); | |
| 241 }, this); | |
| 242 } | |
| 243 }, | |
| 244 insertPolyfillDirectivesInCssText: function(cssText) { | |
| 245 return cssText.replace(cssPolyfillCommentRe, function(match, p1) { | |
| 246 // remove end comment delimiter and add block start | |
| 247 return p1.slice(0, -2) + '{'; | |
| 248 }); | |
| 249 }, | |
| 250 /* | |
| 251 * Process styles to add rules which will only apply under the polyfill | |
| 252 * | |
| 253 * For example, we convert this rule: | |
| 254 * | |
| 255 * (comment start) @polyfill-rule :host menu-item { | |
| 256 * ... } (comment end) | |
| 257 * | |
| 258 * to this: | |
| 259 * | |
| 260 * scopeName menu-item {...} | |
| 261 * | |
| 262 **/ | |
| 263 insertPolyfillRules: function(styles) { | |
| 264 if (styles) { | |
| 265 Array.prototype.forEach.call(styles, function(s) { | |
| 266 s.textContent = this.insertPolyfillRulesInCssText(s.textContent); | |
| 267 }, this); | |
| 268 } | |
| 269 }, | |
| 270 insertPolyfillRulesInCssText: function(cssText) { | |
| 271 return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) { | |
| 272 // remove end comment delimiter | |
| 273 return p1.slice(0, -1); | |
| 274 }); | |
| 275 }, | |
| 276 /* | |
| 277 * Process styles to add rules which will only apply under the polyfill | |
| 278 * and do not process via CSSOM. (CSSOM is destructive to rules on rare | |
| 279 * occasions, e.g. -webkit-calc on Safari.) | |
| 280 * For example, we convert this rule: | |
| 281 * | |
| 282 * (comment start) @polyfill-unscoped-rule menu-item { | |
| 283 * ... } (comment end) | |
| 284 * | |
| 285 * to this: | |
| 286 * | |
| 287 * menu-item {...} | |
| 288 * | |
| 289 **/ | |
| 290 extractPolyfillUnscopedRules: function(styles) { | |
| 291 var cssText = ''; | |
| 292 if (styles) { | |
| 293 Array.prototype.forEach.call(styles, function(s) { | |
| 294 cssText += this.extractPolyfillUnscopedRulesFromCssText( | |
| 295 s.textContent) + '\n\n'; | |
| 296 }, this); | |
| 297 } | |
| 298 return cssText; | |
| 299 }, | |
| 300 extractPolyfillUnscopedRulesFromCssText: function(cssText) { | |
| 301 var r = '', matches; | |
| 302 while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) { | |
| 303 r += matches[1].slice(0, -1) + '\n\n'; | |
| 304 } | |
| 305 return r; | |
| 306 }, | |
| 307 // apply @host and scope shimming | |
| 308 stylesToShimmedCssText: function(styles, name, typeExtension) { | |
| 309 return this.shimAtHost(styles, name, typeExtension) + | |
| 310 this.shimScoping(styles, name, typeExtension); | |
| 311 }, | |
| 312 // form: @host { .foo { declarations } } | |
| 313 // becomes: scopeName.foo { declarations } | |
| 314 shimAtHost: function(styles, name, typeExtension) { | |
| 315 if (styles) { | |
| 316 return this.convertAtHostStyles(styles, name, typeExtension); | |
| 317 } | |
| 318 }, | |
| 319 convertAtHostStyles: function(styles, name, typeExtension) { | |
| 320 var cssText = stylesToCssText(styles), self = this; | |
| 321 cssText = cssText.replace(hostRuleRe, function(m, p1) { | |
| 322 return self.scopeHostCss(p1, name, typeExtension); | |
| 323 }); | |
| 324 cssText = rulesToCss(this.findAtHostRules(cssToRules(cssText), | |
| 325 new RegExp('^' + name + selectorReSuffix, 'm'))); | |
| 326 return cssText; | |
| 327 }, | |
| 328 scopeHostCss: function(cssText, name, typeExtension) { | |
| 329 var self = this; | |
| 330 return cssText.replace(selectorRe, function(m, p1, p2) { | |
| 331 return self.scopeHostSelector(p1, name, typeExtension) + ' ' + p2 + '\n\t'
; | |
| 332 }); | |
| 333 }, | |
| 334 // supports scopig by name and [is=name] syntax | |
| 335 scopeHostSelector: function(selector, name, typeExtension) { | |
| 336 var r = [], parts = selector.split(','), is = '[is=' + name + ']'; | |
| 337 parts.forEach(function(p) { | |
| 338 p = p.trim(); | |
| 339 // selector: *|:scope -> name | |
| 340 if (p.match(hostElementRe)) { | |
| 341 p = p.replace(hostElementRe, typeExtension ? is + '$1$3' : | |
| 342 name + '$1$3'); | |
| 343 // selector: .foo -> name.foo (OR) [bar] -> name[bar] | |
| 344 } else if (p.match(hostFixableRe)) { | |
| 345 p = typeExtension ? is + p : name + p; | |
| 346 } | |
| 347 r.push(p); | |
| 348 }, this); | |
| 349 return r.join(', '); | |
| 350 }, | |
| 351 // consider styles that do not include component name in the selector to be | |
| 352 // unscoped and in need of promotion; | |
| 353 // for convenience, also consider keyframe rules this way. | |
| 354 findAtHostRules: function(cssRules, matcher) { | |
| 355 return Array.prototype.filter.call(cssRules, | |
| 356 this.isHostRule.bind(this, matcher)); | |
| 357 }, | |
| 358 isHostRule: function(matcher, cssRule) { | |
| 359 return (cssRule.selectorText && cssRule.selectorText.match(matcher)) || | |
| 360 (cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).lengt
h) || | |
| 361 (cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE); | |
| 362 }, | |
| 363 /* Ensure styles are scoped. Pseudo-scoping takes a rule like: | |
| 364 * | |
| 365 * .foo {... } | |
| 366 * | |
| 367 * and converts this to | |
| 368 * | |
| 369 * scopeName .foo { ... } | |
| 370 */ | |
| 371 shimScoping: function(styles, name, typeExtension) { | |
| 372 if (styles) { | |
| 373 return this.convertScopedStyles(styles, name, typeExtension); | |
| 374 } | |
| 375 }, | |
| 376 convertScopedStyles: function(styles, name, typeExtension) { | |
| 377 var cssText = stylesToCssText(styles).replace(hostRuleRe, ''); | |
| 378 cssText = this.insertPolyfillHostInCssText(cssText); | |
| 379 cssText = this.convertColonHost(cssText); | |
| 380 cssText = this.convertPseudos(cssText); | |
| 381 cssText = this.convertParts(cssText); | |
| 382 cssText = this.convertCombinators(cssText); | |
| 383 var rules = cssToRules(cssText); | |
| 384 cssText = this.scopeRules(rules, name, typeExtension); | |
| 385 return cssText; | |
| 386 }, | |
| 387 convertPseudos: function(cssText) { | |
| 388 return cssText.replace(cssPseudoRe, ' [pseudo=$1]'); | |
| 389 }, | |
| 390 convertParts: function(cssText) { | |
| 391 return cssText.replace(cssPartRe, ' [part=$1]'); | |
| 392 }, | |
| 393 /* | |
| 394 * convert a rule like :host(.foo) > .bar { } | |
| 395 * | |
| 396 * to | |
| 397 * | |
| 398 * scopeName.foo > .bar, .foo scopeName > .bar { } | |
| 399 * TODO(sorvell): file bug since native impl does not do the former yet. | |
| 400 * http://jsbin.com/OganOCI/2/edit | |
| 401 */ | |
| 402 convertColonHost: function(cssText) { | |
| 403 // p1 = :host, p2 = contents of (), p3 rest of rule | |
| 404 return cssText.replace(cssColonHostRe, function(m, p1, p2, p3) { | |
| 405 return p2 ? polyfillHostNoCombinator + p2 + p3 + ', ' | |
| 406 + p2 + ' ' + p1 + p3 : | |
| 407 p1 + p3; | |
| 408 }); | |
| 409 }, | |
| 410 /* | |
| 411 * Convert ^ and ^^ combinators by replacing with space. | |
| 412 */ | |
| 413 convertCombinators: function(cssText) { | |
| 414 return cssText.replace('^^', ' ').replace('^', ' '); | |
| 415 }, | |
| 416 // change a selector like 'div' to 'name div' | |
| 417 scopeRules: function(cssRules, name, typeExtension) { | |
| 418 var cssText = ''; | |
| 419 Array.prototype.forEach.call(cssRules, function(rule) { | |
| 420 if (rule.selectorText && (rule.style && rule.style.cssText)) { | |
| 421 cssText += this.scopeSelector(rule.selectorText, name, typeExtension, | |
| 422 this.strictStyling) + ' {\n\t'; | |
| 423 cssText += this.propertiesFromRule(rule) + '\n}\n\n'; | |
| 424 } else if (rule.media) { | |
| 425 cssText += '@media ' + rule.media.mediaText + ' {\n'; | |
| 426 cssText += this.scopeRules(rule.cssRules, name); | |
| 427 cssText += '\n}\n\n'; | |
| 428 } else if (rule.cssText) { | |
| 429 cssText += rule.cssText + '\n\n'; | |
| 430 } | |
| 431 }, this); | |
| 432 return cssText; | |
| 433 }, | |
| 434 scopeSelector: function(selector, name, typeExtension, strict) { | |
| 435 var r = [], parts = selector.split(','); | |
| 436 parts.forEach(function(p) { | |
| 437 p = p.trim(); | |
| 438 if (this.selectorNeedsScoping(p, name, typeExtension)) { | |
| 439 p = strict ? this.applyStrictSelectorScope(p, name) : | |
| 440 this.applySimpleSelectorScope(p, name, typeExtension); | |
| 441 } | |
| 442 r.push(p); | |
| 443 }, this); | |
| 444 return r.join(', '); | |
| 445 }, | |
| 446 selectorNeedsScoping: function(selector, name, typeExtension) { | |
| 447 var matchScope = typeExtension ? name : '\\[is=' + name + '\\]'; | |
| 448 var re = new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm'); | |
| 449 return !selector.match(re); | |
| 450 }, | |
| 451 // scope via name and [is=name] | |
| 452 applySimpleSelectorScope: function(selector, name, typeExtension) { | |
| 453 var scoper = typeExtension ? '[is=' + name + ']' : name; | |
| 454 if (selector.match(polyfillHostRe)) { | |
| 455 selector = selector.replace(polyfillHostNoCombinator, scoper); | |
| 456 return selector.replace(polyfillHostRe, scoper + ' '); | |
| 457 } else { | |
| 458 return scoper + ' ' + selector; | |
| 459 } | |
| 460 }, | |
| 461 // return a selector with [name] suffix on each simple selector | |
| 462 // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] | |
| 463 applyStrictSelectorScope: function(selector, name) { | |
| 464 var splits = [' ', '>', '+', '~'], | |
| 465 scoped = selector, | |
| 466 attrName = '[' + name + ']'; | |
| 467 splits.forEach(function(sep) { | |
| 468 var parts = scoped.split(sep); | |
| 469 scoped = parts.map(function(p) { | |
| 470 // remove :host since it should be unnecessary | |
| 471 var t = p.trim().replace(polyfillHostRe, ''); | |
| 472 if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) { | |
| 473 p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3') | |
| 474 } | |
| 475 return p; | |
| 476 }).join(sep); | |
| 477 }); | |
| 478 return scoped; | |
| 479 }, | |
| 480 insertPolyfillHostInCssText: function(selector) { | |
| 481 return selector.replace(hostRe, polyfillHost).replace(colonHostRe, | |
| 482 polyfillHost); | |
| 483 }, | |
| 484 propertiesFromRule: function(rule) { | |
| 485 var properties = rule.style.cssText; | |
| 486 // TODO(sorvell): Chrome cssom incorrectly removes quotes from the content | |
| 487 // property. (https://code.google.com/p/chromium/issues/detail?id=247231) | |
| 488 if (rule.style.content && !rule.style.content.match(/['"]+/)) { | |
| 489 properties = 'content: \'' + rule.style.content + '\';\n' + | |
| 490 rule.style.cssText.replace(/content:[^;]*;/g, ''); | |
| 491 } | |
| 492 return properties; | |
| 493 } | |
| 494 }; | |
| 495 | |
| 496 var hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim, | |
| 497 selectorRe = /([^{]*)({[\s\S]*?})/gim, | |
| 498 hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/, | |
| 499 hostFixableRe = /^[.\[:]/, | |
| 500 cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, | |
| 501 cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?
){/gim, | |
| 502 cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\/
/gim, | |
| 503 cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([
^/*][^*]*\*+)*)\//gim, | |
| 504 cssPseudoRe = /::(x-[^\s{,(]*)/gim, | |
| 505 cssPartRe = /::part\(([^)]*)\)/gim, | |
| 506 // note: :host pre-processed to -host. | |
| 507 cssColonHostRe = /(-host)(?:\(([^)]*)\))?([^,{]*)/gim, | |
| 508 selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$', | |
| 509 hostRe = /@host/gim, | |
| 510 colonHostRe = /\:host/gim, | |
| 511 polyfillHost = '-host', | |
| 512 /* host name without combinator */ | |
| 513 polyfillHostNoCombinator = '-host-no-combinator', | |
| 514 polyfillHostRe = /-host/gim; | |
| 515 | |
| 516 function stylesToCssText(styles, preserveComments) { | |
| 517 var cssText = ''; | |
| 518 Array.prototype.forEach.call(styles, function(s) { | |
| 519 cssText += s.textContent + '\n\n'; | |
| 520 }); | |
| 521 // strip comments for easier processing | |
| 522 if (!preserveComments) { | |
| 523 cssText = cssText.replace(cssCommentRe, ''); | |
| 524 } | |
| 525 return cssText; | |
| 526 } | |
| 527 | |
| 528 function cssTextToStyle(cssText) { | |
| 529 var style = document.createElement('style'); | |
| 530 style.textContent = cssText; | |
| 531 return style; | |
| 532 } | |
| 533 | |
| 534 function cssToRules(cssText) { | |
| 535 var style = cssTextToStyle(cssText); | |
| 536 document.head.appendChild(style); | |
| 537 var rules = style.sheet.cssRules; | |
| 538 style.parentNode.removeChild(style); | |
| 539 return rules; | |
| 540 } | |
| 541 | |
| 542 function rulesToCss(cssRules) { | |
| 543 for (var i=0, css=[]; i < cssRules.length; i++) { | |
| 544 css.push(cssRules[i].cssText); | |
| 545 } | |
| 546 return css.join('\n\n'); | |
| 547 } | |
| 548 | |
| 549 function addCssToDocument(cssText) { | |
| 550 if (cssText) { | |
| 551 getSheet().appendChild(document.createTextNode(cssText)); | |
| 552 } | |
| 553 } | |
| 554 | |
| 555 var sheet; | |
| 556 function getSheet() { | |
| 557 if (!sheet) { | |
| 558 sheet = document.createElement("style"); | |
| 559 sheet.setAttribute('ShadowCSSShim', ''); | |
| 560 } | |
| 561 return sheet; | |
| 562 } | |
| 563 | |
| 564 // add polyfill stylesheet to document | |
| 565 if (window.ShadowDOMPolyfill) { | |
| 566 addCssToDocument('style { display: none !important; }\n'); | |
| 567 var head = document.querySelector('head'); | |
| 568 head.insertBefore(getSheet(), head.childNodes[0]); | |
| 569 } | |
| 570 | |
| 571 // exports | |
| 572 scope.ShadowCSS = ShadowCSS; | |
| 573 | |
| 574 })(window.Platform); | |
| OLD | NEW |