diff --git a/src/HTMLImports.js b/src/HTMLImports.js index eba1478..4f4791c 100644 --- a/src/HTMLImports.js +++ b/src/HTMLImports.js @@ -30,32 +30,40 @@ if (!useNative) { // - loads any linked import documents (with deduping) var importer = { + documents: {}, + // nodes to load in the mian document documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', + // nodes to load in imports importsPreloadSelectors: [ 'link[rel=' + IMPORT_LINK_TYPE + ']' ].join(','), + loadNode: function(node) { importLoader.addNode(node); }, + // load all loadable elements within the parent element loadSubtree: function(parent) { var nodes = this.marshalNodes(parent); // add these nodes to loader's queue importLoader.addNodes(nodes); }, + marshalNodes: function(parent) { // all preloadable nodes in inDocument return parent.querySelectorAll(this.loadSelectorsForNode(parent)); }, + // find the proper set of load selectors for a given node loadSelectorsForNode: function(node) { var doc = node.ownerDocument || node; return doc === mainDoc ? this.documentPreloadSelectors : this.importsPreloadSelectors; }, + loaded: function(url, elt, resource, err, redirectedUrl) { flags.load && console.log('loaded', url, elt); // store generic resource @@ -84,14 +92,17 @@ if (!useNative) { } parser.parseNext(); }, + bootDocument: function(doc) { this.loadSubtree(doc); this.observe(doc); parser.parseNext(); }, + loadedAll: function() { parser.parseNext(); } + }; // loader singleton @@ -184,5 +195,4 @@ scope.importer = importer; scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; scope.importLoader = importLoader; - })(window.HTMLImports); diff --git a/src/Loader.js b/src/Loader.js index cc36bb1..0bc28f9 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -26,6 +26,7 @@ }; Loader.prototype = { + addNodes: function(nodes) { // number of transactions to complete this.inflight += nodes.length; @@ -36,6 +37,7 @@ // anything to do? this.checkDone(); }, + addNode: function(node) { // number of transactions to complete this.inflight++; @@ -44,6 +46,7 @@ // anything to do? this.checkDone(); }, + require: function(elt) { var url = elt.src || elt.href; // ensure we have a standard url that can be used @@ -56,6 +59,7 @@ this.fetch(url, elt); } }, + dedupe: function(url, elt) { if (this.pending[url]) { // add to list of nodes waiting for inUrl @@ -76,6 +80,7 @@ // need fetch (not a dupe) return false; }, + fetch: function(url, elt) { flags.load && console.log('fetch', url, elt); if (url.match(/^data:/)) { @@ -111,6 +116,7 @@ */ } }, + receive: function(url, elt, err, resource, redirectedUrl) { this.cache[url] = resource; var $p = this.pending[url]; @@ -122,24 +128,29 @@ } this.pending[url] = null; }, + tail: function() { --this.inflight; this.checkDone(); }, + checkDone: function() { if (!this.inflight) { this.oncomplete(); } } + }; xhr = xhr || { async: true, + ok: function(request) { return (request.status >= 200 && request.status < 300) || (request.status === 304) || (request.status === 0); }, + load: function(url, next, nextContext) { var request = new XMLHttpRequest(); if (scope.flags.debug || scope.flags.bust) { @@ -164,9 +175,11 @@ request.send(); return request; }, + loadDocument: function(url, next, nextContext) { this.load(url, next, nextContext).responseType = 'document'; } + }; // exports diff --git a/src/Parser.js b/src/Parser.js index 5e87bac..f6f9462 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -24,8 +24,10 @@ var mainDoc = window.ShadowDOMPolyfill ? // highlander object for parsing a document tree var importParser = { + // parse selectors for main document elements documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']', + // parse selectors for import document elements importsSelectors: [ 'link[rel=' + IMPORT_LINK_TYPE + ']', @@ -34,12 +36,15 @@ var importParser = { 'script:not([type])', 'script[type="text/javascript"]' ].join(','), + map: { link: 'parseLink', script: 'parseScript', style: 'parseStyle' }, + dynamicElements: [], + // try to parse the next import in the tree parseNext: function() { var next = this.nextToParse(); @@ -47,6 +52,7 @@ var importParser = { this.parse(next); } }, + parse: function(elt) { if (this.isParsed(elt)) { flags.parse && console.log('[%s] is already parsed', elt.localName); @@ -58,12 +64,14 @@ var importParser = { fn.call(this, elt); } }, + parseDynamic: function(elt, quiet) { this.dynamicElements.push(elt); if (!quiet) { this.parseNext(); } }, + // only 1 element may be parsed at a time; parsing is async so each // parsing implementation must inform the system that parsing is complete // via markParsingComplete. @@ -76,6 +84,7 @@ var importParser = { flags.parse && console.log('parsing', elt); this.parsingElement = elt; }, + markParsingComplete: function(elt) { elt.__importParsed = true; this.markDynamicParsingComplete(elt); @@ -86,12 +95,14 @@ var importParser = { this.parsingElement = null; flags.parse && console.log('completed', elt); }, + markDynamicParsingComplete: function(elt) { var i = this.dynamicElements.indexOf(elt); if (i >= 0) { this.dynamicElements.splice(i, 1); } }, + parseImport: function(elt) { // TODO(sorvell): consider if there's a better way to do this; // expose an imports parsing hook; this is needed, for example, by the @@ -122,6 +133,7 @@ var importParser = { } this.parseNext(); }, + parseLink: function(linkElt) { if (nodeIsImport(linkElt)) { this.parseImport(linkElt); @@ -131,6 +143,7 @@ var importParser = { this.parseGeneric(linkElt); } }, + parseStyle: function(elt) { // TODO(sorvell): style element load event can just not fire so clone styles var src = elt; @@ -138,10 +151,12 @@ var importParser = { elt.__importElement = src; this.parseGeneric(elt); }, + parseGeneric: function(elt) { this.trackElement(elt); this.addElementToDocument(elt); }, + rootImportForElement: function(elt) { var n = elt; while (n.ownerDocument.__importLink) { @@ -149,6 +164,7 @@ var importParser = { } return n; }, + addElementToDocument: function(elt) { var port = this.rootImportForElement(elt.__importElement || elt); var l = port.__insertedElements = port.__insertedElements || 0; @@ -158,6 +174,7 @@ var importParser = { } port.parentNode.insertBefore(elt, refNode); }, + // tracks when a loadable element has loaded trackElement: function(elt, callback) { var self = this; @@ -197,6 +214,7 @@ var importParser = { } } }, + // NOTE: execute scripts by injecting them and watching for the load/error // event. Inline scripts are handled via dataURL's because browsers tend to // provide correct parsing errors in this case. If this has any compatibility @@ -215,12 +233,14 @@ var importParser = { }); this.addElementToDocument(script); }, + // determine the next element in the tree which should be parsed nextToParse: function() { this._mayParse = []; return !this.parsingElement && (this.nextToParseInDoc(mainDoc) || this.nextToParseDynamic()); }, + nextToParseInDoc: function(doc, link) { // use `marParse` list to avoid looping into the same document again // since it could cause an iloop. @@ -240,26 +260,32 @@ var importParser = { // all nodes have been parsed, ready to parse import, if any return link; }, + nextToParseDynamic: function() { return this.dynamicElements[0]; }, + // return the set of parse selectors relevant for this node. parseSelectorsForNode: function(node) { var doc = node.ownerDocument || node; return doc === mainDoc ? this.documentSelectors : this.importsSelectors; }, + isParsed: function(node) { return node.__importParsed; }, + needsDynamicParsing: function(elt) { return (this.dynamicElements.indexOf(elt) >= 0); }, + hasResource: function(node) { if (nodeIsImport(node) && (node.import === undefined)) { return false; } return true; } + }; function nodeIsImport(elt) { @@ -309,17 +335,20 @@ var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; var path = { + resolveUrlsInStyle: function(style) { var doc = style.ownerDocument; var resolver = doc.createElement('a'); style.textContent = this.resolveUrlsInCssText(style.textContent, resolver); return style; }, + resolveUrlsInCssText: function(cssText, urlObj) { var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP); r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP); return r; }, + replaceUrls: function(text, urlObj, regexp) { return text.replace(regexp, function(m, pre, url, post) { var urlPath = url.replace(/["']/g, ''); @@ -328,7 +357,8 @@ var path = { return pre + '\'' + urlPath + '\'' + post; }); } -} + +}; // exports scope.parser = importParser;