mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-11-19 02:13:04 +00:00
593 lines
18 KiB
JavaScript
593 lines
18 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
Cu.import("resource://gre/modules/Promise.jsm");
|
|
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
|
Cu.import("resource://gre/modules/NativeApp.jsm");
|
|
|
|
const LINUX = navigator.platform.startsWith("Linux");
|
|
const MAC = navigator.platform.startsWith("Mac");
|
|
const WIN = navigator.platform.startsWith("Win");
|
|
|
|
const PR_RDWR = 0x04;
|
|
const PR_CREATE_FILE = 0x08;
|
|
const PR_TRUNCATE = 0x20;
|
|
|
|
function checkFiles(files) {
|
|
return Task.spawn(function*() {
|
|
for (let file of files) {
|
|
if (!(yield OS.File.exists(file))) {
|
|
info("File doesn't exist: " + file);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function checkDateHigherThan(files, date) {
|
|
return Task.spawn(function*() {
|
|
for (let file of files) {
|
|
if (!(yield OS.File.exists(file))) {
|
|
info("File doesn't exist: " + file);
|
|
return false;
|
|
}
|
|
|
|
let stat = yield OS.File.stat(file);
|
|
if (!(stat.lastModificationDate > date)) {
|
|
info("File not newer: " + file);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function dirContainsOnly(dir, expectedFiles) {
|
|
return Task.spawn(function*() {
|
|
let iterator = new OS.File.DirectoryIterator(dir);
|
|
|
|
let entries;
|
|
try {
|
|
entries = yield iterator.nextBatch();
|
|
} finally {
|
|
iterator.close();
|
|
}
|
|
|
|
let ret = true;
|
|
|
|
// Find unexpected files
|
|
for (let {path} of entries) {
|
|
if (expectedFiles.indexOf(path) == -1) {
|
|
info("Unexpected file: " + path);
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
// Find missing files
|
|
for (let expectedPath of expectedFiles) {
|
|
if (entries.findIndex(({path}) => path == expectedPath) == -1) {
|
|
info("Missing file: " + expectedPath);
|
|
ret = false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
});
|
|
}
|
|
|
|
var dirSize = Task.async(function*(aDir) {
|
|
let iterator = new OS.File.DirectoryIterator(aDir);
|
|
|
|
let entries;
|
|
try {
|
|
entries = yield iterator.nextBatch();
|
|
} finally {
|
|
iterator.close();
|
|
}
|
|
|
|
let size = 0;
|
|
|
|
for (let entry of entries) {
|
|
if (entry.isDir) {
|
|
size += yield dirSize(entry.path);
|
|
} else {
|
|
let stat = yield OS.File.stat(entry.path);
|
|
size += stat.size;
|
|
}
|
|
}
|
|
|
|
return size;
|
|
});
|
|
|
|
function wait(time) {
|
|
let deferred = Promise.defer();
|
|
|
|
setTimeout(function() {
|
|
deferred.resolve();
|
|
}, time);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
// Helper to create a nsIFile from a set of path components
|
|
function getFile() {
|
|
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
|
file.initWithPath(OS.Path.join.apply(OS.Path, arguments));
|
|
return file;
|
|
}
|
|
|
|
function setDryRunPref() {
|
|
let old_dry_run;
|
|
try {
|
|
old_dry_run = Services.prefs.getBoolPref("browser.mozApps.installer.dry_run");
|
|
} catch (ex) {}
|
|
|
|
Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", false);
|
|
|
|
SimpleTest.registerCleanupFunction(function() {
|
|
if (old_dry_run === undefined) {
|
|
Services.prefs.clearUserPref("browser.mozApps.installer.dry_run");
|
|
} else {
|
|
Services.prefs.setBoolPref("browser.mozApps.installer.dry_run", old_dry_run);
|
|
}
|
|
});
|
|
}
|
|
|
|
function TestAppInfo(aApp, aIsPackaged) {
|
|
this.appProcess = Cc["@mozilla.org/process/util;1"].
|
|
createInstance(Ci.nsIProcess);
|
|
|
|
this.isPackaged = aIsPackaged;
|
|
|
|
this.uniqueName = WebappOSUtils.getUniqueName(aApp);
|
|
|
|
if (LINUX) {
|
|
this.installPath = OS.Path.join(OS.Constants.Path.homeDir,
|
|
"." + this.uniqueName);
|
|
this.exePath = OS.Path.join(this.installPath, "webapprt-stub");
|
|
|
|
this.iconFile = OS.Path.join(this.installPath, "icon.png");
|
|
|
|
this.webappINI = OS.Path.join(this.installPath, "webapp.ini");
|
|
|
|
let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
|
|
getService(Ci.nsIEnvironment).
|
|
get("XDG_DATA_HOME");
|
|
if (!xdg_data_home) {
|
|
xdg_data_home = OS.Path.join(OS.Constants.Path.homeDir, ".local", "share");
|
|
}
|
|
|
|
this.desktopINI = OS.Path.join(xdg_data_home, "applications",
|
|
"owa-" + this.uniqueName + ".desktop");
|
|
|
|
this.installedFiles = [
|
|
OS.Path.join(this.installPath, "webapp.json"),
|
|
this.webappINI,
|
|
this.iconFile,
|
|
this.exePath,
|
|
this.desktopINI,
|
|
];
|
|
this.tempUpdatedFiles = [
|
|
OS.Path.join(this.installPath, "update", "icon.png"),
|
|
OS.Path.join(this.installPath, "update", "webapp.json"),
|
|
OS.Path.join(this.installPath, "update", "webapp.ini"),
|
|
];
|
|
this.updatedFiles = [
|
|
OS.Path.join(this.installPath, "webapp.json"),
|
|
this.webappINI,
|
|
this.iconFile,
|
|
this.desktopINI,
|
|
];
|
|
|
|
if (this.isPackaged) {
|
|
let appZipPath = OS.Path.join(this.installPath, "application.zip");
|
|
this.installedFiles.push(appZipPath);
|
|
this.tempUpdatedFiles.push(appZipPath);
|
|
this.updatedFiles.push(appZipPath);
|
|
}
|
|
|
|
this.profileRoot = this.installPath;
|
|
this.cacheRoot = OS.Path.join(OS.Constants.Path.homeDir, ".cache",
|
|
this.uniqueName);
|
|
|
|
this.cleanup = Task.async(function*() {
|
|
if (this.appProcess && this.appProcess.isRunning) {
|
|
this.appProcess.kill();
|
|
}
|
|
|
|
yield OS.File.removeDir(this.cacheRoot, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(this.profileRoot, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(this.installPath, { ignoreAbsent: true });
|
|
|
|
yield OS.File.remove(this.desktopINI, { ignoreAbsent: true });
|
|
});
|
|
} else if (WIN) {
|
|
this.installPath = OS.Path.join(OS.Constants.Path.winAppDataDir,
|
|
this.uniqueName);
|
|
this.exePath = OS.Path.join(this.installPath, aApp.name + ".exe");
|
|
|
|
this.iconFile = OS.Path.join(this.installPath, "chrome", "icons", "default", "default.ico");
|
|
|
|
this.webappINI = OS.Path.join(this.installPath, "webapp.ini");
|
|
|
|
let desktopShortcut = OS.Path.join(OS.Constants.Path.desktopDir,
|
|
aApp.name + ".lnk");
|
|
let startMenuShortcut = OS.Path.join(OS.Constants.Path.winStartMenuProgsDir,
|
|
aApp.name + ".lnk");
|
|
|
|
this.installedFiles = [
|
|
OS.Path.join(this.installPath, "webapp.json"),
|
|
this.webappINI,
|
|
OS.Path.join(this.installPath, "uninstall", "shortcuts_log.ini"),
|
|
OS.Path.join(this.installPath, "uninstall", "uninstall.log"),
|
|
OS.Path.join(this.installPath, "uninstall", "webapp-uninstaller.exe"),
|
|
this.iconFile,
|
|
this.exePath,
|
|
desktopShortcut,
|
|
startMenuShortcut,
|
|
];
|
|
this.tempUpdatedFiles = [
|
|
OS.Path.join(this.installPath, "update", "chrome", "icons", "default", "default.ico"),
|
|
OS.Path.join(this.installPath, "update", "webapp.json"),
|
|
OS.Path.join(this.installPath, "update", "webapp.ini"),
|
|
OS.Path.join(this.installPath, "update", "uninstall", "shortcuts_log.ini"),
|
|
OS.Path.join(this.installPath, "update", "uninstall", "uninstall.log"),
|
|
OS.Path.join(this.installPath, "update", "uninstall", "webapp-uninstaller.exe"),
|
|
];
|
|
this.updatedFiles = [
|
|
OS.Path.join(this.installPath, "webapp.json"),
|
|
this.webappINI,
|
|
OS.Path.join(this.installPath, "uninstall", "shortcuts_log.ini"),
|
|
OS.Path.join(this.installPath, "uninstall", "uninstall.log"),
|
|
this.iconFile,
|
|
desktopShortcut,
|
|
startMenuShortcut,
|
|
];
|
|
|
|
if (this.isPackaged) {
|
|
let appZipPath = OS.Path.join(this.installPath, "application.zip");
|
|
this.installedFiles.push(appZipPath);
|
|
this.tempUpdatedFiles.push(appZipPath);
|
|
this.updatedFiles.push(appZipPath);
|
|
}
|
|
|
|
this.profileRoot = this.installPath;
|
|
this.cacheRoot = OS.Path.join(Services.dirsvc.get("LocalAppData", Ci.nsIFile).path,
|
|
this.uniqueName);
|
|
|
|
this.cleanup = Task.async(function*() {
|
|
if (this.appProcess && this.appProcess.isRunning) {
|
|
this.appProcess.kill();
|
|
}
|
|
|
|
let uninstallKey;
|
|
try {
|
|
uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
|
|
createInstance(Ci.nsIWindowsRegKey);
|
|
uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
|
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
|
|
uninstallKey.ACCESS_WRITE);
|
|
if (uninstallKey.hasChild(this.uniqueName)) {
|
|
uninstallKey.removeChild(this.uniqueName);
|
|
}
|
|
} catch (e) {
|
|
} finally {
|
|
if (uninstallKey) {
|
|
uninstallKey.close();
|
|
}
|
|
}
|
|
|
|
let removed = false;
|
|
do {
|
|
try {
|
|
yield OS.File.removeDir(this.cacheRoot, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(this.profileRoot, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(this.installPath, { ignoreAbsent: true });
|
|
|
|
yield OS.File.remove(desktopShortcut, { ignoreAbsent: true });
|
|
yield OS.File.remove(startMenuShortcut, { ignoreAbsent: true });
|
|
|
|
removed = true;
|
|
} catch (ex if ex instanceof OS.File.Error &&
|
|
(ex.winLastError == OS.Constants.Win.ERROR_ACCESS_DENIED ||
|
|
ex.winLastError == OS.Constants.Win.ERROR_SHARING_VIOLATION ||
|
|
ex.winLastError == OS.Constants.Win.ERROR_DIR_NOT_EMPTY)) {
|
|
// Wait 100 ms before attempting to remove again.
|
|
yield wait(100);
|
|
}
|
|
} while (!removed);
|
|
});
|
|
} else if (MAC) {
|
|
this.installPath = OS.Path.join(OS.Constants.Path.homeDir,
|
|
"Applications",
|
|
aApp.name + ".app");
|
|
this.exePath = OS.Path.join(this.installPath, "Contents", "MacOS", "webapprt");
|
|
|
|
this.iconFile = OS.Path.join(this.installPath, "Contents", "Resources", "appicon.icns");
|
|
|
|
this.webappINI = OS.Path.join(this.installPath, "Contents", "MacOS", "webapp.ini");
|
|
|
|
let appProfileDir = OS.Path.join(OS.Constants.Path.macUserLibDir,
|
|
"Application Support",
|
|
this.uniqueName);
|
|
|
|
this.installedFiles = [
|
|
OS.Path.join(this.installPath, "Contents", "Info.plist"),
|
|
this.webappINI,
|
|
OS.Path.join(appProfileDir, "webapp.json"),
|
|
this.iconFile,
|
|
this.exePath,
|
|
];
|
|
this.tempUpdatedFiles = [
|
|
OS.Path.join(this.installPath, "update", "Contents", "Info.plist"),
|
|
OS.Path.join(this.installPath, "update", "Contents", "MacOS", "webapp.ini"),
|
|
OS.Path.join(this.installPath, "update", "Contents", "Resources", "appicon.icns"),
|
|
OS.Path.join(this.installPath, "update", "webapp.json")
|
|
];
|
|
this.updatedFiles = [
|
|
OS.Path.join(this.installPath, "Contents", "Info.plist"),
|
|
this.webappINI,
|
|
OS.Path.join(appProfileDir, "webapp.json"),
|
|
this.iconFile,
|
|
];
|
|
|
|
if (this.isPackaged) {
|
|
let appZipPath = OS.Path.join(this.installPath, "Contents", "Resources", "application.zip");
|
|
this.installedFiles.push(appZipPath);
|
|
this.tempUpdatedFiles.push(appZipPath);
|
|
this.updatedFiles.push(appZipPath);
|
|
}
|
|
|
|
this.profileRoot = appProfileDir;
|
|
this.cacheRoot = OS.Path.join(OS.Constants.Path.macUserLibDir, "Caches",
|
|
this.uniqueName);
|
|
|
|
this.cleanup = Task.async(function*() {
|
|
if (this.appProcess && this.appProcess.isRunning) {
|
|
this.appProcess.kill();
|
|
}
|
|
|
|
yield OS.File.removeDir(this.cacheRoot, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(this.profileRoot, { ignoreAbsent: true });
|
|
|
|
if (this.trashDir) {
|
|
yield OS.File.removeDir(this.trashDir, { ignoreAbsent: true });
|
|
}
|
|
|
|
yield OS.File.removeDir(this.installPath, { ignoreAbsent: true });
|
|
|
|
yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
|
|
});
|
|
}
|
|
|
|
this.profilesIni = OS.Path.join(this.profileRoot, "profiles.ini");
|
|
|
|
let profileDir;
|
|
|
|
Object.defineProperty(this, "profileDir", {
|
|
get: function() {
|
|
if (!profileDir && this.profileRelPath) {
|
|
return getFile.apply(null, [this.profileRoot].concat(this.profileRelPath.split("/")));
|
|
}
|
|
|
|
return profileDir;
|
|
},
|
|
set: function(aVal) {
|
|
profileDir = aVal;
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, "cacheDir", {
|
|
get: function() {
|
|
if (!this.profileRelPath) {
|
|
return null;
|
|
}
|
|
|
|
return getFile.apply(null, [this.cacheRoot].concat(this.profileRelPath.split("/")));
|
|
},
|
|
});
|
|
|
|
Object.defineProperty(this, "profileRelPath", {
|
|
get: function() {
|
|
// If the profileDir was set by someone else, use its leafName
|
|
// as the profile name.
|
|
if (profileDir) {
|
|
return profileDir.leafName;
|
|
}
|
|
|
|
// Otherwise, read profiles.ini to get the profile directory
|
|
try {
|
|
let iniParser = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
|
getService(Ci.nsIINIParserFactory).
|
|
createINIParser(getFile(this.profilesIni));
|
|
return iniParser.getString("Profile0", "Path");
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function buildAppPackage(aManifest, aIconFile) {
|
|
let zipFile = getFile(OS.Constants.Path.profileDir, "sample.zip");
|
|
|
|
let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
|
|
zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
|
|
zipWriter.addEntryFile("index.html",
|
|
Ci.nsIZipWriter.COMPRESSION_NONE,
|
|
getFile(getTestFilePath("data/app/index.html")),
|
|
false);
|
|
|
|
let manifestJSON = JSON.stringify(aManifest);
|
|
let manStream = Cc["@mozilla.org/io/string-input-stream;1"].
|
|
createInstance(Ci.nsIStringInputStream);
|
|
manStream.setData(manifestJSON, manifestJSON.length);
|
|
zipWriter.addEntryStream("manifest.webapp", Date.now(),
|
|
Ci.nsIZipWriter.COMPRESSION_NONE,
|
|
manStream, false);
|
|
|
|
if (aIconFile) {
|
|
zipWriter.addEntryFile(aIconFile.leafName,
|
|
Ci.nsIZipWriter.COMPRESSION_NONE,
|
|
aIconFile,
|
|
false);
|
|
}
|
|
|
|
zipWriter.close();
|
|
|
|
return zipFile.path;
|
|
}
|
|
|
|
function xhrRequest(aQueryString) {
|
|
let deferred = Promise.defer();
|
|
|
|
var xhr = new XMLHttpRequest();
|
|
|
|
xhr.addEventListener("load", function() {
|
|
deferred.resolve(xhr.responseText);
|
|
});
|
|
|
|
xhr.addEventListener("error", aError => deferred.reject(aError));
|
|
xhr.addEventListener("abort", aError => deferred.reject(aError));
|
|
|
|
xhr.open('GET', 'http://test/chrome/toolkit/webapps/tests/app.sjs' + aQueryString, true);
|
|
xhr.send();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function wasAppSJSAccessed() {
|
|
return xhrRequest('?testreq').then((aResponseText) => {
|
|
return (aResponseText == 'done') ? true : false;
|
|
});
|
|
}
|
|
|
|
function setState(aVar, aState) {
|
|
return xhrRequest('?set' + aVar + '=' + aState).then((aResponseText) => {
|
|
is(aResponseText, "OK", "set" + aVar + " OK");
|
|
});
|
|
}
|
|
|
|
function getState(aVar) {
|
|
return xhrRequest('?get' + aVar);
|
|
}
|
|
|
|
function generateDataURI(aFile) {
|
|
var contentType = Cc["@mozilla.org/mime;1"].
|
|
getService(Ci.nsIMIMEService).
|
|
getTypeFromFile(aFile);
|
|
|
|
var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
|
createInstance(Ci.nsIFileInputStream);
|
|
inputStream.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
|
|
|
var stream = Cc["@mozilla.org/binaryinputstream;1"].
|
|
createInstance(Ci.nsIBinaryInputStream);
|
|
stream.setInputStream(inputStream);
|
|
|
|
return "data:" + contentType + ";base64," +
|
|
btoa(stream.readBytes(stream.available()));
|
|
}
|
|
|
|
function confirmNextPopup() {
|
|
let popupPanel = window.top.QueryInterface(Ci.nsIInterfaceRequestor).
|
|
getInterface(Ci.nsIWebNavigation).
|
|
QueryInterface(Ci.nsIDocShell).
|
|
chromeEventHandler.ownerDocument.defaultView.
|
|
PopupNotifications.panel;
|
|
|
|
popupPanel.addEventListener("popupshown", function onPopupShown() {
|
|
popupPanel.removeEventListener("popupshown", onPopupShown, false);
|
|
this.childNodes[0].button.doCommand();
|
|
}, false);
|
|
}
|
|
|
|
var readJSON = Task.async(function*(aPath) {
|
|
let decoder = new TextDecoder();
|
|
let data = yield OS.File.read(aPath);
|
|
return JSON.parse(decoder.decode(data));
|
|
});
|
|
|
|
var setMacRootInstallDir = Task.async(function*(aPath) {
|
|
let oldRootInstallDir = NativeApp.prototype._rootInstallDir;
|
|
|
|
NativeApp.prototype._rootInstallDir = OS.Path.join(OS.Constants.Path.homeDir,
|
|
"Applications");
|
|
yield OS.File.makeDir(NativeApp.prototype._rootInstallDir,
|
|
{ ignoreExisting: true });
|
|
|
|
SimpleTest.registerCleanupFunction(function() {
|
|
NativeApp.prototype._rootInstallDir = oldRootInstallDir;
|
|
});
|
|
});
|
|
|
|
var writeToFile = Task.async(function*(aPath, aData) {
|
|
let data = new TextEncoder().encode(aData);
|
|
|
|
let file;
|
|
try {
|
|
file = yield OS.File.open(aPath, { truncate: true, write: true }, { unixMode: 0o777 });
|
|
yield file.write(data);
|
|
} finally {
|
|
yield file.close();
|
|
}
|
|
});
|
|
|
|
// We need to mock the Alerts service, otherwise the alert that is shown
|
|
// at the end of an installation makes the test leak the app's icon.
|
|
|
|
const CID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
|
|
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
|
|
const ALERTS_SERVICE_CID = Components.ID(Cc[ALERTS_SERVICE_CONTRACT_ID].number);
|
|
|
|
var AlertsService = {
|
|
classID: Components.ID(CID),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory,
|
|
Ci.nsIAlertsService]),
|
|
|
|
createInstance: function(aOuter, aIID) {
|
|
if (aOuter) {
|
|
throw Cr.NS_ERROR_NO_AGGREGATION;
|
|
}
|
|
|
|
return this.QueryInterface(aIID);
|
|
},
|
|
|
|
init: function() {
|
|
Components.manager.nsIComponentRegistrar.registerFactory(this.classID,
|
|
"", ALERTS_SERVICE_CONTRACT_ID, this);
|
|
},
|
|
|
|
restore: function() {
|
|
Components.manager.nsIComponentRegistrar.registerFactory(ALERTS_SERVICE_CID,
|
|
"", ALERTS_SERVICE_CONTRACT_ID, null);
|
|
},
|
|
|
|
showAlertNotification: function() {
|
|
},
|
|
};
|
|
|
|
AlertsService.init();
|
|
|
|
SimpleTest.registerCleanupFunction(() => {
|
|
AlertsService.restore();
|
|
});
|
|
|
|
function prepareEnv(cb) {
|
|
SpecialPowers.pushPrefEnv({"set":[["dom.mozApps.debug", true]]}, cb);
|
|
}
|