| OLD | NEW |
| (Empty) | |
| 1 /** |
| 2 @license |
| 3 Copyright (c) 2017 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 */ |
| 10 |
| 11 'use strict'; |
| 12 |
| 13 import {parse, StyleNode} from './css-parse.js' |
| 14 import {nativeShadow, nativeCssVariables} from './style-settings.js' |
| 15 import StyleTransformer from './style-transformer.js' |
| 16 import * as StyleUtil from './style-util.js' |
| 17 import StyleProperties from './style-properties.js' |
| 18 import placeholderMap from './style-placeholder.js' |
| 19 import StyleInfo from './style-info.js' |
| 20 import StyleCache from './style-cache.js' |
| 21 import {flush as watcherFlush} from './document-watcher.js' |
| 22 import templateMap from './template-map.js' |
| 23 import * as ApplyShimUtils from './apply-shim-utils.js' |
| 24 import documentWait from './document-wait.js' |
| 25 import {updateNativeProperties, detectMixin} from './common-utils.js' |
| 26 import {CustomStyleInterfaceInterface} from './custom-style-interface.js' //esli
nt-disable-line no-unused-vars |
| 27 |
| 28 /** |
| 29 * @const {StyleCache} |
| 30 */ |
| 31 const styleCache = new StyleCache(); |
| 32 |
| 33 export default class ScopingShim { |
| 34 constructor() { |
| 35 this._scopeCounter = {}; |
| 36 this._documentOwner = document.documentElement; |
| 37 let ast = new StyleNode(); |
| 38 ast['rules'] = []; |
| 39 this._documentOwnerStyleInfo = StyleInfo.set(this._documentOwner, new StyleI
nfo(ast)); |
| 40 this._elementsHaveApplied = false; |
| 41 this._applyShim = null; |
| 42 /** @type {?CustomStyleInterfaceInterface} */ |
| 43 this._customStyleInterface = null; |
| 44 documentWait(() => { |
| 45 this._ensure(); |
| 46 }); |
| 47 } |
| 48 flush() { |
| 49 watcherFlush(); |
| 50 } |
| 51 _generateScopeSelector(name) { |
| 52 let id = this._scopeCounter[name] = (this._scopeCounter[name] || 0) + 1; |
| 53 return `${name}-${id}`; |
| 54 } |
| 55 getStyleAst(style) { |
| 56 return StyleUtil.rulesForStyle(style); |
| 57 } |
| 58 styleAstToString(ast) { |
| 59 return StyleUtil.toCssText(ast); |
| 60 } |
| 61 _gatherStyles(template) { |
| 62 let styles = template.content.querySelectorAll('style'); |
| 63 let cssText = []; |
| 64 for (let i = 0; i < styles.length; i++) { |
| 65 let s = styles[i]; |
| 66 cssText.push(s.textContent); |
| 67 s.parentNode.removeChild(s); |
| 68 } |
| 69 return cssText.join('').trim(); |
| 70 } |
| 71 _getCssBuild(template) { |
| 72 let style = template.content.querySelector('style'); |
| 73 if (!style) { |
| 74 return ''; |
| 75 } |
| 76 return style.getAttribute('css-build') || ''; |
| 77 } |
| 78 /** |
| 79 * Prepare the styling and template for the given element type |
| 80 * |
| 81 * @param {HTMLTemplateElement} template |
| 82 * @param {string} elementName |
| 83 * @param {string=} typeExtension |
| 84 */ |
| 85 prepareTemplate(template, elementName, typeExtension) { |
| 86 if (template._prepared) { |
| 87 return; |
| 88 } |
| 89 template._prepared = true; |
| 90 template.name = elementName; |
| 91 template.extends = typeExtension; |
| 92 templateMap[elementName] = template; |
| 93 let cssBuild = this._getCssBuild(template); |
| 94 let cssText = this._gatherStyles(template); |
| 95 let info = { |
| 96 is: elementName, |
| 97 extends: typeExtension, |
| 98 __cssBuild: cssBuild, |
| 99 }; |
| 100 if (!nativeShadow) { |
| 101 StyleTransformer.dom(template.content, elementName); |
| 102 } |
| 103 // check if the styling has mixin definitions or uses |
| 104 this._ensure(); |
| 105 let hasMixins = detectMixin(cssText) |
| 106 let ast = parse(cssText); |
| 107 // only run the applyshim transforms if there is a mixin involved |
| 108 if (hasMixins && nativeCssVariables && this._applyShim) { |
| 109 this._applyShim['transformRules'](ast, elementName); |
| 110 } |
| 111 template['_styleAst'] = ast; |
| 112 template._cssBuild = cssBuild; |
| 113 |
| 114 let ownPropertyNames = []; |
| 115 if (!nativeCssVariables) { |
| 116 ownPropertyNames = StyleProperties.decorateStyles(template['_styleAst'], i
nfo); |
| 117 } |
| 118 if (!ownPropertyNames.length || nativeCssVariables) { |
| 119 let root = nativeShadow ? template.content : null; |
| 120 let placeholder = placeholderMap[elementName]; |
| 121 let style = this._generateStaticStyle(info, template['_styleAst'], root, p
laceholder); |
| 122 template._style = style; |
| 123 } |
| 124 template._ownPropertyNames = ownPropertyNames; |
| 125 } |
| 126 _generateStaticStyle(info, rules, shadowroot, placeholder) { |
| 127 let cssText = StyleTransformer.elementStyles(info, rules); |
| 128 if (cssText.length) { |
| 129 return StyleUtil.applyCss(cssText, info.is, shadowroot, placeholder); |
| 130 } |
| 131 } |
| 132 _prepareHost(host) { |
| 133 let {is, typeExtension} = StyleUtil.getIsExtends(host); |
| 134 let placeholder = placeholderMap[is]; |
| 135 let template = templateMap[is]; |
| 136 let ast; |
| 137 let ownStylePropertyNames; |
| 138 let cssBuild; |
| 139 if (template) { |
| 140 ast = template['_styleAst']; |
| 141 ownStylePropertyNames = template._ownPropertyNames; |
| 142 cssBuild = template._cssBuild; |
| 143 } |
| 144 return StyleInfo.set(host, |
| 145 new StyleInfo( |
| 146 ast, |
| 147 placeholder, |
| 148 ownStylePropertyNames, |
| 149 is, |
| 150 typeExtension, |
| 151 cssBuild |
| 152 ) |
| 153 ); |
| 154 } |
| 155 _ensureApplyShim() { |
| 156 if (this._applyShim) { |
| 157 return; |
| 158 } else if (window.ShadyCSS && window.ShadyCSS.ApplyShim) { |
| 159 this._applyShim = window.ShadyCSS.ApplyShim; |
| 160 this._applyShim['invalidCallback'] = ApplyShimUtils.invalidate; |
| 161 } |
| 162 } |
| 163 _ensureCustomStyleInterface() { |
| 164 if (this._customStyleInterface) { |
| 165 return; |
| 166 } else if (window.ShadyCSS && window.ShadyCSS.CustomStyleInterface) { |
| 167 this._customStyleInterface = /** @type {!CustomStyleInterfaceInterface} */
(window.ShadyCSS.CustomStyleInterface); |
| 168 /** @type {function(!HTMLStyleElement)} */ |
| 169 this._customStyleInterface['transformCallback'] = (style) => {this.transfo
rmCustomStyleForDocument(style)}; |
| 170 this._customStyleInterface['validateCallback'] = () => { |
| 171 requestAnimationFrame(() => { |
| 172 if (this._customStyleInterface['enqueued'] || this._elementsHaveApplie
d) { |
| 173 this.flushCustomStyles(); |
| 174 } |
| 175 }) |
| 176 }; |
| 177 } |
| 178 } |
| 179 _ensure() { |
| 180 this._ensureApplyShim(); |
| 181 this._ensureCustomStyleInterface(); |
| 182 } |
| 183 /** |
| 184 * Flush and apply custom styles to document |
| 185 */ |
| 186 flushCustomStyles() { |
| 187 this._ensure(); |
| 188 if (!this._customStyleInterface) { |
| 189 return; |
| 190 } |
| 191 let customStyles = this._customStyleInterface['processStyles'](); |
| 192 // early return if custom-styles don't need validation |
| 193 if (!this._customStyleInterface['enqueued']) { |
| 194 return; |
| 195 } |
| 196 if (!nativeCssVariables) { |
| 197 this._updateProperties(this._documentOwner, this._documentOwnerStyleInfo); |
| 198 this._applyCustomStyles(customStyles); |
| 199 } else { |
| 200 this._revalidateCustomStyleApplyShim(customStyles); |
| 201 } |
| 202 this._customStyleInterface['enqueued'] = false; |
| 203 // if custom elements have upgraded and there are no native css variables, w
e must recalculate the whole tree |
| 204 if (this._elementsHaveApplied && !nativeCssVariables) { |
| 205 this.styleDocument(); |
| 206 } |
| 207 } |
| 208 /** |
| 209 * Apply styles for the given element |
| 210 * |
| 211 * @param {!HTMLElement} host |
| 212 * @param {Object=} overrideProps |
| 213 */ |
| 214 styleElement(host, overrideProps) { |
| 215 let {is} = StyleUtil.getIsExtends(host); |
| 216 let styleInfo = StyleInfo.get(host); |
| 217 if (!styleInfo) { |
| 218 styleInfo = this._prepareHost(host); |
| 219 } |
| 220 // Only trip the `elementsHaveApplied` flag if a node other that the root do
cument has `applyStyle` called |
| 221 if (!this._isRootOwner(host)) { |
| 222 this._elementsHaveApplied = true; |
| 223 } |
| 224 if (overrideProps) { |
| 225 styleInfo.overrideStyleProperties = |
| 226 styleInfo.overrideStyleProperties || {}; |
| 227 Object.assign(styleInfo.overrideStyleProperties, overrideProps); |
| 228 } |
| 229 if (!nativeCssVariables) { |
| 230 this._updateProperties(host, styleInfo); |
| 231 if (styleInfo.ownStylePropertyNames && styleInfo.ownStylePropertyNames.len
gth) { |
| 232 this._applyStyleProperties(host, styleInfo); |
| 233 } |
| 234 } else { |
| 235 if (styleInfo.overrideStyleProperties) { |
| 236 updateNativeProperties(host, styleInfo.overrideStyleProperties); |
| 237 } |
| 238 let template = templateMap[is]; |
| 239 // bail early if there is no shadowroot for this element |
| 240 if (!template && !this._isRootOwner(host)) { |
| 241 return; |
| 242 } |
| 243 if (template && template._style && !ApplyShimUtils.templateIsValid(templat
e)) { |
| 244 // update template |
| 245 if (!ApplyShimUtils.templateIsValidating(template)) { |
| 246 this._ensure(); |
| 247 this._applyShim && this._applyShim['transformRules'](template['_styleA
st'], is); |
| 248 template._style.textContent = StyleTransformer.elementStyles(host, sty
leInfo.styleRules); |
| 249 ApplyShimUtils.startValidatingTemplate(template); |
| 250 } |
| 251 // update instance if native shadowdom |
| 252 if (nativeShadow) { |
| 253 let root = host.shadowRoot; |
| 254 if (root) { |
| 255 let style = root.querySelector('style'); |
| 256 style.textContent = StyleTransformer.elementStyles(host, styleInfo.s
tyleRules); |
| 257 } |
| 258 } |
| 259 styleInfo.styleRules = template['_styleAst']; |
| 260 } |
| 261 } |
| 262 } |
| 263 _styleOwnerForNode(node) { |
| 264 let root = node.getRootNode(); |
| 265 let host = root.host; |
| 266 if (host) { |
| 267 if (StyleInfo.get(host)) { |
| 268 return host; |
| 269 } else { |
| 270 return this._styleOwnerForNode(host); |
| 271 } |
| 272 } |
| 273 return this._documentOwner; |
| 274 } |
| 275 _isRootOwner(node) { |
| 276 return (node === this._documentOwner); |
| 277 } |
| 278 _applyStyleProperties(host, styleInfo) { |
| 279 let is = StyleUtil.getIsExtends(host).is; |
| 280 let cacheEntry = styleCache.fetch(is, styleInfo.styleProperties, styleInfo.o
wnStylePropertyNames); |
| 281 let cachedScopeSelector = cacheEntry && cacheEntry.scopeSelector; |
| 282 let cachedStyle = cacheEntry ? cacheEntry.styleElement : null; |
| 283 let oldScopeSelector = styleInfo.scopeSelector; |
| 284 // only generate new scope if cached style is not found |
| 285 styleInfo.scopeSelector = cachedScopeSelector || this._generateScopeSelector
(is); |
| 286 let style = StyleProperties.applyElementStyle(host, styleInfo.stylePropertie
s, styleInfo.scopeSelector, cachedStyle); |
| 287 if (!nativeShadow) { |
| 288 StyleProperties.applyElementScopeSelector(host, styleInfo.scopeSelector, o
ldScopeSelector); |
| 289 } |
| 290 if (!cacheEntry) { |
| 291 styleCache.store(is, styleInfo.styleProperties, style, styleInfo.scopeSele
ctor); |
| 292 } |
| 293 return style; |
| 294 } |
| 295 _updateProperties(host, styleInfo) { |
| 296 let owner = this._styleOwnerForNode(host); |
| 297 let ownerStyleInfo = StyleInfo.get(owner); |
| 298 let ownerProperties = ownerStyleInfo.styleProperties; |
| 299 let props = Object.create(ownerProperties || null); |
| 300 let hostAndRootProps = StyleProperties.hostAndRootPropertiesForScope(host, s
tyleInfo.styleRules); |
| 301 let propertyData = StyleProperties.propertyDataFromStyles(ownerStyleInfo.sty
leRules, host); |
| 302 let propertiesMatchingHost = propertyData.properties |
| 303 Object.assign( |
| 304 props, |
| 305 hostAndRootProps.hostProps, |
| 306 propertiesMatchingHost, |
| 307 hostAndRootProps.rootProps |
| 308 ); |
| 309 this._mixinOverrideStyles(props, styleInfo.overrideStyleProperties); |
| 310 StyleProperties.reify(props); |
| 311 styleInfo.styleProperties = props; |
| 312 } |
| 313 _mixinOverrideStyles(props, overrides) { |
| 314 for (let p in overrides) { |
| 315 let v = overrides[p]; |
| 316 // skip override props if they are not truthy or 0 |
| 317 // in order to fall back to inherited values |
| 318 if (v || v === 0) { |
| 319 props[p] = v; |
| 320 } |
| 321 } |
| 322 } |
| 323 /** |
| 324 * Update styles of the whole document |
| 325 * |
| 326 * @param {Object=} properties |
| 327 */ |
| 328 styleDocument(properties) { |
| 329 this.styleSubtree(this._documentOwner, properties); |
| 330 } |
| 331 /** |
| 332 * Update styles of a subtree |
| 333 * |
| 334 * @param {!HTMLElement} host |
| 335 * @param {Object=} properties |
| 336 */ |
| 337 styleSubtree(host, properties) { |
| 338 let root = host.shadowRoot; |
| 339 if (root || this._isRootOwner(host)) { |
| 340 this.styleElement(host, properties); |
| 341 } |
| 342 // process the shadowdom children of `host` |
| 343 let shadowChildren = root && (root.children || root.childNodes); |
| 344 if (shadowChildren) { |
| 345 for (let i = 0; i < shadowChildren.length; i++) { |
| 346 let c = /** @type {!HTMLElement} */(shadowChildren[i]); |
| 347 this.styleSubtree(c); |
| 348 } |
| 349 } else { |
| 350 // process the lightdom children of `host` |
| 351 let children = host.children || host.childNodes; |
| 352 if (children) { |
| 353 for (let i = 0; i < children.length; i++) { |
| 354 let c = /** @type {!HTMLElement} */(children[i]); |
| 355 this.styleSubtree(c); |
| 356 } |
| 357 } |
| 358 } |
| 359 } |
| 360 /* Custom Style operations */ |
| 361 _revalidateCustomStyleApplyShim(customStyles) { |
| 362 for (let i = 0; i < customStyles.length; i++) { |
| 363 let c = customStyles[i]; |
| 364 let s = this._customStyleInterface['getStyleForCustomStyle'](c); |
| 365 if (s) { |
| 366 this._revalidateApplyShim(s); |
| 367 } |
| 368 } |
| 369 } |
| 370 _applyCustomStyles(customStyles) { |
| 371 for (let i = 0; i < customStyles.length; i++) { |
| 372 let c = customStyles[i]; |
| 373 let s = this._customStyleInterface['getStyleForCustomStyle'](c); |
| 374 if (s) { |
| 375 StyleProperties.applyCustomStyle(s, this._documentOwnerStyleInfo.stylePr
operties); |
| 376 } |
| 377 } |
| 378 } |
| 379 transformCustomStyleForDocument(style) { |
| 380 let ast = StyleUtil.rulesForStyle(style); |
| 381 StyleUtil.forEachRule(ast, (rule) => { |
| 382 if (nativeShadow) { |
| 383 StyleTransformer.normalizeRootSelector(rule); |
| 384 } else { |
| 385 StyleTransformer.documentRule(rule); |
| 386 } |
| 387 if (nativeCssVariables) { |
| 388 this._ensure(); |
| 389 this._applyShim && this._applyShim['transformRule'](rule); |
| 390 } |
| 391 }); |
| 392 if (nativeCssVariables) { |
| 393 style.textContent = StyleUtil.toCssText(ast); |
| 394 } else { |
| 395 this._documentOwnerStyleInfo.styleRules.rules.push(ast); |
| 396 } |
| 397 } |
| 398 _revalidateApplyShim(style) { |
| 399 if (nativeCssVariables && this._applyShim) { |
| 400 let ast = StyleUtil.rulesForStyle(style); |
| 401 this._ensure(); |
| 402 this._applyShim['transformRules'](ast); |
| 403 style.textContent = StyleUtil.toCssText(ast); |
| 404 } |
| 405 } |
| 406 getComputedStyleValue(element, property) { |
| 407 let value; |
| 408 if (!nativeCssVariables) { |
| 409 // element is either a style host, or an ancestor of a style host |
| 410 let styleInfo = StyleInfo.get(element) || StyleInfo.get(this._styleOwnerFo
rNode(element)); |
| 411 value = styleInfo.styleProperties[property]; |
| 412 } |
| 413 // fall back to the property value from the computed styling |
| 414 value = value || window.getComputedStyle(element).getPropertyValue(property)
; |
| 415 // trim whitespace that can come after the `:` in css |
| 416 // example: padding: 2px -> " 2px" |
| 417 return value ? value.trim() : ''; |
| 418 } |
| 419 // given an element and a classString, replaces |
| 420 // the element's class with the provided classString and adds |
| 421 // any necessary ShadyCSS static and property based scoping selectors |
| 422 setElementClass(element, classString) { |
| 423 let root = element.getRootNode(); |
| 424 let classes = classString ? classString.split(/\s/) : []; |
| 425 let scopeName = root.host && root.host.localName; |
| 426 // If no scope, try to discover scope name from existing class. |
| 427 // This can occur if, for example, a template stamped element that |
| 428 // has been scoped is manipulated when not in a root. |
| 429 if (!scopeName) { |
| 430 var classAttr = element.getAttribute('class'); |
| 431 if (classAttr) { |
| 432 let k$ = classAttr.split(/\s/); |
| 433 for (let i=0; i < k$.length; i++) { |
| 434 if (k$[i] === StyleTransformer.SCOPE_NAME) { |
| 435 scopeName = k$[i+1]; |
| 436 break; |
| 437 } |
| 438 } |
| 439 } |
| 440 } |
| 441 if (scopeName) { |
| 442 classes.push(StyleTransformer.SCOPE_NAME, scopeName); |
| 443 } |
| 444 if (!nativeCssVariables) { |
| 445 let styleInfo = StyleInfo.get(element); |
| 446 if (styleInfo && styleInfo.scopeSelector) { |
| 447 classes.push(StyleProperties.XSCOPE_NAME, styleInfo.scopeSelector); |
| 448 } |
| 449 } |
| 450 StyleUtil.setElementClassRaw(element, classes.join(' ')); |
| 451 } |
| 452 _styleInfoForNode(node) { |
| 453 return StyleInfo.get(node); |
| 454 } |
| 455 } |
| 456 |
| 457 /* exports */ |
| 458 ScopingShim.prototype['flush'] = ScopingShim.prototype.flush; |
| 459 ScopingShim.prototype['prepareTemplate'] = ScopingShim.prototype.prepareTemplate
; |
| 460 ScopingShim.prototype['styleElement'] = ScopingShim.prototype.styleElement; |
| 461 ScopingShim.prototype['styleDocument'] = ScopingShim.prototype.styleDocument; |
| 462 ScopingShim.prototype['styleSubtree'] = ScopingShim.prototype.styleSubtree; |
| 463 ScopingShim.prototype['getComputedStyleValue'] = ScopingShim.prototype.getComput
edStyleValue; |
| 464 ScopingShim.prototype['setElementClass'] = ScopingShim.prototype.setElementClass
; |
| 465 ScopingShim.prototype['_styleInfoForNode'] = ScopingShim.prototype._styleInfoFor
Node; |
| 466 ScopingShim.prototype['transformCustomStyleForDocument'] = ScopingShim.prototype
.transformCustomStyleForDocument; |
| 467 ScopingShim.prototype['getStyleAst'] = ScopingShim.prototype.getStyleAst; |
| 468 ScopingShim.prototype['styleAstToString'] = ScopingShim.prototype.styleAstToStri
ng; |
| 469 ScopingShim.prototype['flushCustomStyles'] = ScopingShim.prototype.flushCustomSt
yles; |
| 470 Object.defineProperties(ScopingShim.prototype, { |
| 471 'nativeShadow': { |
| 472 get() { |
| 473 return nativeShadow; |
| 474 } |
| 475 }, |
| 476 'nativeCss': { |
| 477 get() { |
| 478 return nativeCssVariables; |
| 479 } |
| 480 } |
| 481 }); |
| OLD | NEW |