dom/apps/src/OfflineCacheInstaller.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cu = Components.utils;
     8 const Cc = Components.classes;
     9 const Ci = Components.interfaces;
    10 const CC = Components.Constructor;
    12 this.EXPORTED_SYMBOLS = ["OfflineCacheInstaller"];
    14 Cu.import("resource://gre/modules/Services.jsm");
    15 Cu.import("resource://gre/modules/AppsUtils.jsm");
    16 Cu.import("resource://gre/modules/NetUtil.jsm");
    18 let Namespace = CC('@mozilla.org/network/application-cache-namespace;1',
    19                    'nsIApplicationCacheNamespace',
    20                    'init');
    21 let makeFile = CC('@mozilla.org/file/local;1',
    22                 'nsIFile',
    23                 'initWithPath');
    24 let MutableArray = CC('@mozilla.org/array;1', 'nsIMutableArray');
    26 const nsICache = Ci.nsICache;
    27 const nsIApplicationCache = Ci.nsIApplicationCache;
    28 const applicationCacheService =
    29   Cc['@mozilla.org/network/application-cache-service;1']
    30     .getService(Ci.nsIApplicationCacheService);
    33 function debug(aMsg) {
    34   //dump("-*-*- OfflineCacheInstaller.jsm : " + aMsg + "\n");
    35 }
    38 function enableOfflineCacheForApp(origin, appId) {
    39   let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
    40                     origin, appId, false);
    41   Services.perms.addFromPrincipal(principal, 'offline-app',
    42                                   Ci.nsIPermissionManager.ALLOW_ACTION);
    43   // Prevent cache from being evicted:
    44   Services.perms.addFromPrincipal(principal, 'pin-app',
    45                                   Ci.nsIPermissionManager.ALLOW_ACTION);
    46 }
    49 function storeCache(applicationCache, url, file, itemType) {
    50   let session = Services.cache.createSession(applicationCache.clientID,
    51                                              nsICache.STORE_OFFLINE, true);
    52   session.asyncOpenCacheEntry(url, nsICache.ACCESS_WRITE, {
    53     onCacheEntryAvailable: function (cacheEntry, accessGranted, status) {
    54       cacheEntry.setMetaDataElement('request-method', 'GET');
    55       cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n');
    57       let outputStream = cacheEntry.openOutputStream(0);
    59       // Input-Output stream machinery in order to push nsIFile content into cache
    60       let inputStream = Cc['@mozilla.org/network/file-input-stream;1']
    61                           .createInstance(Ci.nsIFileInputStream);
    62       inputStream.init(file, 1, -1, null);
    63       let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1']
    64                                    .createInstance(Ci.nsIBufferedOutputStream);
    65       bufferedOutputStream.init(outputStream, 1024);
    66       bufferedOutputStream.writeFrom(inputStream, inputStream.available());
    67       bufferedOutputStream.flush();
    68       bufferedOutputStream.close();
    69       inputStream.close();
    71       cacheEntry.markValid();
    72       debug (file.path + ' -> ' + url + ' (' + itemType + ')');
    73       applicationCache.markEntry(url, itemType);
    74       cacheEntry.close();
    75     }
    76   });
    77 }
    79 function readFile(aFile, aCallback) {
    80   let channel = NetUtil.newChannel(aFile);
    81   channel.contentType = "plain/text";
    82   NetUtil.asyncFetch(channel, function(aStream, aResult) {
    83     if (!Components.isSuccessCode(aResult)) {
    84       Cu.reportError("OfflineCacheInstaller: Could not read file " + aFile.path);
    85       if (aCallback)
    86         aCallback(null);
    87       return;
    88     }
    90     // Obtain a converter to read from a UTF-8 encoded input stream.
    91     let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
    92                       .createInstance(Ci.nsIScriptableUnicodeConverter);
    93     converter.charset = "UTF-8";
    95     let data = NetUtil.readInputStreamToString(aStream,
    96                                                aStream.available());
    97     aCallback(converter.ConvertToUnicode(data));
    98   });
    99 }
   101 function parseCacheLine(app, urls, line) {
   102   try {
   103     let url = Services.io.newURI(line, null, app.origin);
   104     urls.push(url.spec);
   105   } catch(e) {
   106     throw new Error('Unable to parse cache line: ' + line + '(' + e + ')');
   107   }
   108 }
   110 function parseFallbackLine(app, urls, namespaces, fallbacks, line) {
   111   let split = line.split(/[ \t]+/);
   112   if (split.length != 2) {
   113     throw new Error('Should be made of two URLs seperated with spaces')
   114   }
   115   let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_FALLBACK;
   116   let [ namespace, fallback ] = split;
   118   // Prepend webapp origin in case of absolute path
   119   try {
   120     namespace = Services.io.newURI(namespace, null, app.origin).spec;
   121     fallback = Services.io.newURI(fallback, null, app.origin).spec;
   122   } catch(e) {
   123     throw new Error('Unable to parse fallback line: ' + line + '(' + e + ')');
   124   }
   126   namespaces.push([type, namespace, fallback]);
   127   fallbacks.push(fallback);
   128   urls.push(fallback);
   129 }
   131 function parseNetworkLine(namespaces, line) {
   132   let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_BYPASS;
   133   if (line[0] == '*' && (line.length == 1 || line[1] == ' '
   134                                           || line[1] == '\t')) {
   135     namespaces.push([type, '', '']);
   136   } else {
   137     namespaces.push([type, namespace, '']);
   138   }
   139 }
   141 function parseAppCache(app, path, content) {
   142   let lines = content.split(/\r?\n/);
   144   let urls = [];
   145   let namespaces = [];
   146   let fallbacks = [];
   148   let currentSection = 'CACHE';
   149   for (let i = 0; i < lines.length; i++) {
   150     let line = lines[i];
   152     // Ignore comments
   153     if (/^#/.test(line) || !line.length)
   154       continue;
   156     // Process section headers
   157     if (line == 'CACHE MANIFEST')
   158       continue;
   159     if (line == 'CACHE:') {
   160       currentSection = 'CACHE';
   161       continue;
   162     } else if (line == 'NETWORK:') {
   163       currentSection = 'NETWORK';
   164       continue;
   165     } else if (line == 'FALLBACK:') {
   166       currentSection = 'FALLBACK';
   167       continue;
   168     }
   170     // Process cache, network and fallback rules
   171     try {
   172       if (currentSection == 'CACHE') {
   173         parseCacheLine(app, urls, line);
   174       } else if (currentSection == 'NETWORK') {
   175         parseNetworkLine(namespaces, line);
   176       } else if (currentSection == 'FALLBACK') {
   177         parseFallbackLine(app, urls, namespaces, fallbacks, line);
   178       }
   179     } catch(e) {
   180       throw new Error('Invalid ' + currentSection + ' line in appcache ' +
   181                       'manifest:\n' + e.message +
   182                       '\nFrom: ' + path +
   183                       '\nLine ' + i + ': ' + line);
   184     }
   185   }
   187   return {
   188     urls: urls,
   189     namespaces: namespaces,
   190     fallbacks: fallbacks
   191   };
   192 }
   194 function installCache(app) {
   195   if (!app.cachePath) {
   196     return;
   197   }
   199   let cacheDir = makeFile(app.cachePath)
   200   cacheDir.append(app.appId);
   201   cacheDir.append('cache');
   202   if (!cacheDir.exists())
   203     return;
   205   let cacheManifest = cacheDir.clone();
   206   cacheManifest.append('manifest.appcache');
   207   if (!cacheManifest.exists())
   208     return;
   210   enableOfflineCacheForApp(app.origin, app.localId);
   212   // Get the url for the manifest.
   213   let appcacheURL = app.appcache_path;
   215   // The group ID contains application id and 'f' for not being hosted in
   216   // a browser element, but a mozbrowser iframe.
   217   // See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier
   218   let groupID = appcacheURL + '#' + app.localId+ '+f';
   219   let applicationCache = applicationCacheService.createApplicationCache(groupID);
   220   applicationCache.activate();
   222   readFile(cacheManifest, function readAppCache(content) {
   223     let entries = parseAppCache(app, cacheManifest.path, content);
   225     entries.urls.forEach(function processCachedFile(url) {
   226       // Get this nsIFile from cache folder for this URL
   227       // We have absolute urls, so remove the origin part to locate the
   228       // files.
   229       let path = url.replace(app.origin.spec, '');
   230       let file = cacheDir.clone();
   231       let paths = path.split('/');
   232       paths.forEach(file.append);
   234       if (!file.exists()) {
   235         let msg = 'File ' + file.path + ' exists in the manifest but does ' +
   236                   'not points to a real file.';
   237         throw new Error(msg);
   238       }
   240       let itemType = nsIApplicationCache.ITEM_EXPLICIT;
   241       storeCache(applicationCache, url, file, itemType);
   242     });
   244     let array = new MutableArray();
   245     entries.namespaces.forEach(function processNamespace([type, spec, data]) {
   246       debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n');
   247       array.appendElement(new Namespace(type, spec, data), false);
   248     });
   249     applicationCache.addNamespaces(array);
   251     entries.fallbacks.forEach(function processFallback(url) {
   252       debug('add fallback: ' + url + '\n');
   253       let type = nsIApplicationCache.ITEM_FALLBACK;
   254       applicationCache.markEntry(url, type);
   255     });
   257     storeCache(applicationCache, appcacheURL, cacheManifest,
   258                nsIApplicationCache.ITEM_MANIFEST);
   259   });
   260 }
   263 // Public API
   265 this.OfflineCacheInstaller = {
   266   installCache: installCache
   267 };

mercurial