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 |