diff options
Diffstat (limited to 'build/offline-cache.js')
-rw-r--r-- | build/offline-cache.js | 159 |
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); + } +} |