| 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 {removeCustomPropAssignment, StyleNode} from './css-parse.js' // eslint-d
isable-line no-unused-vars |
| 14 import {nativeShadow} from './style-settings.js' |
| 15 import StyleTransformer from './style-transformer.js' |
| 16 import * as StyleUtil from './style-util.js' |
| 17 import * as RX from './common-regex.js' |
| 18 import StyleInfo from './style-info.js' |
| 19 |
| 20 // TODO: dedupe with shady |
| 21 /** |
| 22 * @const {function(string):boolean} |
| 23 */ |
| 24 const matchesSelector = ((p) => p.matches || p.matchesSelector || |
| 25 p.mozMatchesSelector || p.msMatchesSelector || |
| 26 p.oMatchesSelector || p.webkitMatchesSelector)(window.Element.prototype); |
| 27 |
| 28 const IS_IE = navigator.userAgent.match('Trident'); |
| 29 |
| 30 const XSCOPE_NAME = 'x-scope'; |
| 31 |
| 32 class StyleProperties { |
| 33 get XSCOPE_NAME() { |
| 34 return XSCOPE_NAME; |
| 35 } |
| 36 /** |
| 37 * decorates styles with rule info and returns an array of used style property n
ames |
| 38 * |
| 39 * @param {StyleNode} rules |
| 40 * @return {Array<string>} |
| 41 */ |
| 42 decorateStyles(rules) { |
| 43 let self = this, props = {}, keyframes = [], ruleIndex = 0; |
| 44 StyleUtil.forEachRule(rules, function(rule) { |
| 45 self.decorateRule(rule); |
| 46 // mark in-order position of ast rule in styles block, used for cache key |
| 47 rule.index = ruleIndex++; |
| 48 self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); |
| 49 }, function onKeyframesRule(rule) { |
| 50 keyframes.push(rule); |
| 51 }); |
| 52 // Cache all found keyframes rules for later reference: |
| 53 rules._keyframes = keyframes; |
| 54 // return this list of property names *consumes* in these styles. |
| 55 let names = []; |
| 56 for (let i in props) { |
| 57 names.push(i); |
| 58 } |
| 59 return names; |
| 60 } |
| 61 |
| 62 // decorate a single rule with property info |
| 63 decorateRule(rule) { |
| 64 if (rule.propertyInfo) { |
| 65 return rule.propertyInfo; |
| 66 } |
| 67 let info = {}, properties = {}; |
| 68 let hasProperties = this.collectProperties(rule, properties); |
| 69 if (hasProperties) { |
| 70 info.properties = properties; |
| 71 // TODO(sorvell): workaround parser seeing mixins as additional rules |
| 72 rule['rules'] = null; |
| 73 } |
| 74 info.cssText = this.collectCssText(rule); |
| 75 rule.propertyInfo = info; |
| 76 return info; |
| 77 } |
| 78 |
| 79 // collects the custom properties from a rule's cssText |
| 80 collectProperties(rule, properties) { |
| 81 let info = rule.propertyInfo; |
| 82 if (info) { |
| 83 if (info.properties) { |
| 84 Object.assign(properties, info.properties); |
| 85 return true; |
| 86 } |
| 87 } else { |
| 88 let m, rx = RX.VAR_ASSIGN; |
| 89 let cssText = rule['parsedCssText']; |
| 90 let value; |
| 91 let any; |
| 92 while ((m = rx.exec(cssText))) { |
| 93 // note: group 2 is var, 3 is mixin |
| 94 value = (m[2] || m[3]).trim(); |
| 95 // value of 'inherit' or 'unset' is equivalent to not setting the proper
ty here |
| 96 if (value !== 'inherit' || value !== 'unset') { |
| 97 properties[m[1].trim()] = value; |
| 98 } |
| 99 any = true; |
| 100 } |
| 101 return any; |
| 102 } |
| 103 |
| 104 } |
| 105 |
| 106 // returns cssText of properties that consume variables/mixins |
| 107 collectCssText(rule) { |
| 108 return this.collectConsumingCssText(rule['parsedCssText']); |
| 109 } |
| 110 |
| 111 // NOTE: we support consumption inside mixin assignment |
| 112 // but not production, so strip out {...} |
| 113 collectConsumingCssText(cssText) { |
| 114 return cssText.replace(RX.BRACKETED, '') |
| 115 .replace(RX.VAR_ASSIGN, ''); |
| 116 } |
| 117 |
| 118 collectPropertiesInCssText(cssText, props) { |
| 119 let m; |
| 120 while ((m = RX.VAR_CONSUMED.exec(cssText))) { |
| 121 let name = m[1]; |
| 122 // This regex catches all variable names, and following non-whitespace cha
r |
| 123 // If next char is not ':', then variable is a consumer |
| 124 if (m[2] !== ':') { |
| 125 props[name] = true; |
| 126 } |
| 127 } |
| 128 } |
| 129 |
| 130 // turns custom properties into realized values. |
| 131 reify(props) { |
| 132 // big perf optimization here: reify only *own* properties |
| 133 // since this object has __proto__ of the element's scope properties |
| 134 let names = Object.getOwnPropertyNames(props); |
| 135 for (let i=0, n; i < names.length; i++) { |
| 136 n = names[i]; |
| 137 props[n] = this.valueForProperty(props[n], props); |
| 138 } |
| 139 } |
| 140 |
| 141 // given a property value, returns the reified value |
| 142 // a property value may be: |
| 143 // (1) a literal value like: red or 5px; |
| 144 // (2) a variable value like: var(--a), var(--a, red), or var(--a, --b) or |
| 145 // var(--a, var(--b)); |
| 146 // (3) a literal mixin value like { properties }. Each of these properties |
| 147 // can have values that are: (a) literal, (b) variables, (c) @apply mixins. |
| 148 valueForProperty(property, props) { |
| 149 // case (1) default |
| 150 // case (3) defines a mixin and we have to reify the internals |
| 151 if (property) { |
| 152 if (property.indexOf(';') >=0) { |
| 153 property = this.valueForProperties(property, props); |
| 154 } else { |
| 155 // case (2) variable |
| 156 let self = this; |
| 157 let fn = function(prefix, value, fallback, suffix) { |
| 158 if (!value) { |
| 159 return prefix + suffix; |
| 160 } |
| 161 let propertyValue = self.valueForProperty(props[value], props); |
| 162 // if value is "initial", then the variable should be treated as unset |
| 163 if (!propertyValue || propertyValue === 'initial') { |
| 164 // fallback may be --a or var(--a) or literal |
| 165 propertyValue = self.valueForProperty(props[fallback] || fallback, p
rops) || |
| 166 fallback; |
| 167 } else if (propertyValue === 'apply-shim-inherit') { |
| 168 // CSS build will replace `inherit` with `apply-shim-inherit` |
| 169 // for use with native css variables. |
| 170 // Since we have full control, we can use `inherit` directly. |
| 171 propertyValue = 'inherit'; |
| 172 } |
| 173 return prefix + (propertyValue || '') + suffix; |
| 174 }; |
| 175 property = StyleUtil.processVariableAndFallback(property, fn); |
| 176 } |
| 177 } |
| 178 return property && property.trim() || ''; |
| 179 } |
| 180 |
| 181 // note: we do not yet support mixin within mixin |
| 182 valueForProperties(property, props) { |
| 183 let parts = property.split(';'); |
| 184 for (let i=0, p, m; i<parts.length; i++) { |
| 185 if ((p = parts[i])) { |
| 186 RX.MIXIN_MATCH.lastIndex = 0; |
| 187 m = RX.MIXIN_MATCH.exec(p); |
| 188 if (m) { |
| 189 p = this.valueForProperty(props[m[1]], props); |
| 190 } else { |
| 191 let colon = p.indexOf(':'); |
| 192 if (colon !== -1) { |
| 193 let pp = p.substring(colon); |
| 194 pp = pp.trim(); |
| 195 pp = this.valueForProperty(pp, props) || pp; |
| 196 p = p.substring(0, colon) + pp; |
| 197 } |
| 198 } |
| 199 parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ? |
| 200 // strip trailing ; |
| 201 p.slice(0, -1) : |
| 202 p || ''; |
| 203 } |
| 204 } |
| 205 return parts.join(';'); |
| 206 } |
| 207 |
| 208 applyProperties(rule, props) { |
| 209 let output = ''; |
| 210 // dynamically added sheets may not be decorated so ensure they are. |
| 211 if (!rule.propertyInfo) { |
| 212 this.decorateRule(rule); |
| 213 } |
| 214 if (rule.propertyInfo.cssText) { |
| 215 output = this.valueForProperties(rule.propertyInfo.cssText, props); |
| 216 } |
| 217 rule['cssText'] = output; |
| 218 } |
| 219 |
| 220 // Apply keyframe transformations to the cssText of a given rule. The |
| 221 // keyframeTransforms object is a map of keyframe names to transformer |
| 222 // functions which take in cssText and spit out transformed cssText. |
| 223 applyKeyframeTransforms(rule, keyframeTransforms) { |
| 224 let input = rule['cssText']; |
| 225 let output = rule['cssText']; |
| 226 if (rule.hasAnimations == null) { |
| 227 // Cache whether or not the rule has any animations to begin with: |
| 228 rule.hasAnimations = RX.ANIMATION_MATCH.test(input); |
| 229 } |
| 230 // If there are no animations referenced, we can skip transforms: |
| 231 if (rule.hasAnimations) { |
| 232 let transform; |
| 233 // If we haven't transformed this rule before, we iterate over all |
| 234 // transforms: |
| 235 if (rule.keyframeNamesToTransform == null) { |
| 236 rule.keyframeNamesToTransform = []; |
| 237 for (let keyframe in keyframeTransforms) { |
| 238 transform = keyframeTransforms[keyframe]; |
| 239 output = transform(input); |
| 240 // If the transform actually changed the CSS text, we cache the |
| 241 // transform name for future use: |
| 242 if (input !== output) { |
| 243 input = output; |
| 244 rule.keyframeNamesToTransform.push(keyframe); |
| 245 } |
| 246 } |
| 247 } else { |
| 248 // If we already have a list of keyframe names that apply to this |
| 249 // rule, we apply only those keyframe name transforms: |
| 250 for (let i = 0; i < rule.keyframeNamesToTransform.length; ++i) { |
| 251 transform = keyframeTransforms[rule.keyframeNamesToTransform[i]]; |
| 252 input = transform(input); |
| 253 } |
| 254 output = input; |
| 255 } |
| 256 } |
| 257 rule['cssText'] = output; |
| 258 } |
| 259 |
| 260 // Test if the rules in these styles matches the given `element` and if so, |
| 261 // collect any custom properties into `props`. |
| 262 /** |
| 263 * @param {StyleNode} rules |
| 264 * @param {Element} element |
| 265 */ |
| 266 propertyDataFromStyles(rules, element) { |
| 267 let props = {}, self = this; |
| 268 // generates a unique key for these matches |
| 269 let o = []; |
| 270 // note: active rules excludes non-matching @media rules |
| 271 StyleUtil.forEachRule(rules, function(rule) { |
| 272 // TODO(sorvell): we could trim the set of rules at declaration |
| 273 // time to only include ones that have properties |
| 274 if (!rule.propertyInfo) { |
| 275 self.decorateRule(rule); |
| 276 } |
| 277 // match element against transformedSelector: selector may contain |
| 278 // unwanted uniquification and parsedSelector does not directly match |
| 279 // for :host selectors. |
| 280 let selectorToMatch = rule.transformedSelector || rule['parsedSelector']; |
| 281 if (element && rule.propertyInfo.properties && selectorToMatch) { |
| 282 if (matchesSelector.call(element, selectorToMatch)) { |
| 283 self.collectProperties(rule, props); |
| 284 // produce numeric key for these matches for lookup |
| 285 addToBitMask(rule.index, o); |
| 286 } |
| 287 } |
| 288 }, null, true); |
| 289 return {properties: props, key: o}; |
| 290 } |
| 291 |
| 292 /** |
| 293 * @param {Element} scope |
| 294 * @param {StyleNode} rule |
| 295 * @param {string|undefined} cssBuild |
| 296 * @param {function(Object)} callback |
| 297 */ |
| 298 whenHostOrRootRule(scope, rule, cssBuild, callback) { |
| 299 if (!rule.propertyInfo) { |
| 300 this.decorateRule(rule); |
| 301 } |
| 302 if (!rule.propertyInfo.properties) { |
| 303 return; |
| 304 } |
| 305 let {is, typeExtension} = StyleUtil.getIsExtends(scope); |
| 306 let hostScope = is ? |
| 307 StyleTransformer._calcHostScope(is, typeExtension) : |
| 308 'html'; |
| 309 let parsedSelector = rule['parsedSelector']; |
| 310 let isRoot = (parsedSelector === ':host > *' || parsedSelector === 'html'); |
| 311 let isHost = parsedSelector.indexOf(':host') === 0 && !isRoot; |
| 312 // build info is either in scope (when scope is an element) or in the style |
| 313 // when scope is the default scope; note: this allows default scope to have |
| 314 // mixed mode built and unbuilt styles. |
| 315 if (cssBuild === 'shady') { |
| 316 // :root -> x-foo > *.x-foo for elements and html for custom-style |
| 317 isRoot = parsedSelector === (hostScope + ' > *.' + hostScope) || parsedSel
ector.indexOf('html') !== -1; |
| 318 // :host -> x-foo for elements, but sub-rules have .x-foo in them |
| 319 isHost = !isRoot && parsedSelector.indexOf(hostScope) === 0; |
| 320 } |
| 321 if (cssBuild === 'shadow') { |
| 322 isRoot = parsedSelector === ':host > *' || parsedSelector === 'html'; |
| 323 isHost = isHost && !isRoot; |
| 324 } |
| 325 if (!isRoot && !isHost) { |
| 326 return; |
| 327 } |
| 328 let selectorToMatch = hostScope; |
| 329 if (isHost) { |
| 330 // need to transform :host under ShadowDOM because `:host` does not work w
ith `matches` |
| 331 if (nativeShadow && !rule.transformedSelector) { |
| 332 // transform :host into a matchable selector |
| 333 rule.transformedSelector = |
| 334 StyleTransformer._transformRuleCss( |
| 335 rule, |
| 336 StyleTransformer._transformComplexSelector, |
| 337 StyleTransformer._calcElementScope(is), |
| 338 hostScope |
| 339 ); |
| 340 } |
| 341 selectorToMatch = rule.transformedSelector || hostScope; |
| 342 } |
| 343 callback({ |
| 344 selector: selectorToMatch, |
| 345 isHost: isHost, |
| 346 isRoot: isRoot |
| 347 }); |
| 348 } |
| 349 /** |
| 350 * @param {Element} scope |
| 351 * @param {StyleNode} rules |
| 352 * @return {Object} |
| 353 */ |
| 354 hostAndRootPropertiesForScope(scope, rules) { |
| 355 let hostProps = {}, rootProps = {}, self = this; |
| 356 // note: active rules excludes non-matching @media rules |
| 357 let cssBuild = rules && rules['__cssBuild']; |
| 358 StyleUtil.forEachRule(rules, function(rule) { |
| 359 // if scope is StyleDefaults, use _element for matchesSelector |
| 360 self.whenHostOrRootRule(scope, rule, cssBuild, function(info) { |
| 361 let element = scope._element || scope; |
| 362 if (matchesSelector.call(element, info.selector)) { |
| 363 if (info.isHost) { |
| 364 self.collectProperties(rule, hostProps); |
| 365 } else { |
| 366 self.collectProperties(rule, rootProps); |
| 367 } |
| 368 } |
| 369 }); |
| 370 }, null, true); |
| 371 return {rootProps: rootProps, hostProps: hostProps}; |
| 372 } |
| 373 |
| 374 /** |
| 375 * @param {Element} element |
| 376 * @param {Object} properties |
| 377 * @param {string} scopeSelector |
| 378 */ |
| 379 transformStyles(element, properties, scopeSelector) { |
| 380 let self = this; |
| 381 let {is, typeExtension} = StyleUtil.getIsExtends(element); |
| 382 let hostSelector = StyleTransformer |
| 383 ._calcHostScope(is, typeExtension); |
| 384 let rxHostSelector = element.extends ? |
| 385 '\\' + hostSelector.slice(0, -1) + '\\]' : |
| 386 hostSelector; |
| 387 let hostRx = new RegExp(RX.HOST_PREFIX + rxHostSelector + |
| 388 RX.HOST_SUFFIX); |
| 389 let rules = StyleInfo.get(element).styleRules; |
| 390 let keyframeTransforms = |
| 391 this._elementKeyframeTransforms(element, rules, scopeSelector); |
| 392 return StyleTransformer.elementStyles(element, rules, function(rule) { |
| 393 self.applyProperties(rule, properties); |
| 394 if (!nativeShadow && |
| 395 !StyleUtil.isKeyframesSelector(rule) && |
| 396 rule['cssText']) { |
| 397 // NOTE: keyframe transforms only scope munge animation names, so it |
| 398 // is not necessary to apply them in ShadowDOM. |
| 399 self.applyKeyframeTransforms(rule, keyframeTransforms); |
| 400 self._scopeSelector(rule, hostRx, hostSelector, scopeSelector); |
| 401 } |
| 402 }); |
| 403 } |
| 404 |
| 405 /** |
| 406 * @param {Element} element |
| 407 * @param {StyleNode} rules |
| 408 * @param {string} scopeSelector |
| 409 * @return {Object} |
| 410 */ |
| 411 _elementKeyframeTransforms(element, rules, scopeSelector) { |
| 412 let keyframesRules = rules._keyframes; |
| 413 let keyframeTransforms = {}; |
| 414 if (!nativeShadow && keyframesRules) { |
| 415 // For non-ShadowDOM, we transform all known keyframes rules in |
| 416 // advance for the current scope. This allows us to catch keyframes |
| 417 // rules that appear anywhere in the stylesheet: |
| 418 for (let i = 0, keyframesRule = keyframesRules[i]; |
| 419 i < keyframesRules.length; |
| 420 keyframesRule = keyframesRules[++i]) { |
| 421 this._scopeKeyframes(keyframesRule, scopeSelector); |
| 422 keyframeTransforms[keyframesRule['keyframesName']] = |
| 423 this._keyframesRuleTransformer(keyframesRule); |
| 424 } |
| 425 } |
| 426 return keyframeTransforms; |
| 427 } |
| 428 |
| 429 // Generate a factory for transforming a chunk of CSS text to handle a |
| 430 // particular scoped keyframes rule. |
| 431 /** |
| 432 * @param {StyleNode} keyframesRule |
| 433 * @return {function(string):string} |
| 434 */ |
| 435 _keyframesRuleTransformer(keyframesRule) { |
| 436 return function(cssText) { |
| 437 return cssText.replace( |
| 438 keyframesRule.keyframesNameRx, |
| 439 keyframesRule.transformedKeyframesName); |
| 440 }; |
| 441 } |
| 442 |
| 443 /** |
| 444 * Transforms `@keyframes` names to be unique for the current host. |
| 445 * Example: @keyframes foo-anim -> @keyframes foo-anim-x-foo-0 |
| 446 * |
| 447 * @param {StyleNode} rule |
| 448 * @param {string} scopeId |
| 449 */ |
| 450 _scopeKeyframes(rule, scopeId) { |
| 451 rule.keyframesNameRx = new RegExp(rule['keyframesName'], 'g'); |
| 452 rule.transformedKeyframesName = rule['keyframesName'] + '-' + scopeId; |
| 453 rule.transformedSelector = rule.transformedSelector || rule['selector']; |
| 454 rule['selector'] = rule.transformedSelector.replace( |
| 455 rule['keyframesName'], rule.transformedKeyframesName); |
| 456 } |
| 457 |
| 458 // Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes): |
| 459 // non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo |
| 460 // host selector: x-foo.wide -> .x-foo-42.wide |
| 461 // note: we use only the scope class (.x-foo-42) and not the hostSelector |
| 462 // (x-foo) to scope :host rules; this helps make property host rules |
| 463 // have low specificity. They are overrideable by class selectors but, |
| 464 // unfortunately, not by type selectors (e.g. overriding via |
| 465 // `.special` is ok, but not by `x-foo`). |
| 466 /** |
| 467 * @param {StyleNode} rule |
| 468 * @param {RegExp} hostRx |
| 469 * @param {string} hostSelector |
| 470 * @param {string} scopeId |
| 471 */ |
| 472 _scopeSelector(rule, hostRx, hostSelector, scopeId) { |
| 473 rule.transformedSelector = rule.transformedSelector || rule['selector']; |
| 474 let selector = rule.transformedSelector; |
| 475 let scope = '.' + scopeId; |
| 476 let parts = selector.split(','); |
| 477 for (let i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { |
| 478 parts[i] = p.match(hostRx) ? |
| 479 p.replace(hostSelector, scope) : |
| 480 scope + ' ' + p; |
| 481 } |
| 482 rule['selector'] = parts.join(','); |
| 483 } |
| 484 |
| 485 /** |
| 486 * @param {Element} element |
| 487 * @param {string} selector |
| 488 * @param {string} old |
| 489 */ |
| 490 applyElementScopeSelector(element, selector, old) { |
| 491 let c = element.getAttribute('class') || ''; |
| 492 let v = c; |
| 493 if (old) { |
| 494 v = c.replace( |
| 495 new RegExp('\\s*' + XSCOPE_NAME + '\\s*' + old + '\\s*', 'g'), ' '); |
| 496 } |
| 497 v += (v ? ' ' : '') + XSCOPE_NAME + ' ' + selector; |
| 498 if (c !== v) { |
| 499 StyleUtil.setElementClassRaw(element, v); |
| 500 } |
| 501 } |
| 502 |
| 503 /** |
| 504 * @param {HTMLElement} element |
| 505 * @param {Object} properties |
| 506 * @param {string} selector |
| 507 * @param {HTMLStyleElement} style |
| 508 * @return {HTMLStyleElement} |
| 509 */ |
| 510 applyElementStyle(element, properties, selector, style) { |
| 511 // calculate cssText to apply |
| 512 let cssText = style ? style.textContent || '' : |
| 513 this.transformStyles(element, properties, selector); |
| 514 // if shady and we have a cached style that is not style, decrement |
| 515 let styleInfo = StyleInfo.get(element); |
| 516 let s = styleInfo.customStyle; |
| 517 if (s && !nativeShadow && (s !== style)) { |
| 518 s['_useCount']--; |
| 519 if (s['_useCount'] <= 0 && s.parentNode) { |
| 520 s.parentNode.removeChild(s); |
| 521 } |
| 522 } |
| 523 // apply styling always under native or if we generated style |
| 524 // or the cached style is not in document(!) |
| 525 if (nativeShadow) { |
| 526 // update existing style only under native |
| 527 if (styleInfo.customStyle) { |
| 528 styleInfo.customStyle.textContent = cssText; |
| 529 style = styleInfo.customStyle; |
| 530 // otherwise, if we have css to apply, do so |
| 531 } else if (cssText) { |
| 532 // apply css after the scope style of the element to help with |
| 533 // style precedence rules. |
| 534 style = StyleUtil.applyCss(cssText, selector, element.shadowRoot, |
| 535 styleInfo.placeholder); |
| 536 } |
| 537 } else { |
| 538 // shady and no cache hit |
| 539 if (!style) { |
| 540 // apply css after the scope style of the element to help with |
| 541 // style precedence rules. |
| 542 if (cssText) { |
| 543 style = StyleUtil.applyCss(cssText, selector, null, |
| 544 styleInfo.placeholder); |
| 545 } |
| 546 // shady and cache hit but not in document |
| 547 } else if (!style.parentNode) { |
| 548 if (IS_IE && cssText.indexOf('@media') > -1) { |
| 549 // @media rules may be stale in IE 10 and 11 |
| 550 // refresh the text content of the style to revalidate them. |
| 551 style.textContent = cssText; |
| 552 } |
| 553 StyleUtil.applyStyle(style, null, styleInfo.placeholder); |
| 554 } |
| 555 } |
| 556 // ensure this style is our custom style and increment its use count. |
| 557 if (style) { |
| 558 style['_useCount'] = style['_useCount'] || 0; |
| 559 // increment use count if we changed styles |
| 560 if (styleInfo.customStyle != style) { |
| 561 style['_useCount']++; |
| 562 } |
| 563 styleInfo.customStyle = style; |
| 564 } |
| 565 return style; |
| 566 } |
| 567 |
| 568 /** |
| 569 * @param {Element} style |
| 570 * @param {Object} properties |
| 571 */ |
| 572 applyCustomStyle(style, properties) { |
| 573 let rules = StyleUtil.rulesForStyle(/** @type {HTMLStyleElement} */(style)); |
| 574 let self = this; |
| 575 style.textContent = StyleUtil.toCssText(rules, function(/** StyleNode */rule
) { |
| 576 let css = rule['cssText'] = rule['parsedCssText']; |
| 577 if (rule.propertyInfo && rule.propertyInfo.cssText) { |
| 578 // remove property assignments |
| 579 // so next function isn't confused |
| 580 // NOTE: we have 3 categories of css: |
| 581 // (1) normal properties, |
| 582 // (2) custom property assignments (--foo: red;), |
| 583 // (3) custom property usage: border: var(--foo); @apply(--foo); |
| 584 // In elements, 1 and 3 are separated for efficiency; here they |
| 585 // are not and this makes this case unique. |
| 586 css = removeCustomPropAssignment(/** @type {string} */(css)); |
| 587 // replace with reified properties, scenario is same as mixin |
| 588 rule['cssText'] = self.valueForProperties(css, properties); |
| 589 } |
| 590 }); |
| 591 } |
| 592 } |
| 593 |
| 594 /** |
| 595 * @param {number} n |
| 596 * @param {Array<number>} bits |
| 597 */ |
| 598 function addToBitMask(n, bits) { |
| 599 let o = parseInt(n / 32, 10); |
| 600 let v = 1 << (n % 32); |
| 601 bits[o] = (bits[o] || 0) | v; |
| 602 } |
| 603 |
| 604 export default new StyleProperties(); |
| OLD | NEW |