1/** 2@license 3Copyright (c) 2017 The Polymer Project Authors. All rights reserved. 4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7Code distributed by Google as part of the polymer project is also 8subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9*/ 10 11'use strict'; 12 13import documentWait from './document-wait.js'; 14 15/** 16 * @typedef {HTMLStyleElement | {getStyle: function():HTMLStyleElement}} 17 */ 18export let CustomStyleProvider; 19 20const SEEN_MARKER = '__seenByShadyCSS'; 21const CACHED_STYLE = '__shadyCSSCachedStyle'; 22 23/** @type {?function(!HTMLStyleElement)} */ 24let transformFn = null; 25 26/** @type {?function()} */ 27let validateFn = null; 28 29/** 30This interface is provided to add document-level <style> elements to ShadyCSS for processing. 31These styles must be processed by ShadyCSS to simulate ShadowRoot upper-bound encapsulation from outside styles 32In addition, these styles may also need to be processed for @apply rules and CSS Custom Properties 33 34To add document-level styles to ShadyCSS, one can call `ShadyCSS.addDocumentStyle(styleElement)` or `ShadyCSS.addDocumentStyle({getStyle: () => styleElement})` 35 36In addition, if the process used to discover document-level styles can be synchronously flushed, one should set `ShadyCSS.documentStyleFlush`. 37This function will be called when calculating styles. 38 39An example usage of the document-level styling api can be found in `examples/document-style-lib.js` 40 41@unrestricted 42*/ 43export default class CustomStyleInterface { 44 constructor() { 45 /** @type {!Array<!CustomStyleProvider>} */ 46 this['customStyles'] = []; 47 this['enqueued'] = false; 48 // NOTE(dfreedm): use quotes here to prevent closure inlining to `function(){}`; 49 documentWait(() => { 50 if (window['ShadyCSS']['flushCustomStyles']) { 51 window['ShadyCSS']['flushCustomStyles'](); 52 } 53 }) 54 } 55 /** 56 * Queue a validation for new custom styles to batch style recalculations 57 */ 58 enqueueDocumentValidation() { 59 if (this['enqueued'] || !validateFn) { 60 return; 61 } 62 this['enqueued'] = true; 63 documentWait(validateFn); 64 } 65 /** 66 * @param {!HTMLStyleElement} style 67 */ 68 addCustomStyle(style) { 69 if (!style[SEEN_MARKER]) { 70 style[SEEN_MARKER] = true; 71 this['customStyles'].push(style); 72 this.enqueueDocumentValidation(); 73 } 74 } 75 /** 76 * @param {!CustomStyleProvider} customStyle 77 * @return {HTMLStyleElement} 78 */ 79 getStyleForCustomStyle(customStyle) { 80 if (customStyle[CACHED_STYLE]) { 81 return customStyle[CACHED_STYLE]; 82 } 83 let style; 84 if (customStyle['getStyle']) { 85 style = customStyle['getStyle'](); 86 } else { 87 style = customStyle; 88 } 89 return style; 90 } 91 /** 92 * @return {!Array<!CustomStyleProvider>} 93 */ 94 processStyles() { 95 const cs = this['customStyles']; 96 for (let i = 0; i < cs.length; i++) { 97 const customStyle = cs[i]; 98 if (customStyle[CACHED_STYLE]) { 99 continue; 100 } 101 const style = this.getStyleForCustomStyle(customStyle); 102 if (style) { 103 // HTMLImports polyfill may have cloned the style into the main document, 104 // which is referenced with __appliedElement. 105 const styleToTransform = /** @type {!HTMLStyleElement} */(style['__appliedElement'] || style); 106 if (transformFn) { 107 transformFn(styleToTransform); 108 } 109 customStyle[CACHED_STYLE] = styleToTransform; 110 } 111 } 112 return cs; 113 } 114} 115 116/* eslint-disable no-self-assign */ 117CustomStyleInterface.prototype['addCustomStyle'] = CustomStyleInterface.prototype.addCustomStyle; 118CustomStyleInterface.prototype['getStyleForCustomStyle'] = CustomStyleInterface.prototype.getStyleForCustomStyle; 119CustomStyleInterface.prototype['processStyles'] = CustomStyleInterface.prototype.processStyles; 120/* eslint-enable no-self-assign */ 121 122Object.defineProperties(CustomStyleInterface.prototype, { 123 'transformCallback': { 124 /** @return {?function(!HTMLStyleElement)} */ 125 get() { 126 return transformFn; 127 }, 128 /** @param {?function(!HTMLStyleElement)} fn */ 129 set(fn) { 130 transformFn = fn; 131 } 132 }, 133 'validateCallback': { 134 /** @return {?function()} */ 135 get() { 136 return validateFn; 137 }, 138 /** 139 * @param {?function()} fn 140 * @this {CustomStyleInterface} 141 */ 142 set(fn) { 143 let needsEnqueue = false; 144 if (!validateFn) { 145 needsEnqueue = true; 146 } 147 validateFn = fn; 148 if (needsEnqueue) { 149 this.enqueueDocumentValidation(); 150 } 151 }, 152 } 153}) 154 155/** @typedef {{ 156 * customStyles: !Array<!CustomStyleProvider>, 157 * addCustomStyle: function(!CustomStyleProvider), 158 * getStyleForCustomStyle: function(!CustomStyleProvider): HTMLStyleElement, 159 * findStyles: function(), 160 * transformCallback: ?function(!HTMLStyleElement), 161 * validateCallback: ?function() 162 * }} 163 */ 164export const CustomStyleInterfaceInterface = {}; 165