Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
factor out Loader.
Browse files Browse the repository at this point in the history
  • Loading branch information
sorvell committed Jan 16, 2014
1 parent c13e8ea commit 7a3ef92
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 176 deletions.
1 change: 1 addition & 0 deletions html-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var scopeName = 'HTMLImports';
var modules = [
'src/scope.js',
'src/path.js',
'src/Loader.js',
'src/HTMLImports.js',
'src/Parser.js',
'src/boot.js'
Expand Down
212 changes: 37 additions & 175 deletions src/HTMLImports.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ if (!useNative) {

// imports
var path = scope.path;
var Loader = scope.Loader;
var xhr = scope.xhr;

// importer
Expand All @@ -33,7 +34,7 @@ if (!useNative) {
// 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 importLoader;
var IMPORT_LINK_TYPE = 'import';
var STYLE_LINK_TYPE = 'stylesheet';

Expand All @@ -47,30 +48,30 @@ if (!useNative) {
'script[src][type="text/javascript"]'
].join(','),
loader: function(next) {
if (loader && loader.inflight) {
var currentComplete = loader.oncomplete;
loader.oncomplete = function() {
if (importLoader && importLoader.inflight) {
var currentComplete = importLoader.oncomplete;
importLoader.oncomplete = function() {
currentComplete();
next();
}
return loader;
return importLoader;
}
// construct a loader instance
loader = new Loader(importer.loaded, next);
importLoader = new Loader(importer.loaded, next);
// alias the importer cache (for debugging)
loader.cache = importer.cache;
return loader;
importLoader.cache = importer.cache;
return importLoader;
},
load: function(doc, next) {
// get a loader instance from the factory
loader = importer.loader(next);
importLoader = importer.loader(next);
// add nodes from document into loader queue
importer.preload(doc);
},
preload: function(doc) {
var nodes = this.marshalNodes(doc);
// add these nodes to loader's queue
loader.addNodes(nodes);
importLoader.addNodes(nodes);
},
marshalNodes: function(doc) {
// all preloadable nodes in inDocument
Expand Down Expand Up @@ -188,178 +189,13 @@ if (!useNative) {
return doc;
}

var Loader = function(onLoad, onComplete) {
this.onload = onLoad;
this.oncomplete = onComplete;
this.inflight = 0;
this.pending = {};
this.cache = {};
};

Loader.prototype = {
addNodes: function(nodes) {
// number of transactions to complete
this.inflight += nodes.length;
// commence transactions
forEach(nodes, this.require, this);
// anything to do?
this.checkDone();
},
require: function(elt) {
var url = path.nodeUrl(elt);
// ensure we have a standard url that can be used
// reliably for deduping.
url = path.makeAbsUrl(url);
// TODO(sjmiles): ad-hoc
elt.__nodeUrl = url;
// deduplication
if (!this.dedupe(url, elt)) {
// fetch this resource
this.fetch(url, elt);
}
},
dedupe: function(url, elt) {
if (this.pending[url]) {
// add to list of nodes waiting for inUrl
this.pending[url].push(elt);
// don't need fetch
return true;
}
if (this.cache[url]) {
// complete load using cache data
this.onload(url, elt, loader.cache[url]);
// finished this transaction
this.tail();
// don't need fetch
return true;
}
// first node waiting for inUrl
this.pending[url] = [elt];
// 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(url, elt, err, resource) {
if (!err) {
loader.cache[url] = resource;
}
loader.pending[url].forEach(function(e) {
if (!err) {
this.onload(url, e, resource);
}
this.tail();
}, this);
loader.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) {
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 || request.responseText, 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.xhr = xhr;
scope.importer = importer;
scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
} else {
// do nothing if using native imports
/*
// TODO(sorvell): this exists as a load extension point.
importer.preloadSelectors = [
'template'
].join(',');
function forEachImport(imp, cb) {
if (cb) {
cb(imp);
}
var n$ = imp.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
if (n.import) {
forEachImport(n.import, cb);
} else {
console.warn('import not loaded', n);
}
}
};
var marshalNodes = importer.marshalNodes;
function preloadImport(imp) {
nodes = marshalNodes.call(this, imp);
var url = path.documentUrlFromNode(imp);
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
path.resolveNodeAttributes(n, url);
}
return nodes;
};
// marshal all relevant nodes in import tree.
importer.marshalNodes = function(doc) {
var nodes = [], self = this;
forEachImport(doc, function(imp) {
// only preload 1x per import.
if (!imp._preloaded) {
imp._preloaded = true;
var n$ = preloadImport.call(self, imp);
nodes = nodes.concat(Array.prototype.slice.call(n$, 0));
}
});
return nodes;
}
*/
}

// NOTE: We cannot polyfill document.currentScript because it's not possible
Expand All @@ -374,8 +210,34 @@ Object.defineProperty(document, '_currentScript', {
configurable: true
});

function whenImportsReady(callback, doc) {
doc = doc || document;
if (doc.readyState === 'loading') {
doc.addEventListener('DOMContentLoaded', function() {
whenImportsReady(callback, doc);
})
return;
}
var imports = doc.querySelectorAll('link[rel=import');
var loaded = 0, l = imports.length;
function check() {
loaded++;
if (loaded == l) {
callback && callback();
}
}
for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
if (imp.import) {
check();
} else {
imp.addEventListener('load', check);
}
}
}

// exports
scope.hasNative = hasNative;
scope.useNative = useNative;
scope.whenImportsReady = whenImportsReady;

})(window.HTMLImports);
Loading

0 comments on commit 7a3ef92

Please sign in to comment.