use class attribute in applyElementScopeSelector

className can be a special object in IE

Fixes #3285
This commit is contained in:
Daniel Freedman 2016-01-15 14:07:54 -08:00
parent 9cd6b796a1
commit 07d8c0622e

View File

@ -1,380 +1,380 @@
<!-- <!--
@license @license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved. Copyright (c) 2014 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 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 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 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 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 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--> -->
<link rel="import" href="style-transformer.html"> <link rel="import" href="style-transformer.html">
<script> <script>
Polymer.StyleProperties = (function() { Polymer.StyleProperties = (function() {
'use strict'; 'use strict';
var nativeShadow = Polymer.Settings.useNativeShadow; var nativeShadow = Polymer.Settings.useNativeShadow;
var matchesSelector = Polymer.DomApi.matchesSelector; var matchesSelector = Polymer.DomApi.matchesSelector;
var styleUtil = Polymer.StyleUtil; var styleUtil = Polymer.StyleUtil;
var styleTransformer = Polymer.StyleTransformer; var styleTransformer = Polymer.StyleTransformer;
return { return {
// decorates styles with rule info and returns an array of used style // decorates styles with rule info and returns an array of used style
// property names // property names
decorateStyles: function(styles) { decorateStyles: function(styles) {
var self = this, props = {}; var self = this, props = {};
styleUtil.forRulesInStyles(styles, function(rule) { styleUtil.forRulesInStyles(styles, function(rule) {
self.decorateRule(rule); self.decorateRule(rule);
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
}); });
// return this list of property names *consumes* in these styles. // return this list of property names *consumes* in these styles.
var names = []; var names = [];
for (var i in props) { for (var i in props) {
names.push(i); names.push(i);
} }
return names; return names;
}, },
// decorate a single rule with property info // decorate a single rule with property info
decorateRule: function(rule) { decorateRule: function(rule) {
if (rule.propertyInfo) { if (rule.propertyInfo) {
return rule.propertyInfo; return rule.propertyInfo;
} }
var info = {}, properties = {}; var info = {}, properties = {};
var hasProperties = this.collectProperties(rule, properties); var hasProperties = this.collectProperties(rule, properties);
if (hasProperties) { if (hasProperties) {
info.properties = properties; info.properties = properties;
// TODO(sorvell): workaround parser seeing mixins as additional rules // TODO(sorvell): workaround parser seeing mixins as additional rules
rule.rules = null; rule.rules = null;
} }
info.cssText = this.collectCssText(rule); info.cssText = this.collectCssText(rule);
rule.propertyInfo = info; rule.propertyInfo = info;
return info; return info;
}, },
// collects the custom properties from a rule's cssText // collects the custom properties from a rule's cssText
collectProperties: function(rule, properties) { collectProperties: function(rule, properties) {
var info = rule.propertyInfo; var info = rule.propertyInfo;
if (info) { if (info) {
if (info.properties) { if (info.properties) {
Polymer.Base.mixin(properties, info.properties); Polymer.Base.mixin(properties, info.properties);
return true; return true;
} }
} else { } else {
var m, rx = this.rx.VAR_ASSIGN; var m, rx = this.rx.VAR_ASSIGN;
var cssText = rule.parsedCssText; var cssText = rule.parsedCssText;
var any; var any;
while (m = rx.exec(cssText)) { while (m = rx.exec(cssText)) {
// note: group 2 is var, 3 is mixin // note: group 2 is var, 3 is mixin
properties[m[1]] = (m[2] || m[3]).trim(); properties[m[1]] = (m[2] || m[3]).trim();
any = true; any = true;
} }
return any; return any;
} }
}, },
// returns cssText of properties that consume variables/mixins // returns cssText of properties that consume variables/mixins
collectCssText: function(rule) { collectCssText: function(rule) {
var customCssText = ''; var customCssText = '';
var cssText = rule.parsedCssText; var cssText = rule.parsedCssText;
// NOTE: we support consumption inside mixin assignment // NOTE: we support consumption inside mixin assignment
// but not production, so strip out {...} // but not production, so strip out {...}
cssText = cssText.replace(this.rx.BRACKETED, '') cssText = cssText.replace(this.rx.BRACKETED, '')
.replace(this.rx.VAR_ASSIGN, ''); .replace(this.rx.VAR_ASSIGN, '');
var parts = cssText.split(';'); var parts = cssText.split(';');
for (var i=0, p; i<parts.length; i++) { for (var i=0, p; i<parts.length; i++) {
p = parts[i]; p = parts[i];
customCssText += p + ';\n'; customCssText += p + ';\n';
} }
return customCssText; return customCssText;
}, },
collectPropertiesInCssText: function(cssText, props) { collectPropertiesInCssText: function(cssText, props) {
var m; var m;
while (m = this.rx.VAR_CAPTURE.exec(cssText)) { while (m = this.rx.VAR_CAPTURE.exec(cssText)) {
props[m[1]] = true; props[m[1]] = true;
var def = m[2]; var def = m[2];
if (def && def.match(this.rx.IS_VAR)) { if (def && def.match(this.rx.IS_VAR)) {
props[def] = true; props[def] = true;
} }
} }
}, },
// turns custom properties into realized values. // turns custom properties into realized values.
reify: function(props) { reify: function(props) {
// big perf optimization here: reify only *own* properties // big perf optimization here: reify only *own* properties
// since this object has __proto__ of the element's scope properties // since this object has __proto__ of the element's scope properties
var names = Object.getOwnPropertyNames(props); var names = Object.getOwnPropertyNames(props);
for (var i=0, n; i < names.length; i++) { for (var i=0, n; i < names.length; i++) {
n = names[i]; n = names[i];
props[n] = this.valueForProperty(props[n], props); props[n] = this.valueForProperty(props[n], props);
} }
}, },
// given a property value, returns the reified value // given a property value, returns the reified value
// a property value may be: // a property value may be:
// (1) a literal value like: red or 5px; // (1) a literal value like: red or 5px;
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b); // (2) a variable value like: var(--a), var(--a, red), or var(--a, --b);
// (3) a literal mixin value like { properties }. Each of these properties // (3) a literal mixin value like { properties }. Each of these properties
// can have values that are: (a) literal, (b) variables, (c) @apply mixins. // can have values that are: (a) literal, (b) variables, (c) @apply mixins.
valueForProperty: function(property, props) { valueForProperty: function(property, props) {
// case (1) default // case (1) default
// case (3) defines a mixin and we have to reify the internals // case (3) defines a mixin and we have to reify the internals
if (property) { if (property) {
if (property.indexOf(';') >=0) { if (property.indexOf(';') >=0) {
property = this.valueForProperties(property, props); property = this.valueForProperties(property, props);
} else { } else {
// case (2) variable // case (2) variable
var self = this; var self = this;
var fn = function(all, prefix, value, fallback) { var fn = function(all, prefix, value, fallback) {
var propertyValue = (self.valueForProperty(props[value], props) || var propertyValue = (self.valueForProperty(props[value], props) ||
(props[fallback] ? (props[fallback] ?
self.valueForProperty(props[fallback], props) : self.valueForProperty(props[fallback], props) :
fallback)); fallback));
return prefix + (propertyValue || ''); return prefix + (propertyValue || '');
}; };
property = property.replace(this.rx.VAR_MATCH, fn); property = property.replace(this.rx.VAR_MATCH, fn);
} }
} }
return property && property.trim() || ''; return property && property.trim() || '';
}, },
// note: we do not yet support mixin within mixin // note: we do not yet support mixin within mixin
valueForProperties: function(property, props) { valueForProperties: function(property, props) {
var parts = property.split(';'); var parts = property.split(';');
for (var i=0, p, m; i<parts.length; i++) { for (var i=0, p, m; i<parts.length; i++) {
if (p = parts[i]) { if (p = parts[i]) {
m = p.match(this.rx.MIXIN_MATCH); m = p.match(this.rx.MIXIN_MATCH);
if (m) { if (m) {
p = this.valueForProperty(props[m[1]], props); p = this.valueForProperty(props[m[1]], props);
} else { } else {
var pp = p.split(':'); var pp = p.split(':');
if (pp[1]) { if (pp[1]) {
pp[1] = pp[1].trim(); pp[1] = pp[1].trim();
pp[1] = this.valueForProperty(pp[1], props) || pp[1]; pp[1] = this.valueForProperty(pp[1], props) || pp[1];
} }
p = pp.join(':'); p = pp.join(':');
} }
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ? parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
// strip trailing ; // strip trailing ;
p.slice(0, -1) : p.slice(0, -1) :
p || ''; p || '';
} }
} }
return parts.join(';'); return parts.join(';');
}, },
applyProperties: function(rule, props) { applyProperties: function(rule, props) {
var output = ''; var output = '';
// dynamically added sheets may not be decorated so ensure they are. // dynamically added sheets may not be decorated so ensure they are.
if (!rule.propertyInfo) { if (!rule.propertyInfo) {
this.decorateRule(rule); this.decorateRule(rule);
} }
if (rule.propertyInfo.cssText) { if (rule.propertyInfo.cssText) {
output = this.valueForProperties(rule.propertyInfo.cssText, props); output = this.valueForProperties(rule.propertyInfo.cssText, props);
} }
rule.cssText = output; rule.cssText = output;
}, },
// Test if the rules in these styles matches the given `element` and if so, // Test if the rules in these styles matches the given `element` and if so,
// collect any custom properties into `props`. // collect any custom properties into `props`.
propertyDataFromStyles: function(styles, element) { propertyDataFromStyles: function(styles, element) {
var props = {}, self = this; var props = {}, self = this;
// generates a unique key for these matches // generates a unique key for these matches
var o = [], i = 0; var o = [], i = 0;
styleUtil.forRulesInStyles(styles, function(rule) { styleUtil.forRulesInStyles(styles, function(rule) {
// TODO(sorvell): we could trim the set of rules at declaration // TODO(sorvell): we could trim the set of rules at declaration
// time to only include ones that have properties // time to only include ones that have properties
if (!rule.propertyInfo) { if (!rule.propertyInfo) {
self.decorateRule(rule); self.decorateRule(rule);
} }
// match element against transformedSelector: selector may contain // match element against transformedSelector: selector may contain
// unwanted uniquification and parsedSelector does not directly match // unwanted uniquification and parsedSelector does not directly match
// for :host selectors. // for :host selectors.
if (element && rule.propertyInfo.properties && if (element && rule.propertyInfo.properties &&
matchesSelector.call(element, rule.transformedSelector matchesSelector.call(element, rule.transformedSelector
|| rule.parsedSelector)) { || rule.parsedSelector)) {
self.collectProperties(rule, props); self.collectProperties(rule, props);
// produce numeric key for these matches for lookup // produce numeric key for these matches for lookup
addToBitMask(i, o); addToBitMask(i, o);
} }
i++; i++;
}); });
return {properties: props, key: o}; return {properties: props, key: o};
}, },
// Test if a rule matches scope criteria (* or :root) and if so, // Test if a rule matches scope criteria (* or :root) and if so,
// collect any custom properties into `props`. // collect any custom properties into `props`.
scopePropertiesFromStyles: function(styles) { scopePropertiesFromStyles: function(styles) {
if (!styles._scopeStyleProperties) { if (!styles._scopeStyleProperties) {
styles._scopeStyleProperties = styles._scopeStyleProperties =
this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS); this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS);
} }
return styles._scopeStyleProperties; return styles._scopeStyleProperties;
}, },
// Test if a rule matches host criteria (:host) and if so, // Test if a rule matches host criteria (:host) and if so,
// collect any custom properties into `props`. // collect any custom properties into `props`.
// //
// TODO(sorvell): this should change to collecting properties from any // TODO(sorvell): this should change to collecting properties from any
// :host(...) and then matching these against self. // :host(...) and then matching these against self.
hostPropertiesFromStyles: function(styles) { hostPropertiesFromStyles: function(styles) {
if (!styles._hostStyleProperties) { if (!styles._hostStyleProperties) {
styles._hostStyleProperties = styles._hostStyleProperties =
this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS); this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS);
} }
return styles._hostStyleProperties; return styles._hostStyleProperties;
}, },
selectedPropertiesFromStyles: function(styles, selectors) { selectedPropertiesFromStyles: function(styles, selectors) {
var props = {}, self = this; var props = {}, self = this;
styleUtil.forRulesInStyles(styles, function(rule) { styleUtil.forRulesInStyles(styles, function(rule) {
if (!rule.propertyInfo) { if (!rule.propertyInfo) {
self.decorateRule(rule); self.decorateRule(rule);
} }
for (var i=0; i < selectors.length; i++) { for (var i=0; i < selectors.length; i++) {
if (rule.parsedSelector === selectors[i]) { if (rule.parsedSelector === selectors[i]) {
self.collectProperties(rule, props); self.collectProperties(rule, props);
return; return;
} }
} }
}); });
return props; return props;
}, },
transformStyles: function(element, properties, scopeSelector) { transformStyles: function(element, properties, scopeSelector) {
var self = this; var self = this;
var hostSelector = styleTransformer var hostSelector = styleTransformer
._calcHostScope(element.is, element.extends); ._calcHostScope(element.is, element.extends);
var rxHostSelector = element.extends ? var rxHostSelector = element.extends ?
'\\' + hostSelector.slice(0, -1) + '\\]' : '\\' + hostSelector.slice(0, -1) + '\\]' :
hostSelector; hostSelector;
var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector +
this.rx.HOST_SUFFIX); this.rx.HOST_SUFFIX);
return styleTransformer.elementStyles(element, function(rule) { return styleTransformer.elementStyles(element, function(rule) {
self.applyProperties(rule, properties); self.applyProperties(rule, properties);
if (rule.cssText && !nativeShadow) { if (rule.cssText && !nativeShadow) {
self._scopeSelector(rule, hostRx, hostSelector, self._scopeSelector(rule, hostRx, hostSelector,
element._scopeCssViaAttr, scopeSelector); element._scopeCssViaAttr, scopeSelector);
} }
}); });
}, },
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes): // Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo // non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
// host selector: x-foo.wide -> x-foo.x-foo-42.wide // host selector: x-foo.wide -> x-foo.x-foo-42.wide
_scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) { _scopeSelector: function(rule, hostRx, hostSelector, viaAttr, scopeId) {
rule.transformedSelector = rule.transformedSelector || rule.selector; rule.transformedSelector = rule.transformedSelector || rule.selector;
var selector = rule.transformedSelector; var selector = rule.transformedSelector;
var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' +
scopeId + ']' : scopeId + ']' :
'.' + scopeId; '.' + scopeId;
var parts = selector.split(','); var parts = selector.split(',');
for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
parts[i] = p.match(hostRx) ? parts[i] = p.match(hostRx) ?
p.replace(hostSelector, hostSelector + scope) : p.replace(hostSelector, hostSelector + scope) :
scope + ' ' + p; scope + ' ' + p;
} }
rule.selector = parts.join(','); rule.selector = parts.join(',');
}, },
applyElementScopeSelector: function(element, selector, old, viaAttr) { applyElementScopeSelector: function(element, selector, old, viaAttr) {
var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) :
element.className; (element.getAttribute('class') || '');
var v = old ? c.replace(old, selector) : var v = old ? c.replace(old, selector) :
(c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector; (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector;
if (c !== v) { if (c !== v) {
if (viaAttr) { if (viaAttr) {
element.setAttribute(styleTransformer.SCOPE_NAME, v); element.setAttribute(styleTransformer.SCOPE_NAME, v);
} else { } else {
element.className = v; element.setAttribute('class', v);
} }
} }
}, },
applyElementStyle: function(element, properties, selector, style) { applyElementStyle: function(element, properties, selector, style) {
// calculate cssText to apply // calculate cssText to apply
var cssText = style ? style.textContent || '' : var cssText = style ? style.textContent || '' :
this.transformStyles(element, properties, selector); this.transformStyles(element, properties, selector);
// if shady and we have a cached style that is not style, decrement // if shady and we have a cached style that is not style, decrement
var s = element._customStyle; var s = element._customStyle;
if (s && !nativeShadow && (s !== style)) { if (s && !nativeShadow && (s !== style)) {
s._useCount--; s._useCount--;
if (s._useCount <= 0 && s.parentNode) { if (s._useCount <= 0 && s.parentNode) {
s.parentNode.removeChild(s); s.parentNode.removeChild(s);
} }
} }
// apply styling always under native or if we generated style // apply styling always under native or if we generated style
// or the cached style is not in document(!) // or the cached style is not in document(!)
if (nativeShadow || (!style || !style.parentNode)) { if (nativeShadow || (!style || !style.parentNode)) {
// update existing style only under native // update existing style only under native
if (nativeShadow && element._customStyle) { if (nativeShadow && element._customStyle) {
element._customStyle.textContent = cssText; element._customStyle.textContent = cssText;
style = element._customStyle; style = element._customStyle;
// otherwise, if we have css to apply, do so // otherwise, if we have css to apply, do so
} else if (cssText) { } else if (cssText) {
// apply css after the scope style of the element to help with // apply css after the scope style of the element to help with
// style precedence rules. // style precedence rules.
style = styleUtil.applyCss(cssText, selector, style = styleUtil.applyCss(cssText, selector,
nativeShadow ? element.root : null, element._scopeStyle); nativeShadow ? element.root : null, element._scopeStyle);
} }
} }
// ensure this style is our custom style and increment its use count. // ensure this style is our custom style and increment its use count.
if (style) { if (style) {
style._useCount = style._useCount || 0; style._useCount = style._useCount || 0;
// increment use count if we changed styles // increment use count if we changed styles
if (element._customStyle != style) { if (element._customStyle != style) {
style._useCount++; style._useCount++;
} }
element._customStyle = style; element._customStyle = style;
} }
return style; return style;
}, },
// customStyle properties are applied if they are truthy or 0. Otherwise, // customStyle properties are applied if they are truthy or 0. Otherwise,
// they are skipped; this allows properties previously set in customStyle // they are skipped; this allows properties previously set in customStyle
// to be easily reset to inherited values. // to be easily reset to inherited values.
mixinCustomStyle: function(props, customStyle) { mixinCustomStyle: function(props, customStyle) {
var v; var v;
for (var i in customStyle) { for (var i in customStyle) {
v = customStyle[i]; v = customStyle[i];
if (v || v === 0) { if (v || v === 0) {
props[i] = v; props[i] = v;
} }
} }
}, },
rx: { rx: {
VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi, VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi,
MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i, MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i,
// note, this supports: // note, this supports:
// var(--a) // var(--a)
// var(--a, --b) // var(--a, --b)
// var(--a, fallback-literal) // var(--a, fallback-literal)
// var(--a, fallback-literal(with-one-nested-parentheses)) // var(--a, fallback-literal(with-one-nested-parentheses))
VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi, VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi,
VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi, VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi,
IS_VAR: /^--/, IS_VAR: /^--/,
BRACKETED: /\{[^}]*\}/g, BRACKETED: /\{[^}]*\}/g,
HOST_PREFIX: '(?:^|[^.#[:])', HOST_PREFIX: '(?:^|[^.#[:])',
HOST_SUFFIX: '($|[.:[\\s>+~])' HOST_SUFFIX: '($|[.:[\\s>+~])'
}, },
HOST_SELECTORS: [':host'], HOST_SELECTORS: [':host'],
SCOPE_SELECTORS: [':root'], SCOPE_SELECTORS: [':root'],
XSCOPE_NAME: 'x-scope' XSCOPE_NAME: 'x-scope'
}; };
function addToBitMask(n, bits) { function addToBitMask(n, bits) {
var o = parseInt(n / 32); var o = parseInt(n / 32);
var v = 1 << (n % 32); var v = 1 << (n % 32);
bits[o] = (bits[o] || 0) | v; bits[o] = (bits[o] || 0) | v;
} }
})(); })();
</script> </script>