Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/build/offline-cache.js
diff options
context:
space:
mode:
Diffstat (limited to 'build/offline-cache.js')
-rw-r--r--build/offline-cache.js159
1 files changed, 159 insertions, 0 deletions
diff --git a/build/offline-cache.js b/build/offline-cache.js
new file mode 100644
index 0000000..abec495
--- /dev/null
+++ b/build/offline-cache.js
@@ -0,0 +1,159 @@
+let Namespace = CC('@mozilla.org/network/application-cache-namespace;1',
+ 'nsIApplicationCacheNamespace',
+ 'init');
+const nsICache = Ci.nsICache;
+const nsIApplicationCache = Ci.nsIApplicationCache;
+const applicationCacheService = Cc['@mozilla.org/network/application-cache-service;1']
+ .getService(Ci.nsIApplicationCacheService);
+
+function log(str) {
+ dump(' +-+ OfflineCache: ' + str + '\n');
+}
+
+/*
+ * Compile Gaia into an offline cache sqlite database.
+ */
+function storeCache(applicationCache, url, file, itemType) {
+ let session = Services.cache.createSession(applicationCache.clientID,
+ nsICache.STORE_OFFLINE, true);
+ session.asyncOpenCacheEntry(url, nsICache.ACCESS_WRITE, {
+ onCacheEntryAvailable: function (cacheEntry, accessGranted, status) {
+ cacheEntry.setMetaDataElement('request-method', 'GET');
+ cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n');
+ // Force an update. the default expiration time is way too far in the future:
+ //cacheEntry.setExpirationTime(0);
+
+ let outputStream = cacheEntry.openOutputStream(0);
+
+ // Input-Output stream machinery in order to push nsIFile content into cache
+ let inputStream = Cc['@mozilla.org/network/file-input-stream;1']
+ .createInstance(Ci.nsIFileInputStream);
+ inputStream.init(file, 1, -1, null);
+ let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1']
+ .createInstance(Ci.nsIBufferedOutputStream);
+ bufferedOutputStream.init(outputStream, 1024);
+ bufferedOutputStream.writeFrom(inputStream, inputStream.available());
+ bufferedOutputStream.flush();
+ bufferedOutputStream.close();
+ outputStream.close();
+ inputStream.close();
+
+ cacheEntry.markValid();
+ log (file.path + ' -> ' + url + ' (' + itemType + ')');
+ applicationCache.markEntry(url, itemType);
+ cacheEntry.close();
+ }
+ });
+}
+
+function getCachedURLs(origin, appcacheFile) {
+ let urls = [];
+ let lines = getFileContent(appcacheFile).split(/\r?\n/);
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i];
+ if (/^#/.test(line) || !line.length)
+ continue;
+ if (line == 'CACHE MANIFEST')
+ continue;
+ if (line == 'CACHE:')
+ continue;
+ if (line == 'NETWORK:')
+ break;
+ // Prepend webapp origin in case of absolute path
+ if (line[0] == '/')
+ urls.push(origin + line.substring(1));
+ // Just pass along the url, if we have one
+ else if (line.substr(0, 4) == 'http')
+ urls.push(line);
+ else
+ throw new Error('Invalid line in appcache manifest:\n' + line +
+ '\nFrom: ' + appcacheFile.path);
+ }
+ return urls;
+}
+
+let webapps = getJSON(getFile(PROFILE_DIR, 'webapps', 'webapps.json'));
+
+Gaia.externalWebapps.forEach(function (webapp) {
+ // Process only webapp with a `appcache_path` field in their manifest.
+ if (!('appcache_path' in webapp.manifest))
+ return;
+
+ // Get the nsIFile for the appcache file by using `origin` file and
+ // `appcache_path` field of the manifest in order to find it in `cache/`.
+ let originDomain = webapp.origin.replace(/^https?:\/\//, '');
+ let appcachePath = 'cache/' + originDomain + webapp.manifest.appcache_path;
+ let appcacheURL = webapp.origin +
+ webapp.manifest.appcache_path.replace(/^\//, '');
+ let appcacheFile = webapp.sourceDirectoryFile.clone();
+ appcachePath.split('/').forEach(function (name) {
+ appcacheFile.append(name);
+ });
+ if (!appcacheFile.exists())
+ throw new Error('Unable to find application cache manifest: ' +
+ appcacheFile.path);
+
+ // Retrieve generated webapp id from platform profile file build by
+ // webapp-manifest.js; in order to allow the webapp to use offline cache.
+ let appId = webapps[webapp.sourceDirectoryName].localId;
+ let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
+ Services.io.newURI(webapp.origin, null, null),
+ appId, false);
+ Services.perms.addFromPrincipal(principal, 'offline-app',
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ // Get the url for the manifest. At some points the root
+ // domain should be extracted from manifest.webapp.
+ // See netwerk/cache/nsDiskCacheDeviceSQL.cpp : AppendJARIdentifier
+ // The group ID contains application id and 'f' for not being hosted in
+ // a browser element, but a mozbrowser iframe.
+ let groupID = appcacheURL + '#' + appId + '+f';
+ let applicationCache = applicationCacheService.createApplicationCache(groupID);
+ applicationCache.activate();
+
+ log ('Compiling (' + webapp.domain + ')');
+
+ let urls = getCachedURLs(webapp.origin, appcacheFile);
+ urls.forEach(function appendFile(url) {
+ // Get this nsIFile out of its relative path
+ let path = url.replace(/https?:\/\//, '');
+ let file = webapp.sourceDirectoryFile.clone();
+ file.append('cache');
+ let paths = path.split('/');
+ for (let i = 0; i < paths.length; i++) {
+ file.append(paths[i]);
+ }
+
+ if (!file.exists()) {
+ let msg = 'File ' + file.path + ' exists in the manifest but does not ' +
+ 'points to a real file.';
+ throw new Error(msg);
+ }
+
+ // TODO: use ITEM_IMPLICIT for launch_path, if it occurs to be important.
+ let itemType = nsIApplicationCache.ITEM_EXPLICIT;
+ storeCache(applicationCache, url, file, itemType);
+ });
+
+ // Store the appcache file
+ storeCache(applicationCache, appcacheURL, appcacheFile,
+ nsIApplicationCache.ITEM_MANIFEST);
+
+ // NETWORK:
+ // http://*
+ // https://*
+ let array = Cc['@mozilla.org/array;1'].createInstance(Ci.nsIMutableArray);
+ let bypass = Ci.nsIApplicationCacheNamespace.NAMESPACE_BYPASS;
+ array.appendElement(new Namespace(bypass, 'http://*/', ''), false);
+ array.appendElement(new Namespace(bypass, 'https://*/', ''), false);
+ applicationCache.addNamespaces(array);
+});
+
+
+// Wait for cache to be filled before quitting
+if (Gaia.engine === 'xpcshell') {
+ let thread = Services.tm.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+}