UNPKG

webdash-readme-preview

Version:
474 lines (468 loc) 16.9 kB
/** @license Copyright (c) 2017 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ 'use strict'; import {parse, StyleNode} from './css-parse.js'; import {nativeShadow, nativeCssVariables} from './style-settings.js'; import StyleTransformer from './style-transformer.js'; import * as StyleUtil from './style-util.js'; import StyleProperties from './style-properties.js'; import placeholderMap from './style-placeholder.js'; import StyleInfo from './style-info.js'; import StyleCache from './style-cache.js'; import {flush as watcherFlush} from './document-watcher.js'; import templateMap from './template-map.js'; import * as ApplyShimUtils from './apply-shim-utils.js'; import documentWait from './document-wait.js'; import {updateNativeProperties, detectMixin} from './common-utils.js'; import {CustomStyleInterfaceInterface} from './custom-style-interface.js'; // eslint-disable-line no-unused-vars /** * @const {StyleCache} */ const styleCache = new StyleCache(); export default class ScopingShim { constructor() { this._scopeCounter = {}; this._documentOwner = document.documentElement; let ast = new StyleNode(); ast['rules'] = []; this._documentOwnerStyleInfo = StyleInfo.set(this._documentOwner, new StyleInfo(ast)); this._elementsHaveApplied = false; this._applyShim = null; /** @type {?CustomStyleInterfaceInterface} */ this._customStyleInterface = null; documentWait(() => { this._ensure(); }); } flush() { watcherFlush(); } _generateScopeSelector(name) { let id = this._scopeCounter[name] = (this._scopeCounter[name] || 0) + 1; return `${name}-${id}`; } getStyleAst(style) { return StyleUtil.rulesForStyle(style); } styleAstToString(ast) { return StyleUtil.toCssText(ast); } _gatherStyles(template) { return StyleUtil.gatherStyleText(template.content); } _getCssBuild(template) { let style = template.content.querySelector('style'); if (!style) { return ''; } return style.getAttribute('css-build') || ''; } /** * Prepare the styling and template for the given element type * * @param {HTMLTemplateElement} template * @param {string} elementName * @param {string=} typeExtension */ prepareTemplate(template, elementName, typeExtension) { if (template._prepared) { return; } template._prepared = true; template.name = elementName; template.extends = typeExtension; templateMap[elementName] = template; let cssBuild = this._getCssBuild(template); let cssText = this._gatherStyles(template); let info = { is: elementName, extends: typeExtension, __cssBuild: cssBuild, }; if (!nativeShadow) { StyleTransformer.dom(template.content, elementName); } // check if the styling has mixin definitions or uses this._ensure(); let hasMixins = detectMixin(cssText) let ast = parse(cssText); // only run the applyshim transforms if there is a mixin involved if (hasMixins && nativeCssVariables && this._applyShim) { this._applyShim['transformRules'](ast, elementName); } template['_styleAst'] = ast; template._cssBuild = cssBuild; let ownPropertyNames = []; if (!nativeCssVariables) { ownPropertyNames = StyleProperties.decorateStyles(template['_styleAst'], info); } if (!ownPropertyNames.length || nativeCssVariables) { let root = nativeShadow ? template.content : null; let placeholder = placeholderMap[elementName]; let style = this._generateStaticStyle(info, template['_styleAst'], root, placeholder); template._style = style; } template._ownPropertyNames = ownPropertyNames; } _generateStaticStyle(info, rules, shadowroot, placeholder) { let cssText = StyleTransformer.elementStyles(info, rules); if (cssText.length) { return StyleUtil.applyCss(cssText, info.is, shadowroot, placeholder); } } _prepareHost(host) { let {is, typeExtension} = StyleUtil.getIsExtends(host); let placeholder = placeholderMap[is]; let template = templateMap[is]; let ast; let ownStylePropertyNames; let cssBuild; if (template) { ast = template['_styleAst']; ownStylePropertyNames = template._ownPropertyNames; cssBuild = template._cssBuild; } return StyleInfo.set(host, new StyleInfo( ast, placeholder, ownStylePropertyNames, is, typeExtension, cssBuild ) ); } _ensureApplyShim() { if (this._applyShim) { return; } else if (window.ShadyCSS && window.ShadyCSS.ApplyShim) { this._applyShim = window.ShadyCSS.ApplyShim; this._applyShim['invalidCallback'] = ApplyShimUtils.invalidate; } } _ensureCustomStyleInterface() { if (this._customStyleInterface) { return; } else if (window.ShadyCSS && window.ShadyCSS.CustomStyleInterface) { this._customStyleInterface = /** @type {!CustomStyleInterfaceInterface} */(window.ShadyCSS.CustomStyleInterface); /** @type {function(!HTMLStyleElement)} */ this._customStyleInterface['transformCallback'] = (style) => {this.transformCustomStyleForDocument(style)}; this._customStyleInterface['validateCallback'] = () => { requestAnimationFrame(() => { if (this._customStyleInterface['enqueued'] || this._elementsHaveApplied) { this.flushCustomStyles(); } }) }; } } _ensure() { this._ensureApplyShim(); this._ensureCustomStyleInterface(); } /** * Flush and apply custom styles to document */ flushCustomStyles() { this._ensure(); if (!this._customStyleInterface) { return; } let customStyles = this._customStyleInterface['processStyles'](); // early return if custom-styles don't need validation if (!this._customStyleInterface['enqueued']) { return; } if (!nativeCssVariables) { this._updateProperties(this._documentOwner, this._documentOwnerStyleInfo); this._applyCustomStyles(customStyles); } else { this._revalidateCustomStyleApplyShim(customStyles); } this._customStyleInterface['enqueued'] = false; // if custom elements have upgraded and there are no native css variables, we must recalculate the whole tree if (this._elementsHaveApplied && !nativeCssVariables) { this.styleDocument(); } } /** * Apply styles for the given element * * @param {!HTMLElement} host * @param {Object=} overrideProps */ styleElement(host, overrideProps) { let {is} = StyleUtil.getIsExtends(host); let styleInfo = StyleInfo.get(host); if (!styleInfo) { styleInfo = this._prepareHost(host); } // Only trip the `elementsHaveApplied` flag if a node other that the root document has `applyStyle` called if (!this._isRootOwner(host)) { this._elementsHaveApplied = true; } if (overrideProps) { styleInfo.overrideStyleProperties = styleInfo.overrideStyleProperties || {}; Object.assign(styleInfo.overrideStyleProperties, overrideProps); } if (!nativeCssVariables) { this._updateProperties(host, styleInfo); if (styleInfo.ownStylePropertyNames && styleInfo.ownStylePropertyNames.length) { this._applyStyleProperties(host, styleInfo); } } else { if (styleInfo.overrideStyleProperties) { updateNativeProperties(host, styleInfo.overrideStyleProperties); } let template = templateMap[is]; // bail early if there is no shadowroot for this element if (!template && !this._isRootOwner(host)) { return; } if (template && template._style && !ApplyShimUtils.templateIsValid(template)) { // update template if (!ApplyShimUtils.templateIsValidating(template)) { this._ensure(); this._applyShim && this._applyShim['transformRules'](template['_styleAst'], is); template._style.textContent = StyleTransformer.elementStyles(host, styleInfo.styleRules); ApplyShimUtils.startValidatingTemplate(template); } // update instance if native shadowdom if (nativeShadow) { let root = host.shadowRoot; if (root) { let style = root.querySelector('style'); style.textContent = StyleTransformer.elementStyles(host, styleInfo.styleRules); } } styleInfo.styleRules = template['_styleAst']; } } } _styleOwnerForNode(node) { let root = node.getRootNode(); let host = root.host; if (host) { if (StyleInfo.get(host)) { return host; } else { return this._styleOwnerForNode(host); } } return this._documentOwner; } _isRootOwner(node) { return (node === this._documentOwner); } _applyStyleProperties(host, styleInfo) { let is = StyleUtil.getIsExtends(host).is; let cacheEntry = styleCache.fetch(is, styleInfo.styleProperties, styleInfo.ownStylePropertyNames); let cachedScopeSelector = cacheEntry && cacheEntry.scopeSelector; let cachedStyle = cacheEntry ? cacheEntry.styleElement : null; let oldScopeSelector = styleInfo.scopeSelector; // only generate new scope if cached style is not found styleInfo.scopeSelector = cachedScopeSelector || this._generateScopeSelector(is); let style = StyleProperties.applyElementStyle(host, styleInfo.styleProperties, styleInfo.scopeSelector, cachedStyle); if (!nativeShadow) { StyleProperties.applyElementScopeSelector(host, styleInfo.scopeSelector, oldScopeSelector); } if (!cacheEntry) { styleCache.store(is, styleInfo.styleProperties, style, styleInfo.scopeSelector); } return style; } _updateProperties(host, styleInfo) { let owner = this._styleOwnerForNode(host); let ownerStyleInfo = StyleInfo.get(owner); let ownerProperties = ownerStyleInfo.styleProperties; let props = Object.create(ownerProperties || null); let hostAndRootProps = StyleProperties.hostAndRootPropertiesForScope(host, styleInfo.styleRules); let propertyData = StyleProperties.propertyDataFromStyles(ownerStyleInfo.styleRules, host); let propertiesMatchingHost = propertyData.properties Object.assign( props, hostAndRootProps.hostProps, propertiesMatchingHost, hostAndRootProps.rootProps ); this._mixinOverrideStyles(props, styleInfo.overrideStyleProperties); StyleProperties.reify(props); styleInfo.styleProperties = props; } _mixinOverrideStyles(props, overrides) { for (let p in overrides) { let v = overrides[p]; // skip override props if they are not truthy or 0 // in order to fall back to inherited values if (v || v === 0) { props[p] = v; } } } /** * Update styles of the whole document * * @param {Object=} properties */ styleDocument(properties) { this.styleSubtree(this._documentOwner, properties); } /** * Update styles of a subtree * * @param {!HTMLElement} host * @param {Object=} properties */ styleSubtree(host, properties) { let root = host.shadowRoot; if (root || this._isRootOwner(host)) { this.styleElement(host, properties); } // process the shadowdom children of `host` let shadowChildren = root && (root.children || root.childNodes); if (shadowChildren) { for (let i = 0; i < shadowChildren.length; i++) { let c = /** @type {!HTMLElement} */(shadowChildren[i]); this.styleSubtree(c); } } else { // process the lightdom children of `host` let children = host.children || host.childNodes; if (children) { for (let i = 0; i < children.length; i++) { let c = /** @type {!HTMLElement} */(children[i]); this.styleSubtree(c); } } } } /* Custom Style operations */ _revalidateCustomStyleApplyShim(customStyles) { for (let i = 0; i < customStyles.length; i++) { let c = customStyles[i]; let s = this._customStyleInterface['getStyleForCustomStyle'](c); if (s) { this._revalidateApplyShim(s); } } } _applyCustomStyles(customStyles) { for (let i = 0; i < customStyles.length; i++) { let c = customStyles[i]; let s = this._customStyleInterface['getStyleForCustomStyle'](c); if (s) { StyleProperties.applyCustomStyle(s, this._documentOwnerStyleInfo.styleProperties); } } } transformCustomStyleForDocument(style) { let ast = StyleUtil.rulesForStyle(style); StyleUtil.forEachRule(ast, (rule) => { if (nativeShadow) { StyleTransformer.normalizeRootSelector(rule); } else { StyleTransformer.documentRule(rule); } if (nativeCssVariables) { this._ensure(); this._applyShim && this._applyShim['transformRule'](rule); } }); if (nativeCssVariables) { style.textContent = StyleUtil.toCssText(ast); } else { this._documentOwnerStyleInfo.styleRules.rules.push(ast); } } _revalidateApplyShim(style) { if (nativeCssVariables && this._applyShim) { let ast = StyleUtil.rulesForStyle(style); this._ensure(); this._applyShim['transformRules'](ast); style.textContent = StyleUtil.toCssText(ast); } } getComputedStyleValue(element, property) { let value; if (!nativeCssVariables) { // element is either a style host, or an ancestor of a style host let styleInfo = StyleInfo.get(element) || StyleInfo.get(this._styleOwnerForNode(element)); value = styleInfo.styleProperties[property]; } // fall back to the property value from the computed styling value = value || window.getComputedStyle(element).getPropertyValue(property); // trim whitespace that can come after the `:` in css // example: padding: 2px -> " 2px" return value ? value.trim() : ''; } // given an element and a classString, replaces // the element's class with the provided classString and adds // any necessary ShadyCSS static and property based scoping selectors setElementClass(element, classString) { let root = element.getRootNode(); let classes = classString ? classString.split(/\s/) : []; let scopeName = root.host && root.host.localName; // If no scope, try to discover scope name from existing class. // This can occur if, for example, a template stamped element that // has been scoped is manipulated when not in a root. if (!scopeName) { var classAttr = element.getAttribute('class'); if (classAttr) { let k$ = classAttr.split(/\s/); for (let i=0; i < k$.length; i++) { if (k$[i] === StyleTransformer.SCOPE_NAME) { scopeName = k$[i+1]; break; } } } } if (scopeName) { classes.push(StyleTransformer.SCOPE_NAME, scopeName); } if (!nativeCssVariables) { let styleInfo = StyleInfo.get(element); if (styleInfo && styleInfo.scopeSelector) { classes.push(StyleProperties.XSCOPE_NAME, styleInfo.scopeSelector); } } StyleUtil.setElementClassRaw(element, classes.join(' ')); } _styleInfoForNode(node) { return StyleInfo.get(node); } } /* exports */ ScopingShim.prototype['flush'] = ScopingShim.prototype.flush; ScopingShim.prototype['prepareTemplate'] = ScopingShim.prototype.prepareTemplate; ScopingShim.prototype['styleElement'] = ScopingShim.prototype.styleElement; ScopingShim.prototype['styleDocument'] = ScopingShim.prototype.styleDocument; ScopingShim.prototype['styleSubtree'] = ScopingShim.prototype.styleSubtree; ScopingShim.prototype['getComputedStyleValue'] = ScopingShim.prototype.getComputedStyleValue; ScopingShim.prototype['setElementClass'] = ScopingShim.prototype.setElementClass; ScopingShim.prototype['_styleInfoForNode'] = ScopingShim.prototype._styleInfoForNode; ScopingShim.prototype['transformCustomStyleForDocument'] = ScopingShim.prototype.transformCustomStyleForDocument; ScopingShim.prototype['getStyleAst'] = ScopingShim.prototype.getStyleAst; ScopingShim.prototype['styleAstToString'] = ScopingShim.prototype.styleAstToString; ScopingShim.prototype['flushCustomStyles'] = ScopingShim.prototype.flushCustomStyles; Object.defineProperties(ScopingShim.prototype, { 'nativeShadow': { get() { return nativeShadow; } }, 'nativeCss': { get() { return nativeCssVariables; } } });