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