| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** Transfomer that inlines polymer-element definitions from html imports. */ | |
| 6 library polymer.src.transform.import_inliner; | |
| 7 | |
| 8 import 'dart:async'; | |
| 9 | |
| 10 import 'package:barback/barback.dart'; | |
| 11 import 'package:path/path.dart' as path; | |
| 12 import 'package:html5lib/dom.dart' show Document, Node, DocumentFragment; | |
| 13 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; | |
| 14 import 'common.dart'; | |
| 15 | |
| 16 /** Recursively inlines polymer-element definitions from html imports. */ | |
| 17 // TODO(sigmund): make sure we match semantics of html-imports for tags other | |
| 18 // than polymer-element (see dartbug.com/12613). | |
| 19 class ImportedElementInliner extends Transformer with PolymerTransformer { | |
| 20 final TransformOptions options; | |
| 21 | |
| 22 ImportedElementInliner(this.options); | |
| 23 | |
| 24 /** Only run on entry point .html files. */ | |
| 25 Future<bool> isPrimary(Asset input) => | |
| 26 new Future.value(options.isHtmlEntryPoint(input.id)); | |
| 27 | |
| 28 Future apply(Transform transform) { | |
| 29 var seen = new Set<AssetId>(); | |
| 30 var elements = []; | |
| 31 var id = transform.primaryInput.id; | |
| 32 seen.add(id); | |
| 33 return readPrimaryAsHtml(transform).then((document) { | |
| 34 var future = _visitImports(document, id, transform, seen, elements); | |
| 35 return future.then((importsFound) { | |
| 36 if (!importsFound) { | |
| 37 transform.addOutput(transform.primaryInput); | |
| 38 return; | |
| 39 } | |
| 40 | |
| 41 for (var tag in document.queryAll('link')) { | |
| 42 if (tag.attributes['rel'] == 'import') { | |
| 43 tag.remove(); | |
| 44 } | |
| 45 } | |
| 46 var fragment = new DocumentFragment()..nodes.addAll(elements); | |
| 47 document.body.insertBefore(fragment, | |
| 48 //TODO(jmesserly): add Node.firstChild to html5lib | |
| 49 document.body.nodes.length == 0 ? null : document.body.nodes[0]); | |
| 50 transform.addOutput(new Asset.fromString(id, document.outerHtml)); | |
| 51 }); | |
| 52 }); | |
| 53 } | |
| 54 | |
| 55 /** | |
| 56 * Visits imports in [document] and add their polymer-element definitions to | |
| 57 * [elements], unless they have already been [seen]. Elements are added in the | |
| 58 * order they appear, transitive imports are added first. | |
| 59 */ | |
| 60 Future<bool> _visitImports(Document document, AssetId sourceId, | |
| 61 Transform transform, Set<AssetId> seen, List<Node> elements) { | |
| 62 var importIds = []; | |
| 63 bool hasImports = false; | |
| 64 for (var tag in document.queryAll('link')) { | |
| 65 if (tag.attributes['rel'] != 'import') continue; | |
| 66 var href = tag.attributes['href']; | |
| 67 var id = resolve(sourceId, href, transform.logger, tag.sourceSpan); | |
| 68 hasImports = true; | |
| 69 if (id == null || seen.contains(id)) continue; | |
| 70 importIds.add(id); | |
| 71 } | |
| 72 | |
| 73 if (importIds.isEmpty) return new Future.value(hasImports); | |
| 74 | |
| 75 // Note: we need to preserve the import order in the generated output. | |
| 76 return Future.forEach(importIds, (id) { | |
| 77 if (seen.contains(id)) return new Future.value(null); | |
| 78 seen.add(id); | |
| 79 return _collectPolymerElements(id, transform, seen, elements); | |
| 80 }).then((_) => true); | |
| 81 } | |
| 82 | |
| 83 /** | |
| 84 * Loads an asset identified by [id], visits its imports and collects it's | |
| 85 * polymer-element definitions. | |
| 86 */ | |
| 87 Future _collectPolymerElements(AssetId id, Transform transform, | |
| 88 Set<AssetId> seen, List elements) { | |
| 89 return readAsHtml(id, transform).then((document) { | |
| 90 return _visitImports(document, id, transform, seen, elements).then((_) { | |
| 91 var normalizer = new _UrlNormalizer(transform, id); | |
| 92 for (var element in document.queryAll('polymer-element')) { | |
| 93 normalizer.visit(document); | |
| 94 elements.add(element); | |
| 95 } | |
| 96 }); | |
| 97 }); | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 /** Internally adjusts urls in the html that we are about to inline. */ | |
| 102 class _UrlNormalizer extends TreeVisitor { | |
| 103 final Transform transform; | |
| 104 | |
| 105 /** Asset where the original content (and original url) was found. */ | |
| 106 final AssetId sourceId; | |
| 107 | |
| 108 _UrlNormalizer(this.transform, this.sourceId); | |
| 109 | |
| 110 visitElement(Element node) { | |
| 111 for (var key in node.attributes.keys) { | |
| 112 if (_urlAttributes.contains(key)) { | |
| 113 var url = node.attributes[key]; | |
| 114 if (url != null && url != '') { | |
| 115 node.attributes[key] = _newUrl(url, node.sourceSpan); | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 super.visitElement(node); | |
| 120 } | |
| 121 | |
| 122 _newUrl(String href, Span span) { | |
| 123 var uri = Uri.parse(href); | |
| 124 if (uri.isAbsolute) return href; | |
| 125 if (!uri.scheme.isEmpty) return href; | |
| 126 if (!uri.host.isEmpty) return href; | |
| 127 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. | |
| 128 if (path.isAbsolute(href)) return href; | |
| 129 | |
| 130 var id = resolve(sourceId, href, transform.logger, span); | |
| 131 var primaryId = transform.primaryInput.id; | |
| 132 | |
| 133 if (id.path.startsWith('lib/')) { | |
| 134 return 'packages/${id.package}/${id.path.substring(4)}'; | |
| 135 } | |
| 136 | |
| 137 if (id.path.startsWith('asset/')) { | |
| 138 return 'assets/${id.package}/${id.path.substring(6)}'; | |
| 139 } | |
| 140 | |
| 141 if (primaryId.package != id.package) { | |
| 142 // Techincally we shouldn't get there | |
| 143 logger.error("don't know how to include $id from $primaryId", span); | |
| 144 return null; | |
| 145 } | |
| 146 | |
| 147 var builder = path.url; | |
| 148 return builder.relative(builder.join('/', id.path), | |
| 149 from: builder.join('/', builder.dirname(primaryId.path))); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 /** | |
| 154 * HTML attributes that expect a URL value. | |
| 155 * <http://dev.w3.org/html5/spec/section-index.html#attributes-1> | |
| 156 * | |
| 157 * Every one of these attributes is a URL in every context where it is used in | |
| 158 * the DOM. The comments show every DOM element where an attribute can be used. | |
| 159 */ | |
| 160 const _urlAttributes = const [ | |
| 161 'action', // in form | |
| 162 'background', // in body | |
| 163 'cite', // in blockquote, del, ins, q | |
| 164 'data', // in object | |
| 165 'formaction', // in button, input | |
| 166 'href', // in a, area, link, base, command | |
| 167 'icon', // in command | |
| 168 'manifest', // in html | |
| 169 'poster', // in video | |
| 170 'src', // in audio, embed, iframe, img, input, script, source, track, | |
| 171 // video | |
| 172 ]; | |
| OLD | NEW |