/* * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ (function(scope) { var endOfMicrotask = Polymer.endOfMicrotask; // Generic url loader function Loader(regex) { this.cache = Object.create(null); this.map = Object.create(null); this.requests = 0; this.regex = regex; } Loader.prototype = { // TODO(dfreedm): there may be a better factoring here // extract absolute urls from the text (full of relative urls) extractUrls: function(text, base) { var matches = []; var matched, u; while ((matched = this.regex.exec(text))) { u = new URL(matched[1], base); matches.push({matched: matched[0], url: u.href}); } return matches; }, // take a text blob, a root url, and a callback and load all the urls found within the text // returns a map of absolute url to text process: function(text, root, callback) { var matches = this.extractUrls(text, root); // every call to process returns all the text this loader has ever received var done = callback.bind(null, this.map); this.fetch(matches, done); }, // build a mapping of url -> text from matches fetch: function(matches, callback) { var inflight = matches.length; // return early if there is no fetching to be done if (!inflight) { return callback(); } // wait for all subrequests to return var done = function() { if (--inflight === 0) { callback(); } }; // start fetching all subrequests var m, req, url; for (var i = 0; i < inflight; i++) { m = matches[i]; url = m.url; req = this.cache[url]; // if this url has already been requested, skip requesting it again if (!req) { req = this.xhr(url); req.match = m; this.cache[url] = req; } // wait for the request to process its subrequests req.wait(done); } }, handleXhr: function(request) { var match = request.match; var url = match.url; // handle errors with an empty string var response = request.response || request.responseText || ''; this.map[url] = response; this.fetch(this.extractUrls(response, url), request.resolve); }, xhr: function(url) { this.requests++; var request = new XMLHttpRequest(); request.open('GET', url, true); request.send(); request.onerror = request.onload = this.handleXhr.bind(this, request); // queue of tasks to run after XHR returns request.pending = []; request.resolve = function() { var pending = request.pending; for(var i = 0; i < pending.length; i++) { pending[i](); } request.pending = null; }; // if we have already resolved, pending is null, async call the callback request.wait = function(fn) { if (request.pending) { request.pending.push(fn); } else { endOfMicrotask(fn); } }; return request; } }; scope.Loader = Loader; })(Polymer);