| 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 {nativeShadow, nativeCssVariables} from './style-settings.js' |
| 14 import {parse, stringify, types, StyleNode} from './css-parse.js' // eslint-disa
ble-line no-unused-vars |
| 15 import {MEDIA_MATCH} from './common-regex.js'; |
| 16 |
| 17 /** |
| 18 * @param {string|StyleNode} rules |
| 19 * @param {function(StyleNode)=} callback |
| 20 * @return {string} |
| 21 */ |
| 22 export function toCssText (rules, callback) { |
| 23 if (!rules) { |
| 24 return ''; |
| 25 } |
| 26 if (typeof rules === 'string') { |
| 27 rules = parse(rules); |
| 28 } |
| 29 if (callback) { |
| 30 forEachRule(rules, callback); |
| 31 } |
| 32 return stringify(rules, nativeCssVariables); |
| 33 } |
| 34 |
| 35 /** |
| 36 * @param {HTMLStyleElement} style |
| 37 * @return {StyleNode} |
| 38 */ |
| 39 export function rulesForStyle(style) { |
| 40 if (!style['__cssRules'] && style.textContent) { |
| 41 style['__cssRules'] = parse(style.textContent); |
| 42 } |
| 43 return style['__cssRules'] || null; |
| 44 } |
| 45 |
| 46 // Tests if a rule is a keyframes selector, which looks almost exactly |
| 47 // like a normal selector but is not (it has nothing to do with scoping |
| 48 // for example). |
| 49 /** |
| 50 * @param {StyleNode} rule |
| 51 * @return {boolean} |
| 52 */ |
| 53 export function isKeyframesSelector(rule) { |
| 54 return Boolean(rule['parent']) && |
| 55 rule['parent']['type'] === types.KEYFRAMES_RULE; |
| 56 } |
| 57 |
| 58 /** |
| 59 * @param {StyleNode} node |
| 60 * @param {Function=} styleRuleCallback |
| 61 * @param {Function=} keyframesRuleCallback |
| 62 * @param {boolean=} onlyActiveRules |
| 63 */ |
| 64 export function forEachRule(node, styleRuleCallback, keyframesRuleCallback, only
ActiveRules) { |
| 65 if (!node) { |
| 66 return; |
| 67 } |
| 68 let skipRules = false; |
| 69 let type = node['type']; |
| 70 if (onlyActiveRules) { |
| 71 if (type === types.MEDIA_RULE) { |
| 72 let matchMedia = node['selector'].match(MEDIA_MATCH); |
| 73 if (matchMedia) { |
| 74 // if rule is a non matching @media rule, skip subrules |
| 75 if (!window.matchMedia(matchMedia[1]).matches) { |
| 76 skipRules = true; |
| 77 } |
| 78 } |
| 79 } |
| 80 } |
| 81 if (type === types.STYLE_RULE) { |
| 82 styleRuleCallback(node); |
| 83 } else if (keyframesRuleCallback && |
| 84 type === types.KEYFRAMES_RULE) { |
| 85 keyframesRuleCallback(node); |
| 86 } else if (type === types.MIXIN_RULE) { |
| 87 skipRules = true; |
| 88 } |
| 89 let r$ = node['rules']; |
| 90 if (r$ && !skipRules) { |
| 91 for (let i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { |
| 92 forEachRule(r, styleRuleCallback, keyframesRuleCallback, onlyActiveRules); |
| 93 } |
| 94 } |
| 95 } |
| 96 |
| 97 // add a string of cssText to the document. |
| 98 /** |
| 99 * @param {string} cssText |
| 100 * @param {string} moniker |
| 101 * @param {Node} target |
| 102 * @param {Node} contextNode |
| 103 * @return {HTMLStyleElement} |
| 104 */ |
| 105 export function applyCss(cssText, moniker, target, contextNode) { |
| 106 let style = createScopeStyle(cssText, moniker); |
| 107 applyStyle(style, target, contextNode); |
| 108 return style; |
| 109 } |
| 110 |
| 111 /** |
| 112 * @param {string} cssText |
| 113 * @param {string} moniker |
| 114 * @return {HTMLStyleElement} |
| 115 */ |
| 116 export function createScopeStyle(cssText, moniker) { |
| 117 let style = /** @type {HTMLStyleElement} */(document.createElement('style')); |
| 118 if (moniker) { |
| 119 style.setAttribute('scope', moniker); |
| 120 } |
| 121 style.textContent = cssText; |
| 122 return style; |
| 123 } |
| 124 |
| 125 /** |
| 126 * Track the position of the last added style for placing placeholders |
| 127 * @type {Node} |
| 128 */ |
| 129 let lastHeadApplyNode = null; |
| 130 |
| 131 // insert a comment node as a styling position placeholder. |
| 132 /** |
| 133 * @param {string} moniker |
| 134 * @return {!Comment} |
| 135 */ |
| 136 export function applyStylePlaceHolder(moniker) { |
| 137 let placeHolder = document.createComment(' Shady DOM styles for ' + |
| 138 moniker + ' '); |
| 139 let after = lastHeadApplyNode ? |
| 140 lastHeadApplyNode['nextSibling'] : null; |
| 141 let scope = document.head; |
| 142 scope.insertBefore(placeHolder, after || scope.firstChild); |
| 143 lastHeadApplyNode = placeHolder; |
| 144 return placeHolder; |
| 145 } |
| 146 |
| 147 /** |
| 148 * @param {HTMLStyleElement} style |
| 149 * @param {?Node} target |
| 150 * @param {?Node} contextNode |
| 151 */ |
| 152 export function applyStyle(style, target, contextNode) { |
| 153 target = target || document.head; |
| 154 let after = (contextNode && contextNode.nextSibling) || |
| 155 target.firstChild; |
| 156 target.insertBefore(style, after); |
| 157 if (!lastHeadApplyNode) { |
| 158 lastHeadApplyNode = style; |
| 159 } else { |
| 160 // only update lastHeadApplyNode if the new style is inserted after the old
lastHeadApplyNode |
| 161 let position = style.compareDocumentPosition(lastHeadApplyNode); |
| 162 if (position === Node.DOCUMENT_POSITION_PRECEDING) { |
| 163 lastHeadApplyNode = style; |
| 164 } |
| 165 } |
| 166 } |
| 167 |
| 168 /** |
| 169 * @param {string} buildType |
| 170 * @return {boolean} |
| 171 */ |
| 172 export function isTargetedBuild(buildType) { |
| 173 return nativeShadow ? buildType === 'shadow' : buildType === 'shady'; |
| 174 } |
| 175 |
| 176 /** |
| 177 * @param {Element} element |
| 178 * @return {?string} |
| 179 */ |
| 180 export function getCssBuildType(element) { |
| 181 return element.getAttribute('css-build'); |
| 182 } |
| 183 |
| 184 /** |
| 185 * Walk from text[start] matching parens and |
| 186 * returns position of the outer end paren |
| 187 * @param {string} text |
| 188 * @param {number} start |
| 189 * @return {number} |
| 190 */ |
| 191 function findMatchingParen(text, start) { |
| 192 let level = 0; |
| 193 for (let i=start, l=text.length; i < l; i++) { |
| 194 if (text[i] === '(') { |
| 195 level++; |
| 196 } else if (text[i] === ')') { |
| 197 if (--level === 0) { |
| 198 return i; |
| 199 } |
| 200 } |
| 201 } |
| 202 return -1; |
| 203 } |
| 204 |
| 205 /** |
| 206 * @param {string} str |
| 207 * @param {function(string, string, string, string)} callback |
| 208 */ |
| 209 export function processVariableAndFallback(str, callback) { |
| 210 // find 'var(' |
| 211 let start = str.indexOf('var('); |
| 212 if (start === -1) { |
| 213 // no var?, everything is prefix |
| 214 return callback(str, '', '', ''); |
| 215 } |
| 216 //${prefix}var(${inner})${suffix} |
| 217 let end = findMatchingParen(str, start + 3); |
| 218 let inner = str.substring(start + 4, end); |
| 219 let prefix = str.substring(0, start); |
| 220 // suffix may have other variables |
| 221 let suffix = processVariableAndFallback(str.substring(end + 1), callback); |
| 222 let comma = inner.indexOf(','); |
| 223 // value and fallback args should be trimmed to match in property lookup |
| 224 if (comma === -1) { |
| 225 // variable, no fallback |
| 226 return callback(prefix, inner.trim(), '', suffix); |
| 227 } |
| 228 // var(${value},${fallback}) |
| 229 let value = inner.substring(0, comma).trim(); |
| 230 let fallback = inner.substring(comma + 1).trim(); |
| 231 return callback(prefix, value, fallback, suffix); |
| 232 } |
| 233 |
| 234 /** |
| 235 * @param {Element} element |
| 236 * @param {string} value |
| 237 */ |
| 238 export function setElementClassRaw(element, value) { |
| 239 // use native setAttribute provided by ShadyDOM when setAttribute is patched |
| 240 if (nativeShadow) { |
| 241 element.setAttribute('class', value); |
| 242 } else { |
| 243 window['ShadyDOM']['nativeMethods']['setAttribute'].call(element, 'class', v
alue); |
| 244 } |
| 245 } |
| 246 |
| 247 /** |
| 248 * @param {Element | {is: string, extends: string}} element |
| 249 * @return {{is: string, typeExtension: string}} |
| 250 */ |
| 251 export function getIsExtends(element) { |
| 252 let localName = element['localName']; |
| 253 let is = '', typeExtension = ''; |
| 254 /* |
| 255 NOTE: technically, this can be wrong for certain svg elements |
| 256 with `-` in the name like `<font-face>` |
| 257 */ |
| 258 if (localName) { |
| 259 if (localName.indexOf('-') > -1) { |
| 260 is = localName; |
| 261 } else { |
| 262 typeExtension = localName; |
| 263 is = (element.getAttribute && element.getAttribute('is')) || ''; |
| 264 } |
| 265 } else { |
| 266 is = /** @type {?} */(element).is; |
| 267 typeExtension = /** @type {?} */(element).extends; |
| 268 } |
| 269 return {is, typeExtension}; |
| 270 } |
| OLD | NEW |