OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library html_css_fixup; | 5 library html_css_fixup; |
6 | 6 |
7 import 'dart:json' as json; | 7 import 'dart:json' as json; |
8 | 8 |
9 import 'package:csslib/parser.dart' as css; | 9 import 'package:csslib/parser.dart' as css; |
10 import 'package:csslib/visitor.dart'; | 10 import 'package:csslib/visitor.dart'; |
11 import 'package:html5lib/dom.dart'; | 11 import 'package:html5lib/dom.dart'; |
12 import 'package:html5lib/dom_parsing.dart'; | 12 import 'package:html5lib/dom_parsing.dart'; |
13 | 13 |
14 import 'compiler.dart'; | 14 import 'compiler.dart'; |
15 import 'emitters.dart'; | 15 import 'emitters.dart'; |
16 import 'info.dart'; | 16 import 'info.dart'; |
17 import 'messages.dart'; | 17 import 'messages.dart'; |
18 import 'options.dart'; | 18 import 'options.dart'; |
19 import 'paths.dart'; | 19 import 'paths.dart'; |
20 import 'utils.dart'; | 20 import 'utils.dart'; |
21 | 21 |
22 /** | 22 /** Enum for type of polyfills supported. */ |
23 * Helper function returns [true] if CSS polyfill is on and component has a | 23 class CssPolyfillKind { |
24 * scoped style tag. | 24 final index; |
25 */ | 25 const CssPolyfillKind(this.index); |
26 bool useCssPolyfill(CompilerOptions opts, ComponentInfo component) => | 26 |
27 opts.processCss && component.scoped; | 27 /** Emit CSS selectors as seen (no polyfill). */ |
| 28 static const NO_POLYFILL = const CssPolyfillKind(0); |
| 29 |
| 30 /** Emit CSS selectors scoped to the "is" attribute of the component. */ |
| 31 static const SCOPED_POLYFILL = const CssPolyfillKind(1); |
| 32 |
| 33 /** Emit CSS selectors mangled. */ |
| 34 static const MANGLED_POLYFILL = const CssPolyfillKind(2); |
| 35 |
| 36 static CssPolyfillKind of(CompilerOptions options, ComponentInfo component) { |
| 37 if (!options.processCss || !component.scoped) return NO_POLYFILL; |
| 38 if (options.mangleCss) return MANGLED_POLYFILL; |
| 39 if (!component.hasAuthorStyles && !hasCssReset) return MANGLED_POLYFILL; |
| 40 return SCOPED_POLYFILL; |
| 41 } |
| 42 } |
| 43 |
28 | 44 |
29 /** | 45 /** |
30 * If processCss is enabled, prefix any component's HTML attributes for id or | 46 * If processCss is enabled, prefix any component's HTML attributes for id or |
31 * class to reference the mangled CSS class name or id. | 47 * class to reference the mangled CSS class name or id. |
32 */ | 48 */ |
33 void fixupHtmlCss(FileInfo fileInfo, CompilerOptions options, | 49 void fixupHtmlCss(FileInfo fileInfo, CompilerOptions options) { |
34 CssPolyfillKind polyfillKind(ComponentInfo component)) { | |
35 // Walk the HTML tree looking for class names or id that are in our parsed | 50 // Walk the HTML tree looking for class names or id that are in our parsed |
36 // stylesheet selectors and making those CSS classes and ids unique to that | 51 // stylesheet selectors and making those CSS classes and ids unique to that |
37 // component. | 52 // component. |
38 if (options.verbose) { | 53 if (options.verbose) { |
39 print(" CSS fixup ${path.basename(fileInfo.inputUrl.resolvedPath)}"); | 54 print(" CSS fixup ${path.basename(fileInfo.inputUrl.resolvedPath)}"); |
40 } | 55 } |
41 for (var component in fileInfo.declaredComponents) { | 56 for (var component in fileInfo.declaredComponents) { |
42 // Mangle class names and element ids in the HTML to match the stylesheet. | 57 // Mangle class names and element ids in the HTML to match the stylesheet. |
43 // TODO(terry): Allow more than one style sheet per component. | 58 // TODO(terry): Allow more than one style sheet per component. |
44 if (component.styleSheets.length == 1) { | 59 if (component.styleSheets.length == 1) { |
45 // For components only 1 stylesheet allowed. | 60 // For components only 1 stylesheet allowed. |
46 var styleSheet = component.styleSheets[0]; | 61 var styleSheet = component.styleSheets[0]; |
47 var prefix = polyfillKind(component) == CssPolyfillKind.MANGLED_POLYFILL ? | 62 var prefix = CssPolyfillKind.of(options, component) == |
48 component.tagName : null; | 63 CssPolyfillKind.MANGLED_POLYFILL ? component.tagName : null; |
49 | 64 |
50 // List of referenced #id and .class in CSS. | 65 // List of referenced #id and .class in CSS. |
51 var knownCss = new IdClassVisitor()..visitTree(styleSheet); | 66 var knownCss = new IdClassVisitor()..visitTree(styleSheet); |
52 // Prefix all id and class refs in CSS selectors and HTML attributes. | 67 // Prefix all id and class refs in CSS selectors and HTML attributes. |
53 new _ScopedStyleRenamer(knownCss, prefix, options.debugCss) | 68 new _ScopedStyleRenamer(knownCss, prefix, options.debugCss) |
54 .visit(component.elementNode); | 69 .visit(component.element); |
55 } | 70 } |
56 } | 71 } |
57 } | 72 } |
58 | 73 |
59 /** Build list of every CSS class name and id selector in a stylesheet. */ | 74 /** Build list of every CSS class name and id selector in a stylesheet. */ |
60 class IdClassVisitor extends Visitor { | 75 class IdClassVisitor extends Visitor { |
61 final Set<String> classes = new Set(); | 76 final Set<String> classes = new Set(); |
62 final Set<String> ids = new Set(); | 77 final Set<String> ids = new Set(); |
63 | 78 |
64 void visitClassSelector(ClassSelector node) { | 79 void visitClassSelector(ClassSelector node) { |
65 classes.add(node.name); | 80 classes.add(node.name); |
66 } | 81 } |
67 | 82 |
68 void visitIdSelector(IdSelector node) { | 83 void visitIdSelector(IdSelector node) { |
69 ids.add(node.name); | 84 ids.add(node.name); |
70 } | 85 } |
71 } | 86 } |
72 | 87 |
73 /** Build the Dart map of managled class/id names and component tag name. */ | 88 /** Build the Dart map of managled class/id names and component tag name. */ |
74 Map _createCssSimpleSelectors(IdClassVisitor visitedCss, ComponentInfo info, | 89 Map _createCssSimpleSelectors(IdClassVisitor visitedCss, ComponentInfo info, |
75 bool mangleNames) { | 90 CssPolyfillKind kind) { |
| 91 bool mangleNames = kind == CssPolyfillKind.MANGLED_POLYFILL; |
76 Map selectors = {}; | 92 Map selectors = {}; |
77 if (visitedCss != null) { | 93 if (visitedCss != null) { |
78 for (var cssClass in visitedCss.classes) { | 94 for (var cssClass in visitedCss.classes) { |
79 selectors['.$cssClass'] = | 95 selectors['.$cssClass'] = |
80 mangleNames ? '${info.tagName}_$cssClass' : cssClass; | 96 mangleNames ? '${info.tagName}_$cssClass' : cssClass; |
81 } | 97 } |
82 for (var id in visitedCss.ids) { | 98 for (var id in visitedCss.ids) { |
83 selectors['#$id'] = mangleNames ? '${info.tagName}_$id' : id; | 99 selectors['#$id'] = mangleNames ? '${info.tagName}_$id' : id; |
84 } | 100 } |
85 } | 101 } |
86 | 102 |
87 // Add tag name selector x-comp == [is="x-comp"]. | 103 // Add tag name selector x-comp == [is="x-comp"]. |
88 var componentName = info.tagName; | 104 var componentName = info.tagName; |
89 selectors['$componentName'] = '[is="$componentName"]'; | 105 selectors['$componentName'] = '[is="$componentName"]'; |
90 | 106 |
91 return selectors; | 107 return selectors; |
92 } | 108 } |
93 | 109 |
94 /** | 110 /** |
95 * Return a map of simple CSS selectors (class and id selectors) as a Dart map | 111 * Return a map of simple CSS selectors (class and id selectors) as a Dart map |
96 * definition. | 112 * definition. |
97 */ | 113 */ |
98 String createCssSelectorsExpression(ComponentInfo info, bool mangled) { | 114 String createCssSelectorsExpression(ComponentInfo info, CssPolyfillKind kind) { |
99 var cssVisited = new IdClassVisitor(); | 115 var cssVisited = new IdClassVisitor(); |
100 | 116 |
101 // For components only 1 stylesheet allowed. | 117 // For components only 1 stylesheet allowed. |
102 if (!info.styleSheets.isEmpty && info.styleSheets.length == 1) { | 118 if (!info.styleSheets.isEmpty && info.styleSheets.length == 1) { |
103 var styleSheet = info.styleSheets[0]; | 119 var styleSheet = info.styleSheets[0]; |
104 cssVisited..visitTree(styleSheet); | 120 cssVisited..visitTree(styleSheet); |
105 } | 121 } |
106 | 122 |
107 return json.stringify(_createCssSimpleSelectors(cssVisited, info, mangled)); | 123 return json.stringify(_createCssSimpleSelectors(cssVisited, info, kind)); |
108 } | 124 } |
109 | 125 |
110 // TODO(terry): Need to handle other selectors than IDs/classes like tag name | 126 // TODO(terry): Need to handle other selectors than IDs/classes like tag name |
111 // e.g., DIV { color: red; } | 127 // e.g., DIV { color: red; } |
112 // TODO(terry): Would be nice if we didn't need to mangle names; requires users | 128 // TODO(terry): Would be nice if we didn't need to mangle names; requires users |
113 // to be careful in their code and makes it more than a "polyfill". | 129 // to be careful in their code and makes it more than a "polyfill". |
114 // Maybe mechanism that generates CSS class name for scoping. This | 130 // Maybe mechanism that generates CSS class name for scoping. This |
115 // would solve tag name selectors (see above TODO). | 131 // would solve tag name selectors (see above TODO). |
116 /** | 132 /** |
117 * Fix a component's HTML to implement scoped stylesheets. | 133 * Fix a component's HTML to implement scoped stylesheets. |
(...skipping 420 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
538 /** List of @imports found. */ | 554 /** List of @imports found. */ |
539 List<UrlInfo> imports = []; | 555 List<UrlInfo> imports = []; |
540 | 556 |
541 CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages, | 557 CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages, |
542 this._options); | 558 this._options); |
543 | 559 |
544 void visitElement(Element node) { | 560 void visitElement(Element node) { |
545 // Don't process any style tags inside of element if we're processing a | 561 // Don't process any style tags inside of element if we're processing a |
546 // FileInfo. The style tags inside of a component defintion will be | 562 // FileInfo. The style tags inside of a component defintion will be |
547 // processed when _info is a ComponentInfo. | 563 // processed when _info is a ComponentInfo. |
548 if (node.tagName == 'element' && _info is FileInfo) return; | 564 if (node.tagName == 'polymer-element' && _info is FileInfo) return; |
549 if (node.tagName == 'style') { | 565 if (node.tagName == 'style') { |
550 // Parse the contents of the scoped style tag. | 566 // Parse the contents of the scoped style tag. |
551 var styleSheet = parseCss(node.nodes.single.value, _messages, _options); | 567 var styleSheet = parseCss(node.nodes.single.value, _messages, _options); |
552 if (styleSheet != null) { | 568 if (styleSheet != null) { |
553 _info.styleSheets.add(styleSheet); | 569 _info.styleSheets.add(styleSheet); |
554 | 570 |
555 // TODO(terry): Check on scoped attribute there's a rumor that styles | 571 // TODO(terry): Check on scoped attribute there's a rumor that styles |
556 // might always be scoped in a component. | 572 // might always be scoped in a component. |
557 // TODO(terry): May need to handle multiple style tags some with scoped | 573 // TODO(terry): May need to handle multiple style tags some with scoped |
558 // and some without for now first style tag determines how | 574 // and some without for now first style tag determines how |
559 // CSS is emitted. | 575 // CSS is emitted. |
560 if (node.attributes.containsKey('scoped') && _info is ComponentInfo) { | 576 if (node.attributes.containsKey('scoped') && _info is ComponentInfo) { |
561 (_info as ComponentInfo).scoped = true; | 577 (_info as ComponentInfo).scoped = true; |
562 } | 578 } |
563 | 579 |
564 // Find all imports return list of @imports in this style tag. | 580 // Find all imports return list of @imports in this style tag. |
565 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot, | 581 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot, |
566 _inputUrl, _messages); | 582 _inputUrl, _messages); |
567 imports.addAll(urlInfos); | 583 imports.addAll(urlInfos); |
568 } | 584 } |
569 } | 585 } |
570 super.visitElement(node); | 586 super.visitElement(node); |
571 } | 587 } |
572 } | 588 } |
OLD | NEW |