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 /** Portion of the analyzer dealing with CSS sources. */ |
6 | 6 library polymer.src.css_analyzer; |
7 import 'dart:json' as json; | |
8 | 7 |
9 import 'package:csslib/parser.dart' as css; | 8 import 'package:csslib/parser.dart' as css; |
10 import 'package:csslib/visitor.dart'; | 9 import 'package:csslib/visitor.dart'; |
11 import 'package:html5lib/dom.dart'; | 10 import 'package:html5lib/dom.dart'; |
12 import 'package:html5lib/dom_parsing.dart'; | 11 import 'package:html5lib/dom_parsing.dart'; |
13 | 12 |
14 import 'compiler.dart'; | |
15 import 'emitters.dart'; | |
16 import 'info.dart'; | 13 import 'info.dart'; |
| 14 import 'files.dart' show SourceFile; |
17 import 'messages.dart'; | 15 import 'messages.dart'; |
18 import 'compiler_options.dart'; | 16 import 'compiler_options.dart'; |
19 import 'paths.dart'; | |
20 import 'utils.dart'; | |
21 | 17 |
22 /** Enum for type of polyfills supported. */ | 18 void analyzeCss(String packageRoot, List<SourceFile> files, |
23 class CssPolyfillKind { | 19 Map<String, FileInfo> info, Map<String, String> pseudoElements, |
24 final _index; | 20 Messages messages, {warningsAsErrors: false}) { |
25 const CssPolyfillKind._internal(this._index); | 21 var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages, |
26 | 22 warningsAsErrors); |
27 /** Emit CSS selectors as seen (no polyfill). */ | 23 for (var file in files) analyzer.process(file); |
28 static const NO_POLYFILL = const CssPolyfillKind._internal(0); | 24 analyzer.normalize(); |
29 | |
30 /** Emit CSS selectors scoped to the "is" attribute of the component. */ | |
31 static const SCOPED_POLYFILL = const CssPolyfillKind._internal(1); | |
32 | |
33 /** Emit CSS selectors mangled. */ | |
34 static const MANGLED_POLYFILL = const CssPolyfillKind._internal(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 && !options.hasCssReset) { | |
40 return MANGLED_POLYFILL; | |
41 } | |
42 return SCOPED_POLYFILL; | |
43 } | |
44 } | 25 } |
45 | 26 |
| 27 class _AnalyzerCss { |
| 28 final String packageRoot; |
| 29 final Map<String, FileInfo> info; |
| 30 final Map<String, String> _pseudoElements; |
| 31 final Messages _messages; |
| 32 final bool _warningsAsErrors; |
46 | 33 |
47 /** | 34 Set<StyleSheet> allStyleSheets = new Set<StyleSheet>(); |
48 * If processCss is enabled, prefix any component's HTML attributes for id or | |
49 * class to reference the mangled CSS class name or id. | |
50 */ | |
51 void fixupHtmlCss(FileInfo fileInfo, CompilerOptions options) { | |
52 // Walk the HTML tree looking for class names or id that are in our parsed | |
53 // stylesheet selectors and making those CSS classes and ids unique to that | |
54 // component. | |
55 if (options.verbose) { | |
56 print(" CSS fixup ${path.basename(fileInfo.inputUrl.resolvedPath)}"); | |
57 } | |
58 for (var component in fileInfo.declaredComponents) { | |
59 // Mangle class names and element ids in the HTML to match the stylesheet. | |
60 // TODO(terry): Allow more than one style sheet per component. | |
61 if (component.styleSheets.length == 1) { | |
62 // For components only 1 stylesheet allowed. | |
63 var styleSheet = component.styleSheets[0]; | |
64 var prefix = CssPolyfillKind.of(options, component) == | |
65 CssPolyfillKind.MANGLED_POLYFILL ? component.tagName : null; | |
66 | 35 |
67 // List of referenced #id and .class in CSS. | 36 /** |
68 var knownCss = new IdClassVisitor()..visitTree(styleSheet); | 37 * [_pseudoElements] list of known pseudo attributes found in HTML, any |
69 // Prefix all id and class refs in CSS selectors and HTML attributes. | 38 * CSS pseudo-elements 'name::custom-element' is mapped to the manged name |
70 new _ScopedStyleRenamer(knownCss, prefix, options.debugCss) | 39 * associated with the pseudo-element key. |
71 .visit(component.element); | 40 */ |
| 41 _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements, |
| 42 this._messages, this._warningsAsErrors); |
| 43 |
| 44 /** |
| 45 * Run the analyzer on every file that is a style sheet or any component that |
| 46 * has a style tag. |
| 47 */ |
| 48 void process(SourceFile file) { |
| 49 var fileInfo = info[file.path]; |
| 50 if (file.isStyleSheet || fileInfo.styleSheets.length > 0) { |
| 51 var styleSheets = processVars(fileInfo); |
| 52 |
| 53 // Add to list of all style sheets analyzed. |
| 54 allStyleSheets.addAll(styleSheets); |
72 } | 55 } |
73 } | |
74 } | |
75 | 56 |
76 /** Build list of every CSS class name and id selector in a stylesheet. */ | 57 // Process any components. |
77 class IdClassVisitor extends Visitor { | 58 for (var component in fileInfo.declaredComponents) { |
78 final Set<String> classes = new Set(); | 59 var all = processVars(component); |
79 final Set<String> ids = new Set(); | |
80 | 60 |
81 void visitClassSelector(ClassSelector node) { | 61 // Add to list of all style sheets analyzed. |
82 classes.add(node.name); | 62 allStyleSheets.addAll(all); |
| 63 } |
| 64 |
| 65 processCustomPseudoElements(); |
83 } | 66 } |
84 | 67 |
85 void visitIdSelector(IdSelector node) { | 68 void normalize() { |
86 ids.add(node.name); | 69 // Remove all var definitions for all style sheets analyzed. |
87 } | 70 for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree)
; |
88 } | |
89 | |
90 /** Build the Dart map of managled class/id names and component tag name. */ | |
91 Map _createCssSimpleSelectors(IdClassVisitor visitedCss, ComponentInfo info, | |
92 CssPolyfillKind kind) { | |
93 bool mangleNames = kind == CssPolyfillKind.MANGLED_POLYFILL; | |
94 Map selectors = {}; | |
95 if (visitedCss != null) { | |
96 for (var cssClass in visitedCss.classes) { | |
97 selectors['.$cssClass'] = | |
98 mangleNames ? '${info.tagName}_$cssClass' : cssClass; | |
99 } | |
100 for (var id in visitedCss.ids) { | |
101 selectors['#$id'] = mangleNames ? '${info.tagName}_$id' : id; | |
102 } | |
103 } | 71 } |
104 | 72 |
105 // Add tag name selector x-comp == [is="x-comp"]. | 73 List<StyleSheet> processVars(var libraryInfo) { |
106 var componentName = info.tagName; | 74 // Get list of all stylesheet(s) dependencies referenced from this file. |
107 selectors['$componentName'] = '[is="$componentName"]'; | 75 var styleSheets = _dependencies(libraryInfo).toList(); |
108 | 76 |
109 return selectors; | 77 var errors = []; |
110 } | 78 css.analyze(styleSheets, errors: errors, options: |
| 79 [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']); |
111 | 80 |
112 /** | 81 // Print errors as warnings. |
113 * Return a map of simple CSS selectors (class and id selectors) as a Dart map | 82 for (var e in errors) { |
114 * definition. | 83 _messages.warning(e.message, e.span); |
115 */ | 84 } |
116 String createCssSelectorsExpression(ComponentInfo info, CssPolyfillKind kind) { | |
117 var cssVisited = new IdClassVisitor(); | |
118 | 85 |
119 // For components only 1 stylesheet allowed. | 86 // Build list of all var definitions. |
120 if (!info.styleSheets.isEmpty && info.styleSheets.length == 1) { | 87 Map varDefs = new Map(); |
121 var styleSheet = info.styleSheets[0]; | 88 for (var tree in styleSheets) { |
122 cssVisited..visitTree(styleSheet); | 89 var allDefs = (new _VarDefinitions()..visitTree(tree)).found; |
| 90 allDefs.forEach((key, value) { |
| 91 varDefs[key] = value; |
| 92 }); |
| 93 } |
| 94 |
| 95 // Resolve all definitions to a non-VarUsage (terminal expression). |
| 96 varDefs.forEach((key, value) { |
| 97 for (var expr in (value.expression as Expressions).expressions) { |
| 98 var def = _findTerminalVarDefinition(varDefs, value); |
| 99 varDefs[key] = def; |
| 100 } |
| 101 }); |
| 102 |
| 103 // Resolve all var usages. |
| 104 for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree)
; |
| 105 |
| 106 return styleSheets; |
123 } | 107 } |
124 | 108 |
125 return json.stringify(_createCssSimpleSelectors(cssVisited, info, kind)); | 109 processCustomPseudoElements() { |
126 } | 110 var polyFiller = new _PseudoElementExpander(_pseudoElements); |
127 | 111 for (var tree in allStyleSheets) { |
128 // TODO(terry): Need to handle other selectors than IDs/classes like tag name | 112 polyFiller.visitTree(tree); |
129 // e.g., DIV { color: red; } | |
130 // TODO(terry): Would be nice if we didn't need to mangle names; requires users | |
131 // to be careful in their code and makes it more than a "polyfill". | |
132 // Maybe mechanism that generates CSS class name for scoping. This | |
133 // would solve tag name selectors (see above TODO). | |
134 /** | |
135 * Fix a component's HTML to implement scoped stylesheets. | |
136 * | |
137 * We do this by renaming all element class and id attributes to be globally | |
138 * unique to a component. | |
139 */ | |
140 class _ScopedStyleRenamer extends TreeVisitor { | |
141 final bool _debugCss; | |
142 | |
143 /** Set of classes and ids defined for this component. */ | |
144 final IdClassVisitor _knownCss; | |
145 | |
146 /** Prefix to apply to each class/id reference. */ | |
147 final String _prefix; | |
148 | |
149 _ScopedStyleRenamer(this._knownCss, this._prefix, this._debugCss); | |
150 | |
151 void visitElement(Element node) { | |
152 // Walk the HTML elements mangling any references to id or class attributes. | |
153 _mangleClassAttribute(node, _knownCss.classes, _prefix); | |
154 _mangleIdAttribute(node, _knownCss.ids, _prefix); | |
155 | |
156 super.visitElement(node); | |
157 } | |
158 | |
159 /** | |
160 * Mangles HTML class reference that matches a CSS class name defined in the | |
161 * component's style sheet. | |
162 */ | |
163 void _mangleClassAttribute(Node node, Set<String> classes, String prefix) { | |
164 if (node.attributes.containsKey('class')) { | |
165 var refClasses = node.attributes['class'].trim().split(" "); | |
166 | |
167 bool changed = false; | |
168 var len = refClasses.length; | |
169 for (var i = 0; i < len; i++) { | |
170 var refClass = refClasses[i]; | |
171 if (classes.contains(refClass)) { | |
172 if (prefix != null) { | |
173 refClasses[i] = '${prefix}_$refClass'; | |
174 changed = true; | |
175 } | |
176 } | |
177 } | |
178 | |
179 if (changed) { | |
180 StringBuffer newClasses = new StringBuffer(); | |
181 refClasses.forEach((String className) { | |
182 newClasses.write("${(newClasses.length > 0) ? ' ' : ''}$className"); | |
183 }); | |
184 var mangledClasses = newClasses.toString(); | |
185 if (_debugCss) { | |
186 print(" class = ${node.attributes['class'].trim()} => " | |
187 "$mangledClasses"); | |
188 } | |
189 node.attributes['class'] = mangledClasses; | |
190 } | |
191 } | 113 } |
192 } | 114 } |
193 | 115 |
194 /** | 116 /** |
195 * Mangles an HTML id reference that matches a CSS id selector name defined | 117 * Given a component or file check if any stylesheets referenced. If so then |
196 * in the component's style sheet. | 118 * return a list of all referenced stylesheet dependencies (@imports or <link |
| 119 * rel="stylesheet" ..>). |
197 */ | 120 */ |
198 void _mangleIdAttribute(Node node, Set<String> ids, String prefix) { | 121 Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) { |
199 if (prefix != null) { | 122 if (seen == null) seen = new Set(); |
200 var id = node.attributes['id']; | 123 |
201 if (id != null && ids.contains(id)) { | 124 // Used to resolve all pathing information. |
202 var mangledName = '${prefix}_$id'; | 125 var inputUrl = libraryInfo is FileInfo |
203 if (_debugCss) { | 126 ? libraryInfo.inputUrl |
204 print(" id = ${node.attributes['id'].toString()} => $mangledName"); | 127 : (libraryInfo as ComponentInfo).declaringFile.inputUrl; |
| 128 |
| 129 for (var styleSheet in libraryInfo.styleSheets) { |
| 130 if (!seen.contains(styleSheet)) { |
| 131 // TODO(terry): VM uses expandos to implement hashes. Currently, it's a |
| 132 // linear (not constant) time cost (see dartbug.com/5746). |
| 133 // If this bug isn't fixed and performance show's this a |
| 134 // a problem we'll need to implement our own hashCode or |
| 135 // use a different key for better perf. |
| 136 // Add the stylesheet. |
| 137 seen.add(styleSheet); |
| 138 |
| 139 // Any other imports in this stylesheet? |
| 140 var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot, |
| 141 inputUrl, _messages); |
| 142 |
| 143 // Process other imports in this stylesheets. |
| 144 for (var importSS in urlInfos) { |
| 145 var importInfo = info[importSS.resolvedPath]; |
| 146 if (importInfo != null) { |
| 147 // Add all known stylesheets processed. |
| 148 seen.addAll(importInfo.styleSheets); |
| 149 // Find dependencies for stylesheet referenced with a |
| 150 // @import |
| 151 for (var ss in importInfo.styleSheets) { |
| 152 var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl, |
| 153 _messages); |
| 154 for (var url in urls) { |
| 155 _dependencies(info[url.resolvedPath], seen: seen); |
| 156 } |
| 157 } |
| 158 } |
205 } | 159 } |
206 node.attributes['id'] = mangledName; | |
207 } | 160 } |
208 } | 161 } |
| 162 |
| 163 return seen; |
209 } | 164 } |
210 } | 165 } |
211 | 166 |
212 | |
213 /** | 167 /** |
214 * Find var- definitions in a style sheet. | 168 * Find var- definitions in a style sheet. |
215 * [found] list of known definitions. | 169 * [found] list of known definitions. |
216 */ | 170 */ |
217 class VarDefinitions extends Visitor { | 171 class _VarDefinitions extends Visitor { |
218 final Map<String, VarDefinition> found = new Map(); | 172 final Map<String, VarDefinition> found = new Map(); |
219 | 173 |
220 void visitTree(StyleSheet tree) { | 174 void visitTree(StyleSheet tree) { |
221 visitStyleSheet(tree); | 175 visitStyleSheet(tree); |
222 } | 176 } |
223 | 177 |
224 visitVarDefinition(VarDefinition node) { | 178 visitVarDefinition(VarDefinition node) { |
225 // Replace with latest variable definition. | 179 // Replace with latest variable definition. |
226 found[node.definedName] = node; | 180 found[node.definedName] = node; |
227 super.visitVarDefinition(node); | 181 super.visitVarDefinition(node); |
(...skipping 10 matching lines...) Expand all Loading... |
238 * | 192 * |
239 * var-one: var(two); | 193 * var-one: var(two); |
240 * var-two: #ff00ff; | 194 * var-two: #ff00ff; |
241 * | 195 * |
242 * .test { | 196 * .test { |
243 * color: var(one); | 197 * color: var(one); |
244 * } | 198 * } |
245 * | 199 * |
246 * then .test's color would be #ff00ff | 200 * then .test's color would be #ff00ff |
247 */ | 201 */ |
248 class ResolveVarUsages extends Visitor { | 202 class _ResolveVarUsages extends Visitor { |
249 final Map<String, VarDefinition> varDefs; | 203 final Map<String, VarDefinition> varDefs; |
250 bool inVarDefinition = false; | 204 bool inVarDefinition = false; |
251 bool inUsage = false; | 205 bool inUsage = false; |
252 Expressions currentExpressions; | 206 Expressions currentExpressions; |
253 | 207 |
254 ResolveVarUsages(this.varDefs); | 208 _ResolveVarUsages(this.varDefs); |
255 | 209 |
256 void visitTree(StyleSheet tree) { | 210 void visitTree(StyleSheet tree) { |
257 visitStyleSheet(tree); | 211 visitStyleSheet(tree); |
258 } | 212 } |
259 | 213 |
260 void visitVarDefinition(VarDefinition varDef) { | 214 void visitVarDefinition(VarDefinition varDef) { |
261 inVarDefinition = true; | 215 inVarDefinition = true; |
262 super.visitVarDefinition(varDef); | 216 super.visitVarDefinition(varDef); |
263 inVarDefinition = false; | 217 inVarDefinition = false; |
264 } | 218 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
330 } | 284 } |
331 | 285 |
332 _resolveVarUsage(List<Expressions> expressions, int index, | 286 _resolveVarUsage(List<Expressions> expressions, int index, |
333 VarDefinition def) { | 287 VarDefinition def) { |
334 var defExpressions = (def.expression as Expressions).expressions; | 288 var defExpressions = (def.expression as Expressions).expressions; |
335 expressions.replaceRange(index, index + 1, defExpressions); | 289 expressions.replaceRange(index, index + 1, defExpressions); |
336 } | 290 } |
337 } | 291 } |
338 | 292 |
339 /** Remove all var definitions. */ | 293 /** Remove all var definitions. */ |
340 class RemoveVarDefinitions extends Visitor { | 294 class _RemoveVarDefinitions extends Visitor { |
341 void visitTree(StyleSheet tree) { | 295 void visitTree(StyleSheet tree) { |
342 visitStyleSheet(tree); | 296 visitStyleSheet(tree); |
343 } | 297 } |
344 | 298 |
345 void visitStyleSheet(StyleSheet ss) { | 299 void visitStyleSheet(StyleSheet ss) { |
346 ss.topLevels.removeWhere((e) => e is VarDefinitionDirective); | 300 ss.topLevels.removeWhere((e) => e is VarDefinitionDirective); |
347 super.visitStyleSheet(ss); | 301 super.visitStyleSheet(ss); |
348 } | 302 } |
349 | 303 |
350 void visitDeclarationGroup(DeclarationGroup node) { | 304 void visitDeclarationGroup(DeclarationGroup node) { |
(...skipping 13 matching lines...) Expand all Loading... |
364 * | 318 * |
365 * Change the custom pseudo-element to be a child of the pseudo attribute's | 319 * Change the custom pseudo-element to be a child of the pseudo attribute's |
366 * mangled custom pseudo element name. e.g, | 320 * mangled custom pseudo element name. e.g, |
367 * | 321 * |
368 * .test::x-box | 322 * .test::x-box |
369 * | 323 * |
370 * would become: | 324 * would become: |
371 * | 325 * |
372 * .test > *[pseudo="x-box_2"] | 326 * .test > *[pseudo="x-box_2"] |
373 */ | 327 */ |
374 class PseudoElementExpander extends Visitor { | 328 class _PseudoElementExpander extends Visitor { |
375 final Map<String, String> _pseudoElements; | 329 final Map<String, String> _pseudoElements; |
376 | 330 |
377 PseudoElementExpander(this._pseudoElements); | 331 _PseudoElementExpander(this._pseudoElements); |
378 | 332 |
379 void visitTree(StyleSheet tree) => visitStyleSheet(tree); | 333 void visitTree(StyleSheet tree) => visitStyleSheet(tree); |
380 | 334 |
381 visitSelector(Selector node) { | 335 visitSelector(Selector node) { |
382 var selectors = node.simpleSelectorSequences; | 336 var selectors = node.simpleSelectorSequences; |
383 for (var index = 0; index < selectors.length; index++) { | 337 for (var index = 0; index < selectors.length; index++) { |
384 var selector = selectors[index].simpleSelector; | 338 var selector = selectors[index].simpleSelector; |
385 if (selector is PseudoElementSelector) { | 339 if (selector is PseudoElementSelector) { |
386 if (_pseudoElements.containsKey(selector.name)) { | 340 if (_pseudoElements.containsKey(selector.name)) { |
387 // Pseudo Element is a custom element. | 341 // Pseudo Element is a custom element. |
388 var mangledName = _pseudoElements[selector.name]; | 342 var mangledName = _pseudoElements[selector.name]; |
389 | 343 |
390 var span = selectors[index].span; | 344 var span = selectors[index].span; |
391 | 345 |
392 var attrSelector = new AttributeSelector( | 346 var attrSelector = new AttributeSelector( |
393 new Identifier('pseudo', span), css.TokenKind.EQUALS, | 347 new Identifier('pseudo', span), css.TokenKind.EQUALS, |
394 mangledName, span); | 348 mangledName, span); |
395 // The wildcard * namespace selector. | 349 // The wildcard * namespace selector. |
396 var wildCard = new ElementSelector(new Wildcard(span), span); | 350 var wildCard = new ElementSelector(new Wildcard(span), span); |
397 selectors[index] = new SimpleSelectorSequence(wildCard, span, | 351 selectors[index] = new SimpleSelectorSequence(wildCard, span, |
398 css.TokenKind.COMBINATOR_GREATER); | 352 css.TokenKind.COMBINATOR_GREATER); |
399 selectors.insert(++index, | 353 selectors.insert(++index, |
400 new SimpleSelectorSequence(attrSelector, span)); | 354 new SimpleSelectorSequence(attrSelector, span)); |
401 } | 355 } |
402 } | 356 } |
403 } | 357 } |
404 } | 358 } |
405 } | 359 } |
406 | 360 |
407 /** Compute each CSS URI resource relative from the generated CSS file. */ | |
408 class UriVisitor extends Visitor { | |
409 /** | |
410 * Relative path from the output css file to the location of the original | |
411 * css file that contained the URI to each resource. | |
412 */ | |
413 final String _pathToOriginalCss; | |
414 | |
415 factory UriVisitor(PathMapper pathMapper, String cssPath, bool rewriteUrl) { | |
416 var cssDir = path.dirname(cssPath); | |
417 var outCssDir = rewriteUrl ? pathMapper.outputDirPath(cssPath) | |
418 : path.dirname(cssPath); | |
419 return new UriVisitor._internal(path.relative(cssDir, from: outCssDir)); | |
420 } | |
421 | |
422 UriVisitor._internal(this._pathToOriginalCss); | |
423 | |
424 void visitUriTerm(UriTerm node) { | |
425 // Don't touch URIs that have any scheme (http, etc.). | |
426 var uri = Uri.parse(node.text); | |
427 if (uri.host != '') return; | |
428 if (uri.scheme != '' && uri.scheme != 'package') return; | |
429 | |
430 node.text = pathToUrl( | |
431 path.normalize(path.join(_pathToOriginalCss, node.text))); | |
432 } | |
433 } | |
434 | |
435 List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet, | 361 List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet, |
436 String packageRoot, UrlInfo inputUrl, Messages messages) { | 362 String packageRoot, UrlInfo inputUrl, Messages messages) { |
437 var visitor = new CssImports(packageRoot, inputUrl, messages); | 363 var visitor = new _CssImports(packageRoot, inputUrl, messages); |
438 visitor.visitTree(styleSheet); | 364 visitor.visitTree(styleSheet); |
439 return visitor.urlInfos; | 365 return visitor.urlInfos; |
440 } | 366 } |
441 | 367 |
442 /** | 368 /** |
443 * Find any imports in the style sheet; normalize the style sheet href and | 369 * Find any imports in the style sheet; normalize the style sheet href and |
444 * return a list of all fully qualified CSS files. | 370 * return a list of all fully qualified CSS files. |
445 */ | 371 */ |
446 class CssImports extends Visitor { | 372 class _CssImports extends Visitor { |
447 final String packageRoot; | 373 final String packageRoot; |
448 | 374 |
449 /** Input url of the css file, used to normalize relative import urls. */ | 375 /** Input url of the css file, used to normalize relative import urls. */ |
450 final UrlInfo inputUrl; | 376 final UrlInfo inputUrl; |
451 | 377 |
452 /** List of all imported style sheets. */ | 378 /** List of all imported style sheets. */ |
453 final List<UrlInfo> urlInfos = []; | 379 final List<UrlInfo> urlInfos = []; |
454 | 380 |
455 final Messages _messages; | 381 final Messages _messages; |
456 | 382 |
457 CssImports(this.packageRoot, this.inputUrl, this._messages); | 383 _CssImports(this.packageRoot, this.inputUrl, this._messages); |
458 | 384 |
459 void visitTree(StyleSheet tree) { | 385 void visitTree(StyleSheet tree) { |
460 visitStyleSheet(tree); | 386 visitStyleSheet(tree); |
461 } | 387 } |
462 | 388 |
463 void visitImportDirective(ImportDirective node) { | 389 void visitImportDirective(ImportDirective node) { |
464 var urlInfo = UrlInfo.resolve(node.import, inputUrl, | 390 var urlInfo = UrlInfo.resolve(node.import, inputUrl, |
465 node.span, packageRoot, _messages, ignoreAbsolute: true); | 391 node.span, packageRoot, _messages, ignoreAbsolute: true); |
466 if (urlInfo == null) return; | 392 if (urlInfo == null) return; |
467 urlInfos.add(urlInfo); | 393 urlInfos.add(urlInfo); |
(...skipping 13 matching lines...) Expand all Loading... |
481 // Note: errors aren't fatal in HTML (unless strict mode is on). | 407 // Note: errors aren't fatal in HTML (unless strict mode is on). |
482 // So just print them as warnings. | 408 // So just print them as warnings. |
483 for (var e in errors) { | 409 for (var e in errors) { |
484 messages.warning(e.message, e.span); | 410 messages.warning(e.message, e.span); |
485 } | 411 } |
486 | 412 |
487 return stylesheet; | 413 return stylesheet; |
488 } | 414 } |
489 | 415 |
490 /** Find terminal definition (non VarUsage implies real CSS value). */ | 416 /** Find terminal definition (non VarUsage implies real CSS value). */ |
491 VarDefinition findTerminalVarDefinition(Map<String, VarDefinition> varDefs, | 417 VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs, |
492 VarDefinition varDef) { | 418 VarDefinition varDef) { |
493 var expressions = varDef.expression as Expressions; | 419 var expressions = varDef.expression as Expressions; |
494 for (var expr in expressions.expressions) { | 420 for (var expr in expressions.expressions) { |
495 if (expr is VarUsage) { | 421 if (expr is VarUsage) { |
496 var usageName = (expr as VarUsage).name; | 422 var usageName = (expr as VarUsage).name; |
497 var foundDef = varDefs[usageName]; | 423 var foundDef = varDefs[usageName]; |
498 | 424 |
499 // If foundDef is unknown check if defaultValues; if it exist then resolve | 425 // If foundDef is unknown check if defaultValues; if it exist then resolve |
500 // to terminal value. | 426 // to terminal value. |
501 if (foundDef == null) { | 427 if (foundDef == null) { |
502 // We're either a VarUsage or terminal definition if in varDefs; | 428 // We're either a VarUsage or terminal definition if in varDefs; |
503 // either way replace VarUsage with it's default value because the | 429 // either way replace VarUsage with it's default value because the |
504 // VarDefinition isn't found. | 430 // VarDefinition isn't found. |
505 var defaultValues = (expr as VarUsage).defaultValues; | 431 var defaultValues = (expr as VarUsage).defaultValues; |
506 var replaceExprs = expressions.expressions; | 432 var replaceExprs = expressions.expressions; |
507 assert(replaceExprs.length == 1); | 433 assert(replaceExprs.length == 1); |
508 replaceExprs.replaceRange(0, 1, defaultValues); | 434 replaceExprs.replaceRange(0, 1, defaultValues); |
509 return varDef; | 435 return varDef; |
510 } | 436 } |
511 if (foundDef is VarDefinition) { | 437 if (foundDef is VarDefinition) { |
512 return findTerminalVarDefinition(varDefs, foundDef); | 438 return _findTerminalVarDefinition(varDefs, foundDef); |
513 } | 439 } |
514 } else { | 440 } else { |
515 // Return real CSS property. | 441 // Return real CSS property. |
516 return varDef; | 442 return varDef; |
517 } | 443 } |
518 } | 444 } |
519 | 445 |
520 // Didn't point to a var definition that existed. | 446 // Didn't point to a var definition that existed. |
521 return varDef; | 447 return varDef; |
522 } | 448 } |
523 | 449 |
524 /** | 450 /** |
525 * Find urls imported inside style tags under [info]. If [info] is a FileInfo | 451 * Find urls imported inside style tags under [info]. If [info] is a FileInfo |
526 * then process only style tags in the body (don't process any style tags in a | 452 * then process only style tags in the body (don't process any style tags in a |
527 * component). If [info] is a ComponentInfo only process style tags inside of | 453 * component). If [info] is a ComponentInfo only process style tags inside of |
528 * the element are processed. For an [info] of type FileInfo [node] is the | 454 * the element are processed. For an [info] of type FileInfo [node] is the |
529 * file's document and for an [info] of type ComponentInfo then [node] is the | 455 * file's document and for an [info] of type ComponentInfo then [node] is the |
530 * component's element tag. | 456 * component's element tag. |
531 */ | 457 */ |
532 List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl, | 458 List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl, |
533 String packageRoot, Node node, Messages messages, CompilerOptions options) { | 459 String packageRoot, Node node, Messages messages, CompilerOptions options) { |
534 // Process any @imports inside of the <style> tag. | 460 // Process any @imports inside of the <style> tag. |
535 var styleProcessor = | 461 var styleProcessor = |
536 new CssStyleTag(packageRoot, info, inputUrl, messages, options); | 462 new _CssStyleTag(packageRoot, info, inputUrl, messages, options); |
537 styleProcessor.visit(node); | 463 styleProcessor.visit(node); |
538 return styleProcessor.imports; | 464 return styleProcessor.imports; |
539 } | 465 } |
540 | 466 |
541 /* Process CSS inside of a style tag. */ | 467 /* Process CSS inside of a style tag. */ |
542 class CssStyleTag extends TreeVisitor { | 468 class _CssStyleTag extends TreeVisitor { |
543 final String _packageRoot; | 469 final String _packageRoot; |
544 | 470 |
545 /** Either a FileInfo or ComponentInfo. */ | 471 /** Either a FileInfo or ComponentInfo. */ |
546 final LibraryInfo _info; | 472 final LibraryInfo _info; |
547 final Messages _messages; | 473 final Messages _messages; |
548 final CompilerOptions _options; | 474 final CompilerOptions _options; |
549 | 475 |
550 /** | 476 /** |
551 * Path of the declaring file, for a [_info] of type FileInfo it's the file's | 477 * Path of the declaring file, for a [_info] of type FileInfo it's the file's |
552 * path for a type ComponentInfo it's the declaring file path. | 478 * path for a type ComponentInfo it's the declaring file path. |
553 */ | 479 */ |
554 final UrlInfo _inputUrl; | 480 final UrlInfo _inputUrl; |
555 | 481 |
556 /** List of @imports found. */ | 482 /** List of @imports found. */ |
557 List<UrlInfo> imports = []; | 483 List<UrlInfo> imports = []; |
558 | 484 |
559 CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages, | 485 _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages, |
560 this._options); | 486 this._options); |
561 | 487 |
562 void visitElement(Element node) { | 488 void visitElement(Element node) { |
563 // Don't process any style tags inside of element if we're processing a | 489 // Don't process any style tags inside of element if we're processing a |
564 // FileInfo. The style tags inside of a component defintion will be | 490 // FileInfo. The style tags inside of a component defintion will be |
565 // processed when _info is a ComponentInfo. | 491 // processed when _info is a ComponentInfo. |
566 if (node.tagName == 'polymer-element' && _info is FileInfo) return; | 492 if (node.tagName == 'polymer-element' && _info is FileInfo) return; |
567 if (node.tagName == 'style') { | 493 if (node.tagName == 'style') { |
568 // Parse the contents of the scoped style tag. | 494 // Parse the contents of the scoped style tag. |
569 var styleSheet = parseCss(node.nodes.single.value, _messages, _options); | 495 var styleSheet = parseCss(node.nodes.single.value, _messages, _options); |
570 if (styleSheet != null) { | 496 if (styleSheet != null) { |
571 _info.styleSheets.add(styleSheet); | 497 _info.styleSheets.add(styleSheet); |
572 | 498 |
573 // TODO(terry): Check on scoped attribute there's a rumor that styles | |
574 // might always be scoped in a component. | |
575 // TODO(terry): May need to handle multiple style tags some with scoped | |
576 // and some without for now first style tag determines how | |
577 // CSS is emitted. | |
578 if (node.attributes.containsKey('scoped') && _info is ComponentInfo) { | |
579 (_info as ComponentInfo).scoped = true; | |
580 } | |
581 | |
582 // Find all imports return list of @imports in this style tag. | 499 // Find all imports return list of @imports in this style tag. |
583 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot, | 500 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot, |
584 _inputUrl, _messages); | 501 _inputUrl, _messages); |
585 imports.addAll(urlInfos); | 502 imports.addAll(urlInfos); |
586 } | 503 } |
587 } | 504 } |
588 super.visitElement(node); | 505 super.visitElement(node); |
589 } | 506 } |
590 } | 507 } |
OLD | NEW |