Bug 1144689 - Allow setting manually a fetch time and modified time for cache entries. r=fabrice
authorAntonio M. Amaya <amac@tid.es>
Mon, 06 Apr 2015 06:14:00 +0200
changeset 267758 20d5299e3b5708713a079beb2f2a033ab907b8a5
parent 267757 714eed0515ff2fda076486aaf911a0d7fac44457
child 267759 1d3edb7b2d2e94480570dcbb0dcccf5cccf63274
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1144689
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1144689 - Allow setting manually a fetch time and modified time for cache entries. r=fabrice
dom/apps/OfflineCacheInstaller.jsm
--- a/dom/apps/OfflineCacheInstaller.jsm
+++ b/dom/apps/OfflineCacheInstaller.jsm
@@ -41,42 +41,53 @@ function enableOfflineCacheForApp(aPrinc
   Services.perms.addFromPrincipal(aPrincipal, 'offline-app',
                                   Ci.nsIPermissionManager.ALLOW_ACTION);
   // Prevent cache from being evicted:
   Services.perms.addFromPrincipal(aPrincipal, 'pin-app',
                                   Ci.nsIPermissionManager.ALLOW_ACTION);
 }
 
 
-function storeCache(applicationCache, url, file, itemType) {
+function storeCache(applicationCache, url, file, itemType, metadata) {
   let storage =
     Services.cache2.appCacheStorage(LoadContextInfo.default, applicationCache);
   let uri = Services.io.newURI(url, null, null);
+  let nowGMT = new Date().toGMTString();
+  metadata = metadata || {};
+  metadata.lastFetched = metadata.lastFetched || nowGMT;
+  metadata.lastModified = metadata.lastModified || nowGMT;
   storage.asyncOpenURI(uri, "", nsICacheStorage.OPEN_TRUNCATE, {
     onCacheEntryAvailable:
       function (cacheEntry, isNew, appCache, result) {
-        cacheEntry.setMetaDataElement('request-method', 'GET');
-        cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n');
+        cacheEntry.setMetaDataElement("request-method", "GET");
+        cacheEntry.setMetaDataElement("response-head",
+          "HTTP/1.1 200 OK\r\n" +
+          "Date: " + metadata.lastFetched + "\r\n" +
+          "Last-Modified: " + metadata.lastModified + "\r\n" +
+          "Cache-Control: no-cache\r\n");
 
         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']
+        // 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);
+        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();
         inputStream.close();
 
+        cacheEntry.setExpirationTime(0);
         cacheEntry.markValid();
-        debug (file.path + ' -> ' + url + ' (' + itemType + ')');
+        debug (file.path + " -> " + url + " (" + itemType + ")");
         applicationCache.markEntry(url, itemType);
         cacheEntry.close();
       }
   });
 }
 
 function readFile(aFile, aPrincipal, aCallback) {
 
@@ -201,77 +212,98 @@ function parseAppCache(app, path, conten
   };
 }
 
 function installCache(app) {
   if (!app.cachePath) {
     return;
   }
 
-  let cacheDir = makeFile(app.cachePath)
+  let cacheDir = makeFile(app.cachePath);
   cacheDir.append(app.appId);
+
+  let resourcesMetadata = cacheDir.clone();
+  resourcesMetadata.append('resources_metadata.json');
+
   cacheDir.append('cache');
   if (!cacheDir.exists())
     return;
 
   let cacheManifest = cacheDir.clone();
   cacheManifest.append('manifest.appcache');
   if (!cacheManifest.exists())
     return;
 
   let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
-                    app.origin, app.localId, false);
-
-  enableOfflineCacheForApp(principal);
-
-  // Get the url for the manifest.
-  let appcacheURL = app.appcache_path;
+      app.origin, app.localId, false);
 
-  // The group ID contains application id and 'f' for not being hosted in
-  // a browser element, but a mozbrowser iframe.
-  // See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier
-  let groupID = appcacheURL + '#' + app.localId+ '+f';
-  let applicationCache = applicationCacheService.createApplicationCache(groupID);
-  applicationCache.activate();
+  // If the build has been correctly configured, this should not happen!
+  // If we install the cache anyway, it won't be updateable. If we don't install
+  // it, the application won't be useable offline.
+  let metadataLoaded;
+  if (!resourcesMetadata.exists()) {
+    // Not debug, since this is something that should be logged always!
+    dump("OfflineCacheInstaller: App " + app.appId + " does have an app cache" +
+         " but does not have a resources_metadata.json file!");
+    metadataLoaded = Promise.resolve({});
+  } else {
+    metadataLoaded = new Promise(
+      (resolve, reject) =>
+        readFile(resourcesMetadata, principal, content => resolve(JSON.parse(content))));
+  }
 
-  readFile(cacheManifest, principal, function readAppCache(content) {
-    let entries = parseAppCache(app, cacheManifest.path, content);
+  metadataLoaded.then(function(metadata) {
+    enableOfflineCacheForApp(principal);
+
+    // Get the url for the manifest.
+    let appcacheURL = app.appcache_path;
+
+    // The group ID contains application id and 'f' for not being hosted in
+    // a browser element, but a mozbrowser iframe.
+    // See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier
+    let groupID = appcacheURL + '#' + app.localId+ '+f';
+    let applicationCache = applicationCacheService.createApplicationCache(groupID);
+    applicationCache.activate();
+
+    readFile(cacheManifest, principal, function readAppCache(content) {
+      let entries = parseAppCache(app, cacheManifest.path, content);
 
-    entries.urls.forEach(function processCachedFile(url) {
-      // Get this nsIFile from cache folder for this URL
-      // We have absolute urls, so remove the origin part to locate the
-      // files.
-      let path = url.replace(app.origin.spec, '');
-      let file = cacheDir.clone();
-      let paths = path.split('/');
-      paths.forEach(file.append);
+      entries.urls.forEach(function processCachedFile(url) {
+        // Get this nsIFile from cache folder for this URL
+        // We have absolute urls, so remove the origin part to locate the
+        // files.
+        let path = url.replace(app.origin.spec, '');
+        let file = cacheDir.clone();
+        let paths = path.split('/');
+        paths.forEach(file.append);
 
-      if (!file.exists()) {
-        let msg = 'File ' + file.path + ' exists in the manifest but does ' +
-                  'not points to a real file.';
-        throw new Error(msg);
-      }
+        if (!file.exists()) {
+          let msg = 'File ' + file.path + ' exists in the manifest but does ' +
+                    'not points to a real file.';
+          throw new Error(msg);
+        }
 
-      let itemType = nsIApplicationCache.ITEM_EXPLICIT;
-      if (entries.fallbacks.indexOf(url) > -1) {
-        debug('add fallback: ' + url + '\n');
-        itemType |= nsIApplicationCache.ITEM_FALLBACK;
-      }
-      storeCache(applicationCache, url, file, itemType);
-    });
+        let itemType = nsIApplicationCache.ITEM_EXPLICIT;
+        if (entries.fallbacks.indexOf(url) > -1) {
+          debug('add fallback: ' + url + '\n');
+          itemType |= nsIApplicationCache.ITEM_FALLBACK;
+        }
+        storeCache(applicationCache, url, file, itemType, metadata[path]);
+      });
 
-    let array = new MutableArray();
-    entries.namespaces.forEach(function processNamespace([type, spec, data]) {
-      debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n');
-      array.appendElement(new Namespace(type, spec, data), false);
+      let array = new MutableArray();
+      entries.namespaces.forEach(function processNamespace([type, spec, data]) {
+        debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n');
+        array.appendElement(new Namespace(type, spec, data), false);
+      });
+      applicationCache.addNamespaces(array);
+
+      storeCache(applicationCache, appcacheURL, cacheManifest,
+                 nsIApplicationCache.ITEM_MANIFEST);
     });
-    applicationCache.addNamespaces(array);
-
-    storeCache(applicationCache, appcacheURL, cacheManifest,
-               nsIApplicationCache.ITEM_MANIFEST);
   });
 }
 
 
 // Public API
 
 this.OfflineCacheInstaller = {
   installCache: installCache