| 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 |