Index: runtime/bin/vmservice/observatory/deployed/web/packages/html_import/src/HTMLImports.js |
diff --git a/runtime/bin/vmservice/observatory/deployed/web/packages/html_import/src/HTMLImports.js b/runtime/bin/vmservice/observatory/deployed/web/packages/html_import/src/HTMLImports.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a77e7165e762204c44d3b7fa8cd4b8af8e325c26 |
--- /dev/null |
+++ b/runtime/bin/vmservice/observatory/deployed/web/packages/html_import/src/HTMLImports.js |
@@ -0,0 +1,423 @@ |
+/* |
+ * Copyright 2013 The Polymer Authors. All rights reserved. |
+ * Use of this source code is governed by a BSD-style |
+ * license that can be found in the LICENSE file. |
+ */ |
+ |
+(function(scope) { |
+ |
+if (!scope) { |
+ scope = window.HTMLImports = {flags:{}}; |
+} |
+ |
+// imports |
+ |
+var xhr = scope.xhr; |
+ |
+// importer |
+ |
+var IMPORT_LINK_TYPE = 'import'; |
+var STYLE_LINK_TYPE = 'stylesheet'; |
+ |
+// highlander object represents a primary document (the argument to 'load') |
+// at the root of a tree of documents |
+ |
+// for any document, importer: |
+// - loads any linked documents (with deduping), modifies paths and feeds them back into importer |
+// - loads text of external script tags |
+// - loads text of external style tags inside of <element>, modifies paths |
+ |
+// when importer 'modifies paths' in a document, this includes |
+// - href/src/action in node attributes |
+// - paths in inline stylesheets |
+// - all content inside templates |
+ |
+// linked style sheets in an import have their own path fixed up when their containing import modifies paths |
+// linked style sheets in an <element> are loaded, and the content gets path fixups |
+// inline style sheets get path fixups when their containing import modifies paths |
+ |
+var loader; |
+ |
+var importer = { |
+ documents: {}, |
+ cache: {}, |
+ preloadSelectors: [ |
+ 'link[rel=' + IMPORT_LINK_TYPE + ']', |
+ 'element link[rel=' + STYLE_LINK_TYPE + ']', |
+ 'template', |
+ 'script[src]:not([type])', |
+ 'script[src][type="text/javascript"]' |
+ ].join(','), |
+ loader: function(inNext) { |
+ // construct a loader instance |
+ loader = new Loader(importer.loaded, inNext); |
+ // alias the loader cache (for debugging) |
+ loader.cache = importer.cache; |
+ return loader; |
+ }, |
+ load: function(inDocument, inNext) { |
+ // construct a loader instance |
+ loader = importer.loader(inNext); |
+ // add nodes from document into loader queue |
+ importer.preload(inDocument); |
+ }, |
+ preload: function(inDocument) { |
+ // all preloadable nodes in inDocument |
+ var nodes = inDocument.querySelectorAll(importer.preloadSelectors); |
+ // from the main document, only load imports |
+ // TODO(sjmiles): do this by altering the selector list instead |
+ nodes = this.filterMainDocumentNodes(inDocument, nodes); |
+ // extra link nodes from templates, filter templates out of the nodes list |
+ nodes = this.extractTemplateNodes(nodes); |
+ // add these nodes to loader's queue |
+ loader.addNodes(nodes); |
+ }, |
+ filterMainDocumentNodes: function(inDocument, nodes) { |
+ if (inDocument === document) { |
+ nodes = Array.prototype.filter.call(nodes, function(n) { |
+ return !isScript(n); |
+ }); |
+ } |
+ return nodes; |
+ }, |
+ extractTemplateNodes: function(nodes) { |
+ var extra = []; |
+ nodes = Array.prototype.filter.call(nodes, function(n) { |
+ if (n.localName === 'template') { |
+ if (n.content) { |
+ var l$ = n.content.querySelectorAll('link[rel=' + STYLE_LINK_TYPE + |
+ ']'); |
+ if (l$.length) { |
+ extra = extra.concat(Array.prototype.slice.call(l$, 0)); |
+ } |
+ } |
+ return false; |
+ } |
+ return true; |
+ }); |
+ if (extra.length) { |
+ nodes = nodes.concat(extra); |
+ } |
+ return nodes; |
+ }, |
+ loaded: function(url, elt, resource) { |
+ if (isDocumentLink(elt)) { |
+ var document = importer.documents[url]; |
+ // if we've never seen a document at this url |
+ if (!document) { |
+ // generate an HTMLDocument from data |
+ document = makeDocument(resource, url); |
+ // resolve resource paths relative to host document |
+ path.resolvePathsInHTML(document); |
+ // cache document |
+ importer.documents[url] = document; |
+ // add nodes from this document to the loader queue |
+ importer.preload(document); |
+ } |
+ // store import record |
+ elt.import = { |
+ href: url, |
+ ownerNode: elt, |
+ content: document |
+ }; |
+ // store document resource |
+ elt.content = resource = document; |
+ } |
+ // store generic resource |
+ // TODO(sorvell): fails for nodes inside <template>.content |
+ // see https://code.google.com/p/chromium/issues/detail?id=249381. |
+ elt.__resource = resource; |
+ // css path fixups |
+ if (isStylesheetLink(elt)) { |
+ path.resolvePathsInStylesheet(elt); |
+ } |
+ } |
+}; |
+ |
+function isDocumentLink(elt) { |
+ return isLinkRel(elt, IMPORT_LINK_TYPE); |
+} |
+ |
+function isStylesheetLink(elt) { |
+ return isLinkRel(elt, STYLE_LINK_TYPE); |
+} |
+ |
+function isLinkRel(elt, rel) { |
+ return elt.localName === 'link' && elt.getAttribute('rel') === rel; |
+} |
+ |
+function isScript(elt) { |
+ return elt.localName === 'script'; |
+} |
+ |
+function makeDocument(resource, url) { |
+ // create a new HTML document |
+ var doc = resource; |
+ if (!(doc instanceof Document)) { |
+ doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); |
+ // install html |
+ doc.body.innerHTML = resource; |
+ } |
+ // cache the new document's source url |
+ doc._URL = url; |
+ // establish a relative path via <base> |
+ var base = doc.createElement('base'); |
+ base.setAttribute('href', document.baseURI); |
+ doc.head.appendChild(base); |
+ // TODO(sorvell): MDV Polyfill intrusion: boostrap template polyfill |
+ if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { |
+ HTMLTemplateElement.bootstrap(doc); |
+ } |
+ return doc; |
+} |
+ |
+var Loader = function(inOnLoad, inOnComplete) { |
+ this.onload = inOnLoad; |
+ this.oncomplete = inOnComplete; |
+ this.inflight = 0; |
+ this.pending = {}; |
+ this.cache = {}; |
+}; |
+ |
+Loader.prototype = { |
+ addNodes: function(inNodes) { |
+ // number of transactions to complete |
+ this.inflight += inNodes.length; |
+ // commence transactions |
+ forEach(inNodes, this.require, this); |
+ // anything to do? |
+ this.checkDone(); |
+ }, |
+ require: function(inElt) { |
+ var url = path.nodeUrl(inElt); |
+ // TODO(sjmiles): ad-hoc |
+ inElt.__nodeUrl = url; |
+ // deduplication |
+ if (!this.dedupe(url, inElt)) { |
+ // fetch this resource |
+ this.fetch(url, inElt); |
+ } |
+ }, |
+ dedupe: function(inUrl, inElt) { |
+ if (this.pending[inUrl]) { |
+ // add to list of nodes waiting for inUrl |
+ this.pending[inUrl].push(inElt); |
+ // don't need fetch |
+ return true; |
+ } |
+ if (this.cache[inUrl]) { |
+ // complete load using cache data |
+ this.onload(inUrl, inElt, loader.cache[inUrl]); |
+ // finished this transaction |
+ this.tail(); |
+ // don't need fetch |
+ return true; |
+ } |
+ // first node waiting for inUrl |
+ this.pending[inUrl] = [inElt]; |
+ // need fetch (not a dupe) |
+ return false; |
+ }, |
+ fetch: function(url, elt) { |
+ var receiveXhr = function(err, resource) { |
+ this.receive(url, elt, err, resource); |
+ }.bind(this); |
+ xhr.load(url, receiveXhr); |
+ // TODO(sorvell): blocked on |
+ // https://code.google.com/p/chromium/issues/detail?id=257221 |
+ // xhr'ing for a document makes scripts in imports runnable; otherwise |
+ // they are not; however, it requires that we have doctype=html in |
+ // the import which is unacceptable. This is only needed on Chrome |
+ // to avoid the bug above. |
+ /* |
+ if (isDocumentLink(elt)) { |
+ xhr.loadDocument(url, receiveXhr); |
+ } else { |
+ xhr.load(url, receiveXhr); |
+ } |
+ */ |
+ }, |
+ receive: function(inUrl, inElt, inErr, inResource) { |
+ if (!inErr) { |
+ loader.cache[inUrl] = inResource; |
+ } |
+ loader.pending[inUrl].forEach(function(e) { |
+ if (!inErr) { |
+ this.onload(inUrl, e, inResource); |
+ } |
+ this.tail(); |
+ }, this); |
+ loader.pending[inUrl] = null; |
+ }, |
+ tail: function() { |
+ --this.inflight; |
+ this.checkDone(); |
+ }, |
+ checkDone: function() { |
+ if (!this.inflight) { |
+ this.oncomplete(); |
+ } |
+ } |
+}; |
+ |
+var URL_ATTRS = ['href', 'src', 'action']; |
+var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
+var URL_TEMPLATE_SEARCH = '{{.*}}'; |
+ |
+var path = { |
+ nodeUrl: function(inNode) { |
+ return path.resolveUrl(path.getDocumentUrl(document), path.hrefOrSrc(inNode)); |
+ }, |
+ hrefOrSrc: function(inNode) { |
+ return inNode.getAttribute("href") || inNode.getAttribute("src"); |
+ }, |
+ documentUrlFromNode: function(inNode) { |
+ return path.getDocumentUrl(inNode.ownerDocument || inNode); |
+ }, |
+ getDocumentUrl: function(inDocument) { |
+ var url = inDocument && |
+ // TODO(sjmiles): ShadowDOMPolyfill intrusion |
+ (inDocument._URL || (inDocument.impl && inDocument.impl._URL) |
+ || inDocument.baseURI || inDocument.URL) |
+ || ''; |
+ // take only the left side if there is a # |
+ return url.split('#')[0]; |
+ }, |
+ resolveUrl: function(inBaseUrl, inUrl, inRelativeToDocument) { |
+ if (this.isAbsUrl(inUrl)) { |
+ return inUrl; |
+ } |
+ var url = this.compressUrl(this.urlToPath(inBaseUrl) + inUrl); |
+ if (inRelativeToDocument) { |
+ url = path.makeRelPath(path.getDocumentUrl(document), url); |
+ } |
+ return url; |
+ }, |
+ isAbsUrl: function(inUrl) { |
+ return /(^data:)|(^http[s]?:)|(^\/)/.test(inUrl); |
+ }, |
+ urlToPath: function(inBaseUrl) { |
+ var parts = inBaseUrl.split("/"); |
+ parts.pop(); |
+ parts.push(''); |
+ return parts.join("/"); |
+ }, |
+ compressUrl: function(inUrl) { |
+ var parts = inUrl.split("/"); |
+ for (var i=0, p; i<parts.length; i++) { |
+ p = parts[i]; |
+ if (p === "..") { |
+ parts.splice(i-1, 2); |
+ i -= 2; |
+ } |
+ } |
+ return parts.join("/"); |
+ }, |
+ // make a relative path from source to target |
+ makeRelPath: function(inSource, inTarget) { |
+ var s, t; |
+ s = this.compressUrl(inSource).split("/"); |
+ t = this.compressUrl(inTarget).split("/"); |
+ while (s.length && s[0] === t[0]){ |
+ s.shift(); |
+ t.shift(); |
+ } |
+ for(var i = 0, l = s.length-1; i < l; i++) { |
+ t.unshift(".."); |
+ } |
+ var r = t.join("/"); |
+ return r; |
+ }, |
+ resolvePathsInHTML: function(root, url) { |
+ url = url || path.documentUrlFromNode(root) |
+ path.resolveAttributes(root, url); |
+ path.resolveStyleElts(root, url); |
+ // handle template.content |
+ var templates = root.querySelectorAll('template'); |
+ if (templates) { |
+ forEach(templates, function(t) { |
+ if (t.content) { |
+ path.resolvePathsInHTML(t.content, url); |
+ } |
+ }); |
+ } |
+ }, |
+ resolvePathsInStylesheet: function(inSheet) { |
+ var docUrl = path.nodeUrl(inSheet); |
+ inSheet.__resource = path.resolveCssText(inSheet.__resource, docUrl); |
+ }, |
+ resolveStyleElts: function(inRoot, inUrl) { |
+ var styles = inRoot.querySelectorAll('style'); |
+ if (styles) { |
+ forEach(styles, function(style) { |
+ style.textContent = path.resolveCssText(style.textContent, inUrl); |
+ }); |
+ } |
+ }, |
+ resolveCssText: function(inCssText, inBaseUrl) { |
+ return inCssText.replace(/url\([^)]*\)/g, function(inMatch) { |
+ // find the url path, ignore quotes in url string |
+ var urlPath = inMatch.replace(/["']/g, "").slice(4, -1); |
+ urlPath = path.resolveUrl(inBaseUrl, urlPath, true); |
+ return "url(" + urlPath + ")"; |
+ }); |
+ }, |
+ resolveAttributes: function(inRoot, inUrl) { |
+ // search for attributes that host urls |
+ var nodes = inRoot && inRoot.querySelectorAll(URL_ATTRS_SELECTOR); |
+ if (nodes) { |
+ forEach(nodes, function(n) { |
+ this.resolveNodeAttributes(n, inUrl); |
+ }, this); |
+ } |
+ }, |
+ resolveNodeAttributes: function(inNode, inUrl) { |
+ URL_ATTRS.forEach(function(v) { |
+ var attr = inNode.attributes[v]; |
+ if (attr && attr.value && |
+ (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) { |
+ var urlPath = path.resolveUrl(inUrl, attr.value, true); |
+ attr.value = urlPath; |
+ } |
+ }); |
+ } |
+}; |
+ |
+xhr = xhr || { |
+ async: true, |
+ ok: function(inRequest) { |
+ return (inRequest.status >= 200 && inRequest.status < 300) |
+ || (inRequest.status === 304) |
+ || (inRequest.status === 0); |
+ }, |
+ load: function(url, next, nextContext) { |
+ var request = new XMLHttpRequest(); |
+ if (scope.flags.debug || scope.flags.bust) { |
+ url += '?' + Math.random(); |
+ } |
+ request.open('GET', url, xhr.async); |
+ request.addEventListener('readystatechange', function(e) { |
+ if (request.readyState === 4) { |
+ next.call(nextContext, !xhr.ok(request) && request, |
+ request.response, url); |
+ } |
+ }); |
+ request.send(); |
+ return request; |
+ }, |
+ loadDocument: function(url, next, nextContext) { |
+ this.load(url, next, nextContext).responseType = 'document'; |
+ } |
+}; |
+ |
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
+ |
+// exports |
+ |
+scope.path = path; |
+scope.xhr = xhr; |
+scope.importer = importer; |
+scope.getDocumentUrl = path.getDocumentUrl; |
+scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
+ |
+})(window.HTMLImports); |