Files
palemoon27/dom/apps/OfflineCacheInstaller.jsm
T
roytam1 b6d6258762 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1182537 - Use channel->ascynOpen2 in dom/security/nsCORSListenerProxy (r=sicking) (5c4b779a12)
- Bug 1155758 - Make about:serviceworkers work in B2G. r=fabrice (195eca3894)
- Bug 1162920 - JavaScript error at aboutServiceWorkers.js when updating the service worker. r=fabrice (2d3a831a8c)
- Bug 1155153 - [e10s] about:serviceworkers should work in e10s mode. Update B2G implementation. r=baku (0d1c2999c1)
- Bug 1171915 - about:serviceworkers in b2g should use originAttributes when calling ServiceWorkerManager. r=baku,fabrice (faa3725da9)
- Bug 1179161 - originAttributes does not have such isInBrowser member (follow-up bug 1171915). r=ferjm (a217140ae5)
- Bug 1171917 - Improve about:serviceworkers tests on b2g. r=ferjm (5fd9d2f478)
- Bug 1179557 - Add userContextId to originAttributes with tests. r=bholley, r=tanvi (8ddf96d921)
- Bug 1179557 - Add getters for userContextId. r=bholley, r=tanvi (ebec5f7c7e)
- Bug 1174110 - The service worker still remains registered when uninstalling the service-worker-enabled application. r=fabrice (c1c93b1250)
- Bug 1144689 - Allow setting manually a fetch time and modified time for cache entries. r=fabrice (8e9dd47425)
- Bug 1150199 - Langpacks should not have to be privileged r=ferjm (d41af25648)
- Bug 1111961 - Developer mode support r=ferjm,pauljt (9b523402ac)
- Bug 1168300 - notify clear-cookiejar-data. r=sicking (7d88bff29d)
- Bug 1136434 - RequestSync API should delete all the timers when a task is unregistered, r=ehsan (5f92977920)
- Bug 1151082 - RequestSyncAPI - avoid infinite loop when processing pending messages, r=ehsan (b5afcd55e8)
- Bug 1165787 - Use origin in RequestSyncService.jsm. r=ehsan (b6fad2bd68)
- Bug 1182347 - Migrate existing code away from .cookieJar. r=sicking,r=allstars.chh (304cbfd660)
- Bug 1118946 - API to provide localized properties r=ferjm,sicking (a28aecaf19)
- Bug 1077168 - Cancel in-flight Webapp install jobs from windows that change location. r=myk. (d55dc8ff6d)
- Bug 1150660 - Fix sendAsyncMessage() uses to not trigger warnings in dom/apps r=fabrice (b087adcc23)
- Bug 1169344 - Allow server apps to restrict access to their IAC ports. r=ferjm (82c8570555)
- Bug 1068400 - Fix devtools when morphing non-e10s tab into e10s one. r=jryans (55be5ccdf5)
- Bug 1145049 - Prevent caching tab actors in child processes. r=jryans (1a3ee9f278)
- Bug 1145049 - Stop leaking tab actors and root actor on disconnect. r=jryans (26f259b441)
- Bug 1181930 - Refactoring: move the message broadcaster out of Webapps.jsm r=ferjm (b1f8bb8b6d)
-  Bu 1115619 - Use a preference to guarantee app permission loading to permissions.sqlite. r=fabrice (5689c459d7)
- Bug 1191579 - Remove useless getAll() implementation in Webapps.jsm (74f0d6874a)
2022-03-24 11:03:39 +08:00

312 lines
10 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const CC = Components.Constructor;
this.EXPORTED_SYMBOLS = ["OfflineCacheInstaller"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
let Namespace = CC('@mozilla.org/network/application-cache-namespace;1',
'nsIApplicationCacheNamespace',
'init');
let makeFile = CC('@mozilla.org/file/local;1',
'nsIFile',
'initWithPath');
let MutableArray = CC('@mozilla.org/array;1', 'nsIMutableArray');
let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
const nsICacheStorage = Ci.nsICacheStorage;
const nsIApplicationCache = Ci.nsIApplicationCache;
const applicationCacheService =
Cc['@mozilla.org/network/application-cache-service;1']
.getService(Ci.nsIApplicationCacheService);
function debug(aMsg) {
//dump("-*-*- OfflineCacheInstaller.jsm : " + aMsg + "\n");
}
function enableOfflineCacheForApp(aPrincipal) {
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, 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" +
"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"]
.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();
inputStream.close();
cacheEntry.setExpirationTime(0);
cacheEntry.markValid();
debug (file.path + " -> " + url + " (" + itemType + ")");
applicationCache.markEntry(url, itemType);
cacheEntry.close();
}
});
}
function readFile(aFile, aPrincipal, aCallback) {
let channel = NetUtil.newChannel2(aFile,
null,
null,
null, // aLoadingNode
aPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
channel.contentType = "plain/text";
NetUtil.asyncFetch(channel, function(aStream, aResult) {
if (!Components.isSuccessCode(aResult)) {
Cu.reportError("OfflineCacheInstaller: Could not read file " + aFile.path);
if (aCallback)
aCallback(null);
return;
}
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let data = NetUtil.readInputStreamToString(aStream,
aStream.available());
aCallback(converter.ConvertToUnicode(data));
});
}
function parseCacheLine(app, urls, line) {
try {
let url = Services.io.newURI(line, null, app.origin);
urls.push(url.spec);
} catch(e) {
throw new Error('Unable to parse cache line: ' + line + '(' + e + ')');
}
}
function parseFallbackLine(app, urls, namespaces, fallbacks, line) {
let split = line.split(/[ \t]+/);
if (split.length != 2) {
throw new Error('Should be made of two URLs seperated with spaces')
}
let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_FALLBACK;
let [ namespace, fallback ] = split;
// Prepend webapp origin in case of absolute path
try {
namespace = Services.io.newURI(namespace, null, app.origin).spec;
fallback = Services.io.newURI(fallback, null, app.origin).spec;
} catch(e) {
throw new Error('Unable to parse fallback line: ' + line + '(' + e + ')');
}
namespaces.push([type, namespace, fallback]);
fallbacks.push(fallback);
urls.push(fallback);
}
function parseNetworkLine(namespaces, line) {
let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_BYPASS;
if (line[0] == '*' && (line.length == 1 || line[1] == ' '
|| line[1] == '\t')) {
namespaces.push([type, '', '']);
} else {
namespaces.push([type, namespace, '']);
}
}
function parseAppCache(app, path, content) {
let lines = content.split(/\r?\n/);
let urls = [];
let namespaces = [];
let fallbacks = [];
let currentSection = 'CACHE';
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
// Ignore comments
if (/^#/.test(line) || !line.length)
continue;
// Process section headers
if (line == 'CACHE MANIFEST')
continue;
if (line == 'CACHE:') {
currentSection = 'CACHE';
continue;
} else if (line == 'NETWORK:') {
currentSection = 'NETWORK';
continue;
} else if (line == 'FALLBACK:') {
currentSection = 'FALLBACK';
continue;
}
// Process cache, network and fallback rules
try {
if (currentSection == 'CACHE') {
parseCacheLine(app, urls, line);
} else if (currentSection == 'NETWORK') {
parseNetworkLine(namespaces, line);
} else if (currentSection == 'FALLBACK') {
parseFallbackLine(app, urls, namespaces, fallbacks, line);
}
} catch(e) {
throw new Error('Invalid ' + currentSection + ' line in appcache ' +
'manifest:\n' + e.message +
'\nFrom: ' + path +
'\nLine ' + i + ': ' + line);
}
}
return {
urls: urls,
namespaces: namespaces,
fallbacks: fallbacks
};
}
function installCache(app) {
if (!app.cachePath) {
return;
}
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);
// 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))));
}
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);
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, 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);
});
applicationCache.addNamespaces(array);
storeCache(applicationCache, appcacheURL, cacheManifest,
nsIApplicationCache.ITEM_MANIFEST);
});
});
}
// Public API
this.OfflineCacheInstaller = {
installCache: installCache
};