Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(736)

Side by Side Diff: pkg/html_import/lib/src/HTMLImports.js

Issue 158083002: introduce web_components pkg for consolidated polyfills (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/html_import/lib/html_import.min.js ('k') | pkg/html_import/lib/src/Parser.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright 2013 The Polymer Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 */
6
7 (function(scope) {
8
9 if (!scope) {
10 scope = window.HTMLImports = {flags:{}};
11 }
12
13 // imports
14
15 var xhr = scope.xhr;
16
17 // importer
18
19 var IMPORT_LINK_TYPE = 'import';
20 var STYLE_LINK_TYPE = 'stylesheet';
21
22 // highlander object represents a primary document (the argument to 'load')
23 // at the root of a tree of documents
24
25 // for any document, importer:
26 // - loads any linked documents (with deduping), modifies paths and feeds them b ack into importer
27 // - loads text of external script tags
28 // - loads text of external style tags inside of <element>, modifies paths
29
30 // when importer 'modifies paths' in a document, this includes
31 // - href/src/action in node attributes
32 // - paths in inline stylesheets
33 // - all content inside templates
34
35 // linked style sheets in an import have their own path fixed up when their cont aining import modifies paths
36 // linked style sheets in an <element> are loaded, and the content gets path fix ups
37 // inline style sheets get path fixups when their containing import modifies pat hs
38
39 var loader;
40
41 var importer = {
42 documents: {},
43 cache: {},
44 preloadSelectors: [
45 'link[rel=' + IMPORT_LINK_TYPE + ']',
46 'element link[rel=' + STYLE_LINK_TYPE + ']',
47 'template',
48 'script[src]:not([type])',
49 'script[src][type="text/javascript"]'
50 ].join(','),
51 loader: function(inNext) {
52 // construct a loader instance
53 loader = new Loader(importer.loaded, inNext);
54 // alias the loader cache (for debugging)
55 loader.cache = importer.cache;
56 return loader;
57 },
58 load: function(inDocument, inNext) {
59 // construct a loader instance
60 loader = importer.loader(inNext);
61 // add nodes from document into loader queue
62 importer.preload(inDocument);
63 },
64 preload: function(inDocument) {
65 // all preloadable nodes in inDocument
66 var nodes = inDocument.querySelectorAll(importer.preloadSelectors);
67 // from the main document, only load imports
68 // TODO(sjmiles): do this by altering the selector list instead
69 nodes = this.filterMainDocumentNodes(inDocument, nodes);
70 // extra link nodes from templates, filter templates out of the nodes list
71 nodes = this.extractTemplateNodes(nodes);
72 // add these nodes to loader's queue
73 loader.addNodes(nodes);
74 },
75 filterMainDocumentNodes: function(inDocument, nodes) {
76 if (inDocument === document) {
77 nodes = Array.prototype.filter.call(nodes, function(n) {
78 return !isScript(n);
79 });
80 }
81 return nodes;
82 },
83 extractTemplateNodes: function(nodes) {
84 var extra = [];
85 nodes = Array.prototype.filter.call(nodes, function(n) {
86 if (n.localName === 'template') {
87 if (n.content) {
88 var l$ = n.content.querySelectorAll('link[rel=' + STYLE_LINK_TYPE +
89 ']');
90 if (l$.length) {
91 extra = extra.concat(Array.prototype.slice.call(l$, 0));
92 }
93 }
94 return false;
95 }
96 return true;
97 });
98 if (extra.length) {
99 nodes = nodes.concat(extra);
100 }
101 return nodes;
102 },
103 loaded: function(url, elt, resource) {
104 if (isDocumentLink(elt)) {
105 var document = importer.documents[url];
106 // if we've never seen a document at this url
107 if (!document) {
108 // generate an HTMLDocument from data
109 document = makeDocument(resource, url);
110 // resolve resource paths relative to host document
111 path.resolvePathsInHTML(document);
112 // cache document
113 importer.documents[url] = document;
114 // add nodes from this document to the loader queue
115 importer.preload(document);
116 }
117 // store import record
118 elt.import = {
119 href: url,
120 ownerNode: elt,
121 content: document
122 };
123 // store document resource
124 elt.content = resource = document;
125 }
126 // store generic resource
127 // TODO(sorvell): fails for nodes inside <template>.content
128 // see https://code.google.com/p/chromium/issues/detail?id=249381.
129 elt.__resource = resource;
130 // css path fixups
131 if (isStylesheetLink(elt)) {
132 path.resolvePathsInStylesheet(elt);
133 }
134 }
135 };
136
137 function isDocumentLink(elt) {
138 return isLinkRel(elt, IMPORT_LINK_TYPE);
139 }
140
141 function isStylesheetLink(elt) {
142 return isLinkRel(elt, STYLE_LINK_TYPE);
143 }
144
145 function isLinkRel(elt, rel) {
146 return elt.localName === 'link' && elt.getAttribute('rel') === rel;
147 }
148
149 function isScript(elt) {
150 return elt.localName === 'script';
151 }
152
153 function makeDocument(resource, url) {
154 // create a new HTML document
155 var doc = resource;
156 if (!(doc instanceof Document)) {
157 doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
158 // install html
159 doc.body.innerHTML = resource;
160 }
161 // cache the new document's source url
162 doc._URL = url;
163 // establish a relative path via <base>
164 var base = doc.createElement('base');
165 base.setAttribute('href', document.baseURI);
166 doc.head.appendChild(base);
167 // TODO(sorvell): MDV Polyfill intrusion: boostrap template polyfill
168 if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
169 HTMLTemplateElement.bootstrap(doc);
170 }
171 return doc;
172 }
173
174 var Loader = function(inOnLoad, inOnComplete) {
175 this.onload = inOnLoad;
176 this.oncomplete = inOnComplete;
177 this.inflight = 0;
178 this.pending = {};
179 this.cache = {};
180 };
181
182 Loader.prototype = {
183 addNodes: function(inNodes) {
184 // number of transactions to complete
185 this.inflight += inNodes.length;
186 // commence transactions
187 forEach(inNodes, this.require, this);
188 // anything to do?
189 this.checkDone();
190 },
191 require: function(inElt) {
192 var url = path.nodeUrl(inElt);
193 // TODO(sjmiles): ad-hoc
194 inElt.__nodeUrl = url;
195 // deduplication
196 if (!this.dedupe(url, inElt)) {
197 // fetch this resource
198 this.fetch(url, inElt);
199 }
200 },
201 dedupe: function(inUrl, inElt) {
202 if (this.pending[inUrl]) {
203 // add to list of nodes waiting for inUrl
204 this.pending[inUrl].push(inElt);
205 // don't need fetch
206 return true;
207 }
208 if (this.cache[inUrl]) {
209 // complete load using cache data
210 this.onload(inUrl, inElt, loader.cache[inUrl]);
211 // finished this transaction
212 this.tail();
213 // don't need fetch
214 return true;
215 }
216 // first node waiting for inUrl
217 this.pending[inUrl] = [inElt];
218 // need fetch (not a dupe)
219 return false;
220 },
221 fetch: function(url, elt) {
222 var receiveXhr = function(err, resource) {
223 this.receive(url, elt, err, resource);
224 }.bind(this);
225 xhr.load(url, receiveXhr);
226 // TODO(sorvell): blocked on
227 // https://code.google.com/p/chromium/issues/detail?id=257221
228 // xhr'ing for a document makes scripts in imports runnable; otherwise
229 // they are not; however, it requires that we have doctype=html in
230 // the import which is unacceptable. This is only needed on Chrome
231 // to avoid the bug above.
232 /*
233 if (isDocumentLink(elt)) {
234 xhr.loadDocument(url, receiveXhr);
235 } else {
236 xhr.load(url, receiveXhr);
237 }
238 */
239 },
240 receive: function(inUrl, inElt, inErr, inResource) {
241 if (!inErr) {
242 loader.cache[inUrl] = inResource;
243 }
244 loader.pending[inUrl].forEach(function(e) {
245 if (!inErr) {
246 this.onload(inUrl, e, inResource);
247 }
248 this.tail();
249 }, this);
250 loader.pending[inUrl] = null;
251 },
252 tail: function() {
253 --this.inflight;
254 this.checkDone();
255 },
256 checkDone: function() {
257 if (!this.inflight) {
258 this.oncomplete();
259 }
260 }
261 };
262
263 var URL_ATTRS = ['href', 'src', 'action'];
264 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
265 var URL_TEMPLATE_SEARCH = '{{.*}}';
266
267 var path = {
268 nodeUrl: function(inNode) {
269 return path.resolveUrl(path.getDocumentUrl(document), path.hrefOrSrc(inNode) );
270 },
271 hrefOrSrc: function(inNode) {
272 return inNode.getAttribute("href") || inNode.getAttribute("src");
273 },
274 documentUrlFromNode: function(inNode) {
275 return path.getDocumentUrl(inNode.ownerDocument || inNode);
276 },
277 getDocumentUrl: function(inDocument) {
278 var url = inDocument &&
279 // TODO(sjmiles): ShadowDOMPolyfill intrusion
280 (inDocument._URL || (inDocument.impl && inDocument.impl._URL)
281 || inDocument.baseURI || inDocument.URL)
282 || '';
283 // take only the left side if there is a #
284 return url.split('#')[0];
285 },
286 resolveUrl: function(inBaseUrl, inUrl, inRelativeToDocument) {
287 if (this.isAbsUrl(inUrl)) {
288 return inUrl;
289 }
290 var url = this.compressUrl(this.urlToPath(inBaseUrl) + inUrl);
291 if (inRelativeToDocument) {
292 url = path.makeRelPath(path.getDocumentUrl(document), url);
293 }
294 return url;
295 },
296 isAbsUrl: function(inUrl) {
297 return /(^data:)|(^http[s]?:)|(^\/)/.test(inUrl);
298 },
299 urlToPath: function(inBaseUrl) {
300 var parts = inBaseUrl.split("/");
301 parts.pop();
302 parts.push('');
303 return parts.join("/");
304 },
305 compressUrl: function(inUrl) {
306 var parts = inUrl.split("/");
307 for (var i=0, p; i<parts.length; i++) {
308 p = parts[i];
309 if (p === "..") {
310 parts.splice(i-1, 2);
311 i -= 2;
312 }
313 }
314 return parts.join("/");
315 },
316 // make a relative path from source to target
317 makeRelPath: function(inSource, inTarget) {
318 var s, t;
319 s = this.compressUrl(inSource).split("/");
320 t = this.compressUrl(inTarget).split("/");
321 while (s.length && s[0] === t[0]){
322 s.shift();
323 t.shift();
324 }
325 for(var i = 0, l = s.length-1; i < l; i++) {
326 t.unshift("..");
327 }
328 var r = t.join("/");
329 return r;
330 },
331 resolvePathsInHTML: function(root, url) {
332 url = url || path.documentUrlFromNode(root)
333 path.resolveAttributes(root, url);
334 path.resolveStyleElts(root, url);
335 // handle template.content
336 var templates = root.querySelectorAll('template');
337 if (templates) {
338 forEach(templates, function(t) {
339 if (t.content) {
340 path.resolvePathsInHTML(t.content, url);
341 }
342 });
343 }
344 },
345 resolvePathsInStylesheet: function(inSheet) {
346 var docUrl = path.nodeUrl(inSheet);
347 inSheet.__resource = path.resolveCssText(inSheet.__resource, docUrl);
348 },
349 resolveStyleElts: function(inRoot, inUrl) {
350 var styles = inRoot.querySelectorAll('style');
351 if (styles) {
352 forEach(styles, function(style) {
353 style.textContent = path.resolveCssText(style.textContent, inUrl);
354 });
355 }
356 },
357 resolveCssText: function(inCssText, inBaseUrl) {
358 return inCssText.replace(/url\([^)]*\)/g, function(inMatch) {
359 // find the url path, ignore quotes in url string
360 var urlPath = inMatch.replace(/["']/g, "").slice(4, -1);
361 urlPath = path.resolveUrl(inBaseUrl, urlPath, true);
362 return "url(" + urlPath + ")";
363 });
364 },
365 resolveAttributes: function(inRoot, inUrl) {
366 // search for attributes that host urls
367 var nodes = inRoot && inRoot.querySelectorAll(URL_ATTRS_SELECTOR);
368 if (nodes) {
369 forEach(nodes, function(n) {
370 this.resolveNodeAttributes(n, inUrl);
371 }, this);
372 }
373 },
374 resolveNodeAttributes: function(inNode, inUrl) {
375 URL_ATTRS.forEach(function(v) {
376 var attr = inNode.attributes[v];
377 if (attr && attr.value &&
378 (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) {
379 var urlPath = path.resolveUrl(inUrl, attr.value, true);
380 attr.value = urlPath;
381 }
382 });
383 }
384 };
385
386 xhr = xhr || {
387 async: true,
388 ok: function(inRequest) {
389 return (inRequest.status >= 200 && inRequest.status < 300)
390 || (inRequest.status === 304)
391 || (inRequest.status === 0);
392 },
393 load: function(url, next, nextContext) {
394 var request = new XMLHttpRequest();
395 if (scope.flags.debug || scope.flags.bust) {
396 url += '?' + Math.random();
397 }
398 request.open('GET', url, xhr.async);
399 request.addEventListener('readystatechange', function(e) {
400 if (request.readyState === 4) {
401 next.call(nextContext, !xhr.ok(request) && request,
402 request.response, url);
403 }
404 });
405 request.send();
406 return request;
407 },
408 loadDocument: function(url, next, nextContext) {
409 this.load(url, next, nextContext).responseType = 'document';
410 }
411 };
412
413 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
414
415 // exports
416
417 scope.path = path;
418 scope.xhr = xhr;
419 scope.importer = importer;
420 scope.getDocumentUrl = path.getDocumentUrl;
421 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
422
423 })(window.HTMLImports);
OLDNEW
« no previous file with comments | « pkg/html_import/lib/html_import.min.js ('k') | pkg/html_import/lib/src/Parser.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698