Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(386)

Side by Side Diff: appengine/config_service/ui/bower_components/shadycss/src/style-properties.js

Issue 2923973003: Added base template for config ui. (Closed)
Patch Set: Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698