Commit ba356131 by Cédric Le Ninivin

CribJS uses RenderJS and a loader

1 parent 69199231
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>Service worker demo</title>
<script src="lib/sha256.js"></script>
<script src="lib/sha256.amd.js"></script>
<script src="lib/rsvp.js"></script>
<script src="lib/jio-latest.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<h1>Free Web Initiative: Start your Website</h1>
<p>You can start building this website by editing the content of the input here:</p>
<ul>
<li>First field: the path</li>
<li>First field: mimetype</li>
<li>First field: the content</li>
</ul>
<p>Have fun building the web :) <a href="todo.html">TODO</a></p>
<div class="output">
<div id="status"></div>
<div id="commands" style="display: none">
<h3>Edit</h3>
<div>
<label for="url">Resource URL:</label>
<input id="url" type="text" size="50" value="test.html">
<button id="get">Get from Cache</button>
<input id="mimetype" type="text" size="50" value="text/html">
<div>
<textarea id="information" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="add">Add to Cache</button>
<button id="delete">Delete from Cache</button>
</div>
<h3>Save</h3>
<div>
<label for="save-path">Save:</label>
<input id="save-path" type="text" size="30" value="">
<label for="save-id"> to:</label>
<input id="save-id" type="text" size="30" value="cribjs">
<button id="save-contents">Save Cache</button>
</div>
<h3>Load</h3>
<div>
<label for="load-id">Load :</label>
<input id="load-id" type="text" size="30" value="cribjs">
<label for="load-path"> to path:</label>
<input id="load-path" type="text" size="30" value="cribeditor/v1.1">
<button id="load-contents">Load Cache</button>
</div>
<h3>Mass removal</h3>
<div>
<textarea id="mass-remove-list" cols="35" wrap="soft"></textarea>
</div>
<div>
<button id="mass-remove">Mass remove from Cache</button>
</div>
<h3>List Cache content</h3>
<div>
<button id="list-contents">List Cache Contents</button>
</div>
<ul id="contents"></ul>
</div>
</div>
<script src="base.js"></script>
</body>
</html>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', {scope: './'}).then(function() {
// Registration was successful. Now, check to see whether the Service Worker is controlling the page.
if (navigator.serviceWorker.controller) {
// If .controller is set, then this page is being actively controlled by the Service Worker.
// Show the interface for sending messages to the service worker.
showCommands();
} else {
// If .controller isn't set, then prompt the user to reload the page so that the Service Worker can take
// control. Until that happens, the Service Worker's message handler won't be used.
document.querySelector('#status').textContent =
'Please reload this page to allow the service worker to take control.';
}
}).catch(function(error) {
// Something went wrong during registration. The service-worker.js file
// might be unavailable or contain a syntax error.
document.querySelector('#status').textContent = error;
});
} else {
// The current browser doesn't support Service Workers.
var aElement = document.createElement('a');
aElement.href = 'http://www.chromium.org/blink/serviceworker/service-worker-faq';
aElement.textContent = 'Service Workers are not supported in the current browser.';
document.querySelector('#status').appendChild(aElement);
}
//jio = jIO.createJIO({type: "indexeddb", database: "cribjs"});
jio = jIO.createJIO({type:"dav", url: "https://cedriclendav.node.vifib.com/toto", basic_login: btoa("cedriclen:foo")})
function setStatus(statusMessage) {
document.querySelector('#status').textContent = statusMessage;
}
function getExtension(url) {
var extension = url.split('.').pop();
if (extension === "/") {
return ".html";
}
return "." + extension;
}
function showCommands() {
document.querySelector('#add').addEventListener('click', function() {
var url = document.querySelector('#url').value;
sendMessage({
command: 'add',
url: url,
information: new Blob([document.querySelector('#information').value], {
type: document.querySelector('#mimetype').value
})
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Added to cache: ' + url + ' at ' + Date());
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#delete').addEventListener('click', function() {
sendMessage({
command: 'delete',
url: document.querySelector('#url').value
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#list-contents').addEventListener('click', function() {
sendMessage({
command: 'keys'
}).then(function(data) {
var contentsElement = document.querySelector('#contents');
// Clear out the existing items from the list.
while (contentsElement.firstChild) {
contentsElement.removeChild(contentsElement.firstChild);
}
// Add each cached URL to the list, one by one.
data.urls.forEach(function(url) {
var liElement = document.createElement('li'),
aElement = document.createElement('a');
aElement.setAttribute('href', url);
aElement.textContent = url;
liElement.innerHTML = aElement.outerHTML;
contentsElement.appendChild(liElement);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#get').addEventListener('click', function() {
console.log("bar");
getContent(document.querySelector('#url').value,
function() {
document.querySelector('#mimetype').value = this.getResponseHeader("content-type");
document.querySelector('#information').value = this.responseText;
}
);
})
document.querySelector('#save-path').value = document.location.origin;
document.querySelector('#save-contents').addEventListener('click', function() {
console.log('asking for the keys');
var path_to_save, path_to_save_length, application_id;
path_to_save = document.querySelector('#save-path').value
application_id = document.querySelector('#save-id').value
path_to_save_length = path_to_save.length
sendMessage({
command: 'keys'
}).then(function(data) {
// Add each cached URL to the list, one by one.
console.log("foo");
return new RSVP.Queue()
.push(function() {
console.log("put");
return RSVP.all(
[jio.put("/" + application_id + ".attachment/", {
// url: path_to_save
}),
jio.putAttachment("/", application_id, new Blob([JSON.stringify({url: path_to_save})],{type: "application/json"}))
]);
})
.push(function() {
var promise_list = [],
i, i_len, url;
for (i = 0, i_len = data.urls.length; i < i_len; i += 1) {
url = new String(data.urls[i]);
if (url.indexOf(path_to_save) === 0) {
promise_list.push(jIO.util.ajax({
'url': url,
dataType: "blob"
}));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, response, extension;
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
response = response_list[i];
url = response.target.responseURL.substr(path_to_save_length)
extension = getExtension(url)
console.log("Pushing attachment " + url + " to " + path_to_save);
promise_list.push(
jio.putAttachment("/" + application_id + ".attachment/", btoa(url) + extension,
response.target.response
)
);
};
return RSVP.all(promise_list)
})
.push(undefined, function(error) {
console.log(error);
});
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
});
document.querySelector('#load-path').value = document.location.origin;
document.querySelector('#load-contents').addEventListener('click', function() {
console.log('asking for the keys');
var url_list = [],
application_id;
application_id = document.querySelector('#load-id').value
return new RSVP.Queue()
.push(function() {
console.log("put");
return jio.allAttachments("/" + application_id + ".attachment/")
})
.push(function(response) {
var promise_list = [],
key, extension;
for (key in response) {
if (response.hasOwnProperty(key)) {
extension = getExtension(key);
console.log(key);
url_list.push(atob(key.substr(0, key.length - extension.length)))
promise_list.push(jio.getAttachment("/" + application_id + ".attachment/", key));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, index, response, location, location_len;
location = document.location.origin;
location_len = location.length
console.log(url_list);
console.log(response_list);
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
url = url_list[i]
index = url.indexOf(location);
if (index != -1)
url = url.substr(index + location_len);
sendMessage({
command: 'add',
url: document.querySelector('#load-path').value + url,
information: response_list[i]
})
}
})
.push(undefined, function(error) {
console.log(error);
});
});
document.querySelector('#mass-remove').addEventListener('click', function() {
var url_list = document.querySelector('#mass-remove-list').value.match(/[^\r\n]+/g),
url_list_length = url_list.length,
i;
console.log(url_list)
for (i = 0; i < url_list_length; i += 1) {
console.log(url_list[i])
sendMessage({
command: 'delete',
url: url_list[i],
}).then(function() {
// If the promise resolves, just display a success message.
setStatus('Deleted from cache.');
}).catch(setStatus); // If the promise rejects, then setStatus will be called with the error.
}
});
document.querySelector('#commands').style.display = 'block';
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new Promise(function(resolve, reject) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function getContent(url, reqListener) {
var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get", url, true);
oReq.send();
}
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Crib SW interface Gadget</title>
<!-- renderjs -->
<script src="/lib/rsvp.js" type="text/javascript"></script>
<script src="/lib/renderjs.js" type="text/javascript"></script>
<!-- custom script -->
<script src="crib-sw-gadget.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP */
/*jslint indent: 2, maxerr: 3 */
(function(window, rJS, RSVP) {
"use strict";
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', {scope: './'})
}
/**
* Send request with XHR and return a promise. xhr.onload: The promise is
* resolved when the status code is lower than 400 with the xhr object as
* first parameter. xhr.onerror: reject with xhr object as first
* parameter. xhr.onprogress: notifies the xhr object.
*
* @param {Object} param The parameters
* @param {String} [param.type="GET"] The request method
* @param {String} [param.dataType=""] The data type to retrieve
* @param {String} param.url The url
* @param {Any} [param.data] The data to send
* @param {Function} [param.beforeSend] A function called just before the
* send request. The first parameter of this function is the XHR object.
* @return {Promise} The promise
*/
function ajax(param) {
var xhr = new XMLHttpRequest();
return new RSVP.Promise(function(resolve, reject, notify) {
var k;
xhr.open(param.type || "GET", param.url, true);
xhr.addEventListener("load", function(e) {
var answer = {};
if (e.target.status >= 400) {
return reject(e);
}
answer.responseText = this.responseText;
answer.responseType = this.getResponseHeader("content-type");
answer.responseURL = param.url
resolve(answer);
});
xhr.addEventListener("error", reject);
xhr.addEventListener("progress", notify);
if (typeof param.xhrFields === 'object' && param.xhrFields !== null) {
for (k in param.xhrFields) {
if (param.xhrFields.hasOwnProperty(k)) {
xhr[k] = param.xhrFields[k];
}
}
}
xhr.send();
}, function() {
xhr.abort();
});
}
function sendMessage(message) {
// This wraps the message posting/response in a promise, which will resolve if the response doesn't
// contain an error, and reject with the error if it does. If you'd prefer, it's possible to call
// controller.postMessage() and set up the onmessage handler independently of a promise, but this is
// a convenient wrapper.
return new RSVP.Promise(function(resolve, reject, notify) {
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function(event) {
console.log(event);
if (event.data.error) {
reject(event.data.error);
} else {
resolve(event.data);
}
};
// This sends the message data as well as transferring messageChannel.port2 to the service worker.
// The service worker can then use the transferred port to reply via postMessage(), which
// will in turn trigger the onmessage handler on messageChannel.port1.
// See https://html.spec.whatwg.org/multipage/workers.html#dom-worker-postmessage
return navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
});
}
function setStatus(statusMessage) {
console.log(statusMessage);
}
rJS(window)
.ready(function(gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
if (navigator.serviceWorker.controller === null) {
document.location = document.location.origin;
}
})
.declareMethod('allDocs', function() {
return new RSVP.Queue()
.push(function() {
return sendMessage({
command: 'keys'
})
})
})
.declareMethod('get', function(url) {
return ajax({
url: url
})
})
.declareMethod('put', function(url, parameter) {
return new RSVP.Queue()
.push(function() {
if (parameter.blob !== undefined) {
return sendMessage({
command: 'add',
url: url,
information: parameter.blob
})
}
return sendMessage({
command: 'add',
url: url,
information: new Blob([parameter.content], {
type: parameter.type,
})
})
}).push(function() {
// If the promise resolves, just display a success message.
console.log("Done adding "+ url);
return 'Added to cache: ' + url + ' at ' + Date();
}).fail(setStatus);
})
.declareMethod('remove', function(url) {
return new RSVP.Queue()
.push(function() {
return sendMessage({
command: 'delete',
url: url
})
})
});
}(window, rJS, RSVP));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_cribjs_loader.js"></script>
</head>
<body>
<h1>CribJS Loader</h1>
<div data-gadget-url="/gadget/gadget_jio_cribjs.html"
data-gadget-scope="jio_cribjs"
data-gadget-sandbox="public"></div>
<div data-gadget-url="/gadget/gadget_jio_configurator.html"
data-gadget-scope="jio_configurator"
data-gadget-sandbox="public"></div>
<form>
<button type="submit">Load</button>
</form>
<ul>
</ul>
</body>
</html>
\ No newline at end of file
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS) {
"use strict";
function loadCribJS (gadget, event) {
var jio_cribjs_gadget,
jio_configurator_gadget;
return RSVP.Queue()
.push(function() {
return RSPV.all([
gadget.getDeclaredGadget("jio_cribjs"),
gadget.getDeclaredGadget("jio_configurator")]);
})
.push(function(gadget_list) {
jio_cribjs_gadget = gadget_list[0];
jio_configurator_gadget = gadget_list[1];
return jio_configurator_gadget.getContent();
})
.push(function (jio_string) {
return jio_cribjs_gadget.load({
path: document.location.origin,
jio_config: JSON.parse(jio_string),
application_id: "cribjs"
})
})
.push(function (url_list) {
console.log(url_list);
document.location = document.location.origin + "/crib-editor/";
})
}
rJS(window)
.declareMethod('render', function (options) {
var gadget = this,
jio_configuration_string = "";
console.log("rendering");
if (localStorage.hasOwnProperty("crib_js_loader_jio")) {
jio_configuration_string = localStorage.getItem("crib_js_loader_jio");
} else {
jio_configuration_string = '{"type": "dav", "url": "https://cedriclendav.node.vifib.com/public/cribjs-storage/"}';
}
return RSVP.Queue()
.push(function () {
return gadget.getDeclaredGadget('jio_configurator');
})
.push(function (jio_configurator_gadget) {
console.log(jio_configuration_string);
return jio_configurator_gadget.render({value: jio_configuration_string});
})
.push(function () {
return loopEventListener(
gadget.props.element.querySelector("form"),
'submit',
false,
function (event) {loadCribJS(gadget, event)}
);
})
.fail(function(e){console.log(e)})
})
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
});
}(window, rJS));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>Jio Gadget</title>
<!-- renderjs -->
<script src="/lib/rsvp.js" type="text/javascript"></script>
<script src="/lib/renderjs.js" type="text/javascript"></script>
<script src="/lib/jio-latest.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_jio.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*global window, rJS, jIO */
/*jslint indent: 2, maxerr: 3 */
(function (window, rJS, jIO) {
"use strict";
rJS(window)
.ready(function (gadget) {
// Initialize the gadget local parameters
gadget.state_parameter_dict = {};
})
.declareMethod('createJio', function (jio_options) {
this.state_parameter_dict.jio_storage = jIO.createJIO(jio_options);
})
.declareMethod('allDocs', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allDocs.apply(storage, arguments);
})
.declareMethod('allAttachments', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.allAttachments.apply(storage, arguments);
})
.declareMethod('get', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.get.apply(storage, arguments);
})
.declareMethod('put', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.put.apply(storage, arguments);
})
.declareMethod('post', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.post.apply(storage, arguments);
})
.declareMethod('remove', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.remove.apply(storage, arguments);
})
.declareMethod('getAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.getAttachment.apply(storage, arguments);
})
.declareMethod('putAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.putAttachment.apply(storage, arguments);
})
.declareMethod('removeAttachment', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.removeAttachment.apply(storage, arguments);
})
.declareMethod('repair', function () {
var storage = this.state_parameter_dict.jio_storage;
return storage.repair.apply(storage, arguments);
});
}(window, rJS, jIO));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_jio_configurator.js"></script>
</head>
<body>
<form>
<textarea name="jio_json_configuration"></textarea>
</form>
</body>
</html>
\ No newline at end of file
/*jslint nomen: true, indent: 2, maxerr: 3 */
/*global window, rJS */
(function (window, rJS) {
"use strict";
rJS(window)
.declareAcquiredMethod("saveContent", "editor_saveContent")
.declareMethod('render', function (options) {
if (options && options.hasOwnProperty("value"))
this.props.element.querySelector("textarea").value = options.value || "";
})
.declareMethod('getContent', function () {
return this.props.element.querySelector("textarea").value;
})
.ready(function (g) {
g.props = {};
return g.getElement()
.push(function (element) {
g.props.element = element;
});
});
}(window, rJS));
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width">
<title>CribJS Loader</title>
<script src="/lib/rsvp.js"></script>
<script src="/lib/renderjs.js"></script>
<script src="gadget_jio_cribjs.js"></script>
</head>
<body>
<div data-gadget-url="/gadget/crib-sw-gadget.html"
data-gadget-scope="crib_sw_gadget"
data-gadget-sandbox="public"></div>
<div data-gadget-url="/gadget/gadget_jio.html"
data-gadget-scope="jio_gadget"
data-gadget-sandbox="public"></div>
</body>
</html>
\ No newline at end of file
/*globals window, document, RSVP, rJS,
location, console*/
/*jslint indent: 2, maxlen: 80*/
(function (window, document, RSVP, rJS, location, console) {
"use strict";
function getExtension(url) {
var extension = url.split('.').pop();
if (extension.endsWith('/')) {
return ".html";
}
return "." + extension;
}
rJS(window)
.ready(function (g) {
g.props = {};
})
.declareMethod('load', function (options) {
var path_to_load, path_to_load_length, application_id, crib_sw_gadget, jio_config,
jio_gadget, url_list = [], gadget = this;
path_to_load = options.path;
application_id = options.application_id;
jio_config = options.jio_config;
path_to_load_length = path_to_load.length;
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
gadget.getDeclaredGadget('jio_gadget')]);
})
.push(function (gadget_list) {
crib_sw_gadget = gadget_list[0];
jio_gadget = gadget_list[1];
return jio_gadget.createJio(jio_config);
})
.push(function () {
return jio_gadget.allAttachments("/" + application_id + ".attachment/");
})
.push(function(response) {
var promise_list = [],
key, extension;
for (key in response) {
if (response.hasOwnProperty(key)) {
extension = getExtension(key);
console.log(key);
url_list.push(atob(key.substr(0, key.length - extension.length)))
promise_list.push(jio_gadget.getAttachment("/" + application_id + ".attachment/", key));
}
};
return RSVP.all(promise_list)
})
.push(function(response_list) {
var promise_list = [],
i, i_len, url, index, response, location, location_len;
location = document.location.origin;
location_len = location.length
for (i = 0, i_len = response_list.length; i < i_len; i += 1) {
url = url_list[i]
index = url.indexOf(location);
if (index != -1)
url = url.substr(index + location_len);
console.log(path_to_load + url);
promise_list.push(
crib_sw_gadget.put(path_to_load + url, {blob: response_list[i]})
)
}
return RSVP.all(promise_list);
})
.push(function() {
return url_list
});
})
.declareMethod('save', function (options) {
var path_to_save, path_to_save_length, application_id, crib_sw_gadget, jio_config,
jio_gadget, url_list = [], saved_url_list = [], gadget = this;
path_to_save = options.path;
application_id = options.application_id;
jio_config = options.jio_config;
path_to_save_length = path_to_save.length;
return new RSVP.Queue()
.push(function () {
return RSVP.all([
gadget.getDeclaredGadget('crib_sw_gadget'),
gadget.getDeclaredGadget('jio_gadget')]);
})
.push(function (gadget_list) {
crib_sw_gadget = gadget_list[0];
jio_gadget = gadget_list[1];
return jio_gadget.createJio(jio_config);
})
.push(function (){
return crib_sw_gadget.allDocs();
})
.push(function(data) {
url_list = data.urls;
// This is buggy, it fails if the document already exists with dav storage
// XX Should be able to add some metadata here such as url, contributor, version ...
return RSVP.all(
[jio_gadget.put("/" + application_id + ".attachment/", {
}),
jio_gadget.putAttachment("/", application_id, new Blob([JSON.stringify({url: path_to_save})],{type: "application/json"}))
]);
})
.push(function() {
var promise_list = [],
i, i_len, url;
for (i = 0, i_len = url_list.length; i < i_len; i += 1) {
url = new String(url_list[i]);
if (url.indexOf(path_to_save) === 0) {
saved_url_list.push({url: url});
promise_list.push(jIO.