mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 898647 - Backend for app updates. r=myk,fabrice
--HG-- rename : browser/modules/webappsUI.jsm => browser/modules/WebappManager.jsm rename : toolkit/webapps/WebappsInstaller.jsm => toolkit/webapps/NativeApp.jsm rename : webapprt/WebappsHandler.jsm => webapprt/WebappManager.jsm
This commit is contained in:
parent
32dd78ae54
commit
97ab2c87f2
@ -38,8 +38,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
|
||||
"resource://gre/modules/BookmarkJSONUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "webappsUI",
|
||||
"resource:///modules/webappsUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
|
||||
"resource:///modules/WebappManager.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
|
||||
"resource://gre/modules/PageThumbs.jsm");
|
||||
@ -468,7 +468,7 @@ BrowserGlue.prototype = {
|
||||
|
||||
this._syncSearchEngines();
|
||||
|
||||
webappsUI.init();
|
||||
WebappManager.init();
|
||||
PageThumbs.init();
|
||||
NewTabUtils.init();
|
||||
BrowserNewTabPreloader.init();
|
||||
@ -654,7 +654,7 @@ BrowserGlue.prototype = {
|
||||
|
||||
BrowserNewTabPreloader.uninit();
|
||||
CustomizationTabPreloader.uninit();
|
||||
webappsUI.uninit();
|
||||
WebappManager.uninit();
|
||||
SignInToWebsiteUX.uninit();
|
||||
webrtcUI.uninit();
|
||||
},
|
||||
|
@ -798,7 +798,7 @@ bin/libfreebl_32int64_3.so
|
||||
@BINPATH@/webapprt/defaults/preferences/prefs.js
|
||||
@BINPATH@/webapprt/modules/Startup.jsm
|
||||
@BINPATH@/webapprt/modules/WebappRT.jsm
|
||||
@BINPATH@/webapprt/modules/WebappsHandler.jsm
|
||||
@BINPATH@/webapprt/modules/WebappManager.jsm
|
||||
@BINPATH@/webapprt/modules/RemoteDebugger.jsm
|
||||
@BINPATH@/webapprt/modules/WebRTCHandler.jsm
|
||||
#endif
|
||||
|
@ -2,7 +2,7 @@
|
||||
* 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/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["webappsUI"];
|
||||
this.EXPORTED_SYMBOLS = ["WebappManager"];
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
@ -12,7 +12,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/WebappsInstaller.jsm");
|
||||
Cu.import("resource://gre/modules/NativeApp.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
@ -21,11 +21,11 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
||||
"@mozilla.org/childprocessmessagemanager;1",
|
||||
"nsIMessageSender");
|
||||
|
||||
this.webappsUI = {
|
||||
this.WebappManager = {
|
||||
// List of promises for in-progress installations
|
||||
installations: {},
|
||||
|
||||
init: function webappsUI_init() {
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "webapps-ask-install", false);
|
||||
Services.obs.addObserver(this, "webapps-launch", false);
|
||||
Services.obs.addObserver(this, "webapps-uninstall", false);
|
||||
@ -34,7 +34,7 @@ this.webappsUI = {
|
||||
cpmm.addMessageListener("Webapps:UpdateState", this);
|
||||
},
|
||||
|
||||
uninit: function webappsUI_uninit() {
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(this, "webapps-ask-install");
|
||||
Services.obs.removeObserver(this, "webapps-launch");
|
||||
Services.obs.removeObserver(this, "webapps-uninstall");
|
||||
@ -71,7 +71,7 @@ this.webappsUI = {
|
||||
}
|
||||
},
|
||||
|
||||
observe: function webappsUI_observe(aSubject, aTopic, aData) {
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
data.mm = aSubject;
|
||||
|
||||
@ -108,6 +108,8 @@ this.webappsUI = {
|
||||
|
||||
let bundle = chromeWin.gNavigatorBundle;
|
||||
|
||||
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
|
||||
|
||||
let notification;
|
||||
|
||||
let mainAction = {
|
||||
@ -128,7 +130,7 @@ this.webappsUI = {
|
||||
|
||||
let manifestURL = aData.app.manifestURL;
|
||||
|
||||
let cleanup = (ex) => {
|
||||
let cleanup = () => {
|
||||
popupProgressContent.removeChild(progressMeter);
|
||||
delete this.installations[manifestURL];
|
||||
if (Object.getOwnPropertyNames(this.installations).length == 0) {
|
||||
@ -142,38 +144,36 @@ this.webappsUI = {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
let app = WebappsInstaller.init(aData);
|
||||
|
||||
if (app) {
|
||||
let localDir = null;
|
||||
if (app.appProfile) {
|
||||
localDir = app.appProfile.localDir;
|
||||
}
|
||||
|
||||
DOMApplicationRegistry.confirmInstall(aData, localDir,
|
||||
(aManifest, aZipPath) => {
|
||||
Task.spawn(function() {
|
||||
try {
|
||||
yield WebappsInstaller.install(aData, aManifest, aZipPath);
|
||||
yield this.installations[manifestURL].promise;
|
||||
installationSuccessNotification(aData, app, bundle);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error installing webapp: " + ex);
|
||||
// TODO: Notify user that the installation has failed
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}.bind(this));
|
||||
});
|
||||
} else {
|
||||
let nativeApp = new NativeApp(aData.app, jsonManifest,
|
||||
aData.app.categories);
|
||||
let localDir;
|
||||
try {
|
||||
localDir = nativeApp.createProfile();
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error installing webapp: " + ex);
|
||||
DOMApplicationRegistry.denyInstall(aData);
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
DOMApplicationRegistry.confirmInstall(aData, localDir,
|
||||
(aManifest, aZipPath) => Task.spawn((function*() {
|
||||
try {
|
||||
yield nativeApp.install(aManifest, aZipPath);
|
||||
yield this.installations[manifestURL].promise;
|
||||
notifyInstallSuccess(aData.app, nativeApp, bundle);
|
||||
} catch (ex) {
|
||||
Cu.reportError("Error installing webapp: " + ex);
|
||||
// TODO: Notify user that the installation has failed
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}).bind(this))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let requestingURI = chromeWin.makeURI(aData.from);
|
||||
let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest;
|
||||
let manifest = new ManifestHelper(jsonManifest, aData.app.origin);
|
||||
|
||||
let host;
|
||||
@ -195,11 +195,11 @@ this.webappsUI = {
|
||||
}
|
||||
}
|
||||
|
||||
function installationSuccessNotification(aData, app, aBundle) {
|
||||
function notifyInstallSuccess(aApp, aNativeApp, aBundle) {
|
||||
let launcher = {
|
||||
observe: function(aSubject, aTopic) {
|
||||
if (aTopic == "alertclickcallback") {
|
||||
WebappOSUtils.launch(aData.app);
|
||||
WebappOSUtils.launch(aApp);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -208,9 +208,9 @@ function installationSuccessNotification(aData, app, aBundle) {
|
||||
let notifier = Cc["@mozilla.org/alerts-service;1"].
|
||||
getService(Ci.nsIAlertsService);
|
||||
|
||||
notifier.showAlertNotification(app.iconURI.spec,
|
||||
notifier.showAlertNotification(aNativeApp.iconURI.spec,
|
||||
aBundle.getString("webapps.install.success"),
|
||||
app.appNameAsFilename,
|
||||
aNativeApp.appNameAsFilename,
|
||||
true, null, launcher);
|
||||
} catch (ex) {}
|
||||
}
|
@ -20,7 +20,7 @@ EXTRA_JS_MODULES += [
|
||||
'SitePermissions.jsm',
|
||||
'Social.jsm',
|
||||
'TabCrashReporter.jsm',
|
||||
'webappsUI.jsm',
|
||||
'WebappManager.jsm',
|
||||
'webrtcUI.jsm',
|
||||
]
|
||||
|
||||
|
@ -208,7 +208,7 @@ this.AppsUtils = {
|
||||
isCoreApp = app.basePath == this.getCoreAppsBasePath();
|
||||
#endif
|
||||
debug(app.basePath + " isCoreApp: " + isCoreApp);
|
||||
return { "path": WebappOSUtils.getInstallPath(app),
|
||||
return { "path": WebappOSUtils.getPackagePath(app),
|
||||
"isCoreApp": isCoreApp };
|
||||
},
|
||||
|
||||
|
@ -77,6 +77,8 @@ const MIN_PROGRESS_EVENT_DELAY = 1500;
|
||||
|
||||
const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
|
||||
|
||||
const chromeWindowType = WEBAPP_RUNTIME ? "webapprt:webapp" : "navigator:browser";
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
return NetUtil;
|
||||
@ -133,6 +135,7 @@ this.DOMApplicationRegistry = {
|
||||
webapps: { },
|
||||
children: [ ],
|
||||
allAppsLaunchable: false,
|
||||
_updateHandlers: [ ],
|
||||
|
||||
init: function() {
|
||||
this.messages = ["Webapps:Install", "Webapps:Uninstall",
|
||||
@ -1196,6 +1199,23 @@ this.DOMApplicationRegistry = {
|
||||
});
|
||||
},
|
||||
|
||||
registerUpdateHandler: function(aHandler) {
|
||||
this._updateHandlers.push(aHandler);
|
||||
},
|
||||
|
||||
unregisterUpdateHandler: function(aHandler) {
|
||||
let index = this._updateHandlers.indexOf(aHandler);
|
||||
if (index != -1) {
|
||||
this._updateHandlers.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
notifyUpdateHandlers: function(aApp, aManifest, aZipPath) {
|
||||
for (let updateHandler of this._updateHandlers) {
|
||||
updateHandler(aApp, aManifest, aZipPath);
|
||||
}
|
||||
},
|
||||
|
||||
_getAppDir: function(aId) {
|
||||
return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
||||
},
|
||||
@ -1487,6 +1507,13 @@ this.DOMApplicationRegistry = {
|
||||
this._saveApps().then(() => {
|
||||
// Update the handlers and permissions for this app.
|
||||
this.updateAppHandlers(aOldManifest, aData, app);
|
||||
|
||||
this._loadJSONAsync(staged.path).then((aUpdateManifest) => {
|
||||
let appObject = AppsUtils.cloneAppObject(app);
|
||||
appObject.updateManifest = aUpdateManifest;
|
||||
this.notifyUpdateHandlers(appObject, aData, appFile.path);
|
||||
});
|
||||
|
||||
if (supportUseCurrentProfile()) {
|
||||
PermissionsInstaller.installPermissions(
|
||||
{ manifest: aData,
|
||||
@ -1897,6 +1924,8 @@ this.DOMApplicationRegistry = {
|
||||
if (aNewManifest) {
|
||||
this.updateAppHandlers(aOldManifest, aNewManifest, aApp);
|
||||
|
||||
this.notifyUpdateHandlers(AppsUtils.cloneAppObject(aApp), aNewManifest);
|
||||
|
||||
// Store the new manifest.
|
||||
let dir = this._getAppDir(aId).path;
|
||||
let manFile = OS.Path.join(dir, "manifest.webapp");
|
||||
@ -2669,7 +2698,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
|
||||
_ensureSufficientStorage: function(aNewApp) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let navigator = Services.wm.getMostRecentWindow("navigator:browser")
|
||||
let navigator = Services.wm.getMostRecentWindow(chromeWindowType)
|
||||
.navigator;
|
||||
let deviceStorage = null;
|
||||
|
||||
|
@ -63,7 +63,7 @@ function test() {
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
// The installation may have created a XUL alert window
|
||||
// (see webappsUI.installationSuccessNotification).
|
||||
// (see notifyInstallSuccess in WebappManager.jsm).
|
||||
// It need to be closed before the test finishes.
|
||||
var browsers = windowMediator.getEnumerator('alert:alert');
|
||||
while (browsers.hasMoreElements()) {
|
||||
|
335
toolkit/webapps/LinuxNativeApp.js
Normal file
335
toolkit/webapps/LinuxNativeApp.js
Normal file
@ -0,0 +1,335 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Constructor for the Linux native app shell
|
||||
*
|
||||
* @param aApp {Object} the app object provided to the install function
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aCategories {Array} array of app categories
|
||||
* @param aRegistryDir {String} (optional) path to the registry
|
||||
*/
|
||||
function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
|
||||
CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
|
||||
|
||||
this.iconFile = "icon.png";
|
||||
this.webapprt = "webapprt-stub";
|
||||
this.configJson = "webapp.json";
|
||||
this.webappINI = "webapp.ini";
|
||||
this.zipFile = "application.zip";
|
||||
|
||||
this.backupFiles = [ this.iconFile, this.configJson, this.webappINI ];
|
||||
if (this.isPackaged) {
|
||||
this.backupFiles.push(this.zipFile);
|
||||
}
|
||||
|
||||
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(HOME_DIR, ".local", "share");
|
||||
}
|
||||
|
||||
// The desktop file name is: "owa-" + sanitized app name +
|
||||
// "-" + manifest url hash.
|
||||
this.desktopINI = OS.Path.join(xdg_data_home, "applications",
|
||||
"owa-" + this.uniqueName + ".desktop");
|
||||
}
|
||||
|
||||
NativeApp.prototype = {
|
||||
__proto__: CommonNativeApp.prototype,
|
||||
|
||||
/**
|
||||
* Creates a native installation of the web app in the OS
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
install: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the application is already installed, this is a reinstallation.
|
||||
if (WebappOSUtils.getInstallPath(this.app)) {
|
||||
return yield this.prepareUpdate(aManifest, aZipPath);
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
// The installation directory name is: sanitized app name +
|
||||
// "-" + manifest url hash.
|
||||
let installDir = OS.Path.join(HOME_DIR, "." + this.uniqueName);
|
||||
|
||||
let dir = getFile(TMP_DIR, this.uniqueName);
|
||||
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
||||
let tmpDir = dir.path;
|
||||
|
||||
// Create the installation in a temporary directory.
|
||||
try {
|
||||
this._copyPrebuiltFiles(tmpDir);
|
||||
yield this._createConfigFiles(tmpDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(tmpDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Apply the installation.
|
||||
this._removeInstallation(true, installDir);
|
||||
|
||||
try {
|
||||
yield this._applyTempInstallation(tmpDir, installDir);
|
||||
} catch (ex) {
|
||||
this._removeInstallation(false, installDir);
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates an update in a temporary directory to be applied later.
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
prepareUpdate: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
let installDir = WebappOSUtils.getInstallPath(this.app);
|
||||
if (!installDir) {
|
||||
throw ERR_NOT_INSTALLED;
|
||||
}
|
||||
|
||||
let baseName = OS.Path.basename(installDir)
|
||||
let oldUniqueName = baseName.substring(1, baseName.length);
|
||||
if (this.uniqueName != oldUniqueName) {
|
||||
// Bug 919799: If the app is still in the registry, migrate its data to
|
||||
// the new format.
|
||||
throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
|
||||
}
|
||||
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(updateDir);
|
||||
|
||||
try {
|
||||
yield this._createConfigFiles(updateDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(updateDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Applies an update.
|
||||
*/
|
||||
applyUpdate: Task.async(function*() {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
let installDir = WebappOSUtils.getInstallPath(this.app);
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
|
||||
let backupDir = yield this._backupInstallation(installDir);
|
||||
|
||||
try {
|
||||
yield this._applyTempInstallation(updateDir, installDir);
|
||||
} catch (ex) {
|
||||
yield this._restoreInstallation(backupDir, installDir);
|
||||
throw ex;
|
||||
} finally {
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
}
|
||||
}),
|
||||
|
||||
_applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
|
||||
yield moveDirectory(aTmpDir, aInstallDir);
|
||||
|
||||
this._createSystemFiles(aInstallDir);
|
||||
}),
|
||||
|
||||
_removeInstallation: function(keepProfile, aInstallDir) {
|
||||
let filesToRemove = [this.desktopINI];
|
||||
|
||||
if (keepProfile) {
|
||||
for (let filePath of this.backupFiles) {
|
||||
filesToRemove.push(OS.Path.join(aInstallDir, filePath));
|
||||
}
|
||||
|
||||
filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
|
||||
} else {
|
||||
filesToRemove.push(aInstallDir);
|
||||
}
|
||||
|
||||
removeFiles(filesToRemove);
|
||||
},
|
||||
|
||||
_backupInstallation: Task.async(function*(aInstallDir) {
|
||||
let backupDir = OS.Path.join(aInstallDir, "backup");
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(backupDir);
|
||||
|
||||
for (let filePath of this.backupFiles) {
|
||||
yield OS.File.move(OS.Path.join(aInstallDir, filePath),
|
||||
OS.Path.join(backupDir, filePath));
|
||||
}
|
||||
|
||||
return backupDir;
|
||||
}),
|
||||
|
||||
_restoreInstallation: function(aBackupDir, aInstallDir) {
|
||||
return moveDirectory(aBackupDir, aInstallDir);
|
||||
},
|
||||
|
||||
_copyPrebuiltFiles: function(aDir) {
|
||||
let destDir = getFile(aDir);
|
||||
let stub = getFile(this.runtimeFolder, this.webapprt);
|
||||
stub.copyTo(destDir, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Translate marketplace categories to freedesktop.org categories.
|
||||
*
|
||||
* @link http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry
|
||||
*
|
||||
* @return an array of categories
|
||||
*/
|
||||
_translateCategories: function() {
|
||||
let translations = {
|
||||
"books": "Education;Literature",
|
||||
"business": "Finance",
|
||||
"education": "Education",
|
||||
"entertainment": "Amusement",
|
||||
"sports": "Sports",
|
||||
"games": "Game",
|
||||
"health-fitness": "MedicalSoftware",
|
||||
"lifestyle": "Amusement",
|
||||
"music": "Audio;Music",
|
||||
"news-weather": "News",
|
||||
"photo-video": "Video;AudioVideo;Photography",
|
||||
"productivity": "Office",
|
||||
"shopping": "Amusement",
|
||||
"social": "Chat",
|
||||
"travel": "Amusement",
|
||||
"reference": "Science;Education;Documentation",
|
||||
"maps-navigation": "Maps",
|
||||
"utilities": "Utility"
|
||||
};
|
||||
|
||||
// The trailing semicolon is needed as written in the freedesktop specification
|
||||
let categories = "";
|
||||
for (let category of this.categories) {
|
||||
let catLower = category.toLowerCase();
|
||||
if (catLower in translations) {
|
||||
categories += translations[catLower] + ";";
|
||||
}
|
||||
}
|
||||
|
||||
return categories;
|
||||
},
|
||||
|
||||
_createConfigFiles: function(aDir) {
|
||||
// ${InstallDir}/webapp.json
|
||||
yield writeToFile(OS.Path.join(aDir, this.configJson),
|
||||
JSON.stringify(this.webappJson));
|
||||
|
||||
let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
|
||||
|
||||
// ${InstallDir}/webapp.ini
|
||||
let webappINIfile = getFile(aDir, this.webappINI);
|
||||
|
||||
let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
||||
getService(Ci.nsIINIParserFactory).
|
||||
createINIParser(webappINIfile).
|
||||
QueryInterface(Ci.nsIINIParserWriter);
|
||||
writer.setString("Webapp", "Name", this.appName);
|
||||
writer.setString("Webapp", "Profile", this.uniqueName);
|
||||
writer.setString("Webapp", "UninstallMsg", webappsBundle.formatStringFromName("uninstall.notification", [this.appName], 1));
|
||||
writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
|
||||
writer.writeFile();
|
||||
},
|
||||
|
||||
_createSystemFiles: function(aInstallDir) {
|
||||
let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
|
||||
|
||||
let webapprtPath = OS.Path.join(aInstallDir, this.webapprt);
|
||||
|
||||
// $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
|
||||
let desktopINIfile = getFile(this.desktopINI);
|
||||
|
||||
let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
||||
getService(Ci.nsIINIParserFactory).
|
||||
createINIParser(desktopINIfile).
|
||||
QueryInterface(Ci.nsIINIParserWriter);
|
||||
writer.setString("Desktop Entry", "Name", this.appName);
|
||||
writer.setString("Desktop Entry", "Comment", this.shortDescription);
|
||||
writer.setString("Desktop Entry", "Exec", '"' + webapprtPath + '"');
|
||||
writer.setString("Desktop Entry", "Icon", OS.Path.join(aInstallDir,
|
||||
this.iconFile));
|
||||
writer.setString("Desktop Entry", "Type", "Application");
|
||||
writer.setString("Desktop Entry", "Terminal", "false");
|
||||
|
||||
let categories = this._translateCategories();
|
||||
if (categories)
|
||||
writer.setString("Desktop Entry", "Categories", categories);
|
||||
|
||||
writer.setString("Desktop Entry", "Actions", "Uninstall;");
|
||||
writer.setString("Desktop Action Uninstall", "Name", webappsBundle.GetStringFromName("uninstall.label"));
|
||||
writer.setString("Desktop Action Uninstall", "Exec", webapprtPath + " -remove");
|
||||
|
||||
writer.writeFile();
|
||||
|
||||
desktopINIfile.permissions = PERMS_FILE | OS.Constants.libc.S_IXUSR;
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the icon from the imageStream as retrieved from
|
||||
* the URL by getIconForApp().
|
||||
*
|
||||
* @param aMimeType ahe icon mimetype
|
||||
* @param aImageStream the stream for the image data
|
||||
* @param aDir the directory where the icon should be stored
|
||||
*/
|
||||
_processIcon: function(aMimeType, aImageStream, aDir) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let imgTools = Cc["@mozilla.org/image/tools;1"].
|
||||
createInstance(Ci.imgITools);
|
||||
|
||||
let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
|
||||
let iconStream = imgTools.encodeImage(imgContainer, "image/png");
|
||||
|
||||
let iconFile = getFile(aDir, this.iconFile);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(iconFile);
|
||||
NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
|
||||
if (Components.isSuccessCode(aResult)) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject("Failure copying icon: " + aResult);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
308
toolkit/webapps/MacNativeApp.js
Normal file
308
toolkit/webapps/MacNativeApp.js
Normal file
@ -0,0 +1,308 @@
|
||||
/* 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/. */
|
||||
|
||||
const USER_LIB_DIR = OS.Constants.Path.macUserLibDir;
|
||||
const LOCAL_APP_DIR = OS.Constants.Path.macLocalApplicationsDir;
|
||||
|
||||
/**
|
||||
* Constructor for the Mac native app shell
|
||||
*
|
||||
* @param aApp {Object} the app object provided to the install function
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aCategories {Array} array of app categories
|
||||
* @param aRegistryDir {String} (optional) path to the registry
|
||||
*/
|
||||
function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
|
||||
CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
|
||||
|
||||
// The ${ProfileDir} is: sanitized app name + "-" + manifest url hash
|
||||
this.appProfileDir = OS.Path.join(USER_LIB_DIR, "Application Support",
|
||||
this.uniqueName);
|
||||
this.configJson = "webapp.json";
|
||||
|
||||
this.contentsDir = "Contents";
|
||||
this.macOSDir = OS.Path.join(this.contentsDir, "MacOS");
|
||||
this.resourcesDir = OS.Path.join(this.contentsDir, "Resources");
|
||||
this.iconFile = OS.Path.join(this.resourcesDir, "appicon.icns");
|
||||
this.zipFile = OS.Path.join(this.resourcesDir, "application.zip");
|
||||
}
|
||||
|
||||
NativeApp.prototype = {
|
||||
__proto__: CommonNativeApp.prototype,
|
||||
|
||||
/**
|
||||
* Creates a native installation of the web app in the OS
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
install: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the application is already installed, this is a reinstallation.
|
||||
if (WebappOSUtils.getInstallPath(this.app)) {
|
||||
return yield this.prepareUpdate(aManifest, aZipPath);
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
let localAppDir = getFile(LOCAL_APP_DIR);
|
||||
if (!localAppDir.isWritable()) {
|
||||
throw("Not enough privileges to install apps");
|
||||
}
|
||||
|
||||
let destinationName = yield getAvailableFileName([ LOCAL_APP_DIR ],
|
||||
this.appNameAsFilename,
|
||||
".app");
|
||||
|
||||
let installDir = OS.Path.join(LOCAL_APP_DIR, destinationName);
|
||||
|
||||
let dir = getFile(TMP_DIR, this.appNameAsFilename + ".app");
|
||||
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
||||
let tmpDir = dir.path;
|
||||
|
||||
try {
|
||||
yield this._createDirectoryStructure(tmpDir);
|
||||
this._copyPrebuiltFiles(tmpDir);
|
||||
yield this._createConfigFiles(tmpDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(tmpDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
|
||||
this._removeInstallation(true, installDir);
|
||||
|
||||
try {
|
||||
// Move the temp installation directory to the /Applications directory
|
||||
yield this._applyTempInstallation(tmpDir, installDir);
|
||||
} catch (ex) {
|
||||
this._removeInstallation(false, installDir);
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates an update in a temporary directory to be applied later.
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
prepareUpdate: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
let [ oldUniqueName, installDir ] = WebappOSUtils.getLaunchTarget(this.app);
|
||||
if (!installDir) {
|
||||
throw ERR_NOT_INSTALLED;
|
||||
}
|
||||
|
||||
if (this.uniqueName != oldUniqueName) {
|
||||
// Bug 919799: If the app is still in the registry, migrate its data to
|
||||
// the new format.
|
||||
throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
|
||||
}
|
||||
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(updateDir);
|
||||
|
||||
try {
|
||||
yield this._createDirectoryStructure(updateDir);
|
||||
this._copyPrebuiltFiles(updateDir);
|
||||
yield this._createConfigFiles(updateDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(updateDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Applies an update.
|
||||
*/
|
||||
applyUpdate: Task.async(function*() {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
let installDir = WebappOSUtils.getInstallPath(this.app);
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
|
||||
let backupDir = yield this._backupInstallation(installDir);
|
||||
|
||||
try {
|
||||
// Move the update directory to the /Applications directory
|
||||
yield this._applyTempInstallation(updateDir, installDir);
|
||||
} catch (ex) {
|
||||
yield this._restoreInstallation(backupDir, installDir);
|
||||
throw ex;
|
||||
} finally {
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
}
|
||||
}),
|
||||
|
||||
_applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
|
||||
yield OS.File.move(OS.Path.join(aTmpDir, this.configJson),
|
||||
OS.Path.join(this.appProfileDir, this.configJson));
|
||||
|
||||
yield moveDirectory(aTmpDir, aInstallDir);
|
||||
}),
|
||||
|
||||
_removeInstallation: function(keepProfile, aInstallDir) {
|
||||
let filesToRemove = [ aInstallDir ];
|
||||
|
||||
if (!keepProfile) {
|
||||
filesToRemove.push(this.appProfileDir);
|
||||
}
|
||||
|
||||
removeFiles(filesToRemove);
|
||||
},
|
||||
|
||||
_backupInstallation: Task.async(function*(aInstallDir) {
|
||||
let backupDir = OS.Path.join(aInstallDir, "backup");
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(backupDir);
|
||||
|
||||
yield moveDirectory(OS.Path.join(aInstallDir, this.contentsDir),
|
||||
backupDir);
|
||||
yield OS.File.move(OS.Path.join(this.appProfileDir, this.configJson),
|
||||
OS.Path.join(backupDir, this.configJson));
|
||||
|
||||
return backupDir;
|
||||
}),
|
||||
|
||||
_restoreInstallation: Task.async(function*(aBackupDir, aInstallDir) {
|
||||
yield OS.File.move(OS.Path.join(aBackupDir, this.configJson),
|
||||
OS.Path.join(this.appProfileDir, this.configJson));
|
||||
yield moveDirectory(aBackupDir,
|
||||
OS.Path.join(aInstallDir, this.contentsDir));
|
||||
}),
|
||||
|
||||
_createDirectoryStructure: Task.async(function*(aDir) {
|
||||
yield OS.File.makeDir(this.appProfileDir,
|
||||
{ unixMode: PERMS_DIRECTORY, ignoreExisting: true });
|
||||
|
||||
yield OS.File.makeDir(OS.Path.join(aDir, this.contentsDir),
|
||||
{ unixMode: PERMS_DIRECTORY, ignoreExisting: true });
|
||||
|
||||
yield OS.File.makeDir(OS.Path.join(aDir, this.macOSDir),
|
||||
{ unixMode: PERMS_DIRECTORY, ignoreExisting: true });
|
||||
|
||||
yield OS.File.makeDir(OS.Path.join(aDir, this.resourcesDir),
|
||||
{ unixMode: PERMS_DIRECTORY, ignoreExisting: true });
|
||||
}),
|
||||
|
||||
_copyPrebuiltFiles: function(aDir) {
|
||||
let destDir = getFile(aDir, this.macOSDir);
|
||||
let stub = getFile(this.runtimeFolder, "webapprt-stub");
|
||||
stub.copyTo(destDir, "webapprt");
|
||||
},
|
||||
|
||||
_createConfigFiles: function(aDir) {
|
||||
// ${ProfileDir}/webapp.json
|
||||
yield writeToFile(OS.Path.join(aDir, this.configJson),
|
||||
JSON.stringify(this.webappJson));
|
||||
|
||||
// ${InstallDir}/Contents/MacOS/webapp.ini
|
||||
let applicationINI = getFile(aDir, this.macOSDir, "webapp.ini");
|
||||
|
||||
let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
||||
getService(Ci.nsIINIParserFactory).
|
||||
createINIParser(applicationINI).
|
||||
QueryInterface(Ci.nsIINIParserWriter);
|
||||
writer.setString("Webapp", "Name", this.appName);
|
||||
writer.setString("Webapp", "Profile", this.uniqueName);
|
||||
writer.writeFile();
|
||||
applicationINI.permissions = PERMS_FILE;
|
||||
|
||||
// ${InstallDir}/Contents/Info.plist
|
||||
let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
|
||||
<plist version="1.0">\n\
|
||||
<dict>\n\
|
||||
<key>CFBundleDevelopmentRegion</key>\n\
|
||||
<string>English</string>\n\
|
||||
<key>CFBundleDisplayName</key>\n\
|
||||
<string>' + escapeXML(this.appName) + '</string>\n\
|
||||
<key>CFBundleExecutable</key>\n\
|
||||
<string>webapprt</string>\n\
|
||||
<key>CFBundleIconFile</key>\n\
|
||||
<string>appicon</string>\n\
|
||||
<key>CFBundleIdentifier</key>\n\
|
||||
<string>' + escapeXML(this.uniqueName) + '</string>\n\
|
||||
<key>CFBundleInfoDictionaryVersion</key>\n\
|
||||
<string>6.0</string>\n\
|
||||
<key>CFBundleName</key>\n\
|
||||
<string>' + escapeXML(this.appName) + '</string>\n\
|
||||
<key>CFBundlePackageType</key>\n\
|
||||
<string>APPL</string>\n\
|
||||
<key>CFBundleVersion</key>\n\
|
||||
<string>0</string>\n\
|
||||
<key>NSHighResolutionCapable</key>\n\
|
||||
<true/>\n\
|
||||
<key>NSPrincipalClass</key>\n\
|
||||
<string>GeckoNSApplication</string>\n\
|
||||
<key>FirefoxBinary</key>\n\
|
||||
#expand <string>__MOZ_MACBUNDLE_ID__</string>\n\
|
||||
</dict>\n\
|
||||
</plist>';
|
||||
|
||||
yield writeToFile(OS.Path.join(aDir, this.contentsDir, "Info.plist"),
|
||||
infoPListContent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the icon from the imageStream as retrieved from
|
||||
* the URL by getIconForApp(). This will bundle the icon to the
|
||||
* app package at Contents/Resources/appicon.icns.
|
||||
*
|
||||
* @param aMimeType the icon mimetype
|
||||
* @param aImageStream the stream for the image data
|
||||
* @param aDir the directory where the icon should be stored
|
||||
*/
|
||||
_processIcon: function(aMimeType, aIcon, aDir) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
function conversionDone(aSubject, aTopic) {
|
||||
if (aTopic == "process-finished") {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject("Failure converting icon, exit code: " + aSubject.exitValue);
|
||||
}
|
||||
}
|
||||
|
||||
let process = Cc["@mozilla.org/process/util;1"].
|
||||
createInstance(Ci.nsIProcess);
|
||||
let sipsFile = getFile("/usr/bin/sips");
|
||||
|
||||
process.init(sipsFile);
|
||||
process.runAsync(["-s", "format", "icns",
|
||||
aIcon.path,
|
||||
"--out", OS.Path.join(aDir, this.iconFile),
|
||||
"-z", "128", "128"],
|
||||
9, conversionDone);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
410
toolkit/webapps/NativeApp.jsm
Normal file
410
toolkit/webapps/NativeApp.jsm
Normal file
@ -0,0 +1,410 @@
|
||||
/* 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/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["NativeApp"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const ERR_NOT_INSTALLED = "The application isn't installed";
|
||||
const ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME =
|
||||
"Updates for apps installed with the old naming scheme unsupported";
|
||||
|
||||
// 0755
|
||||
const PERMS_DIRECTORY = OS.Constants.libc.S_IRWXU |
|
||||
OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IXGRP |
|
||||
OS.Constants.libc.S_IROTH | OS.Constants.libc.S_IXOTH;
|
||||
|
||||
// 0644
|
||||
const PERMS_FILE = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
|
||||
OS.Constants.libc.S_IRGRP |
|
||||
OS.Constants.libc.S_IROTH;
|
||||
|
||||
const DESKTOP_DIR = OS.Constants.Path.desktopDir;
|
||||
const HOME_DIR = OS.Constants.Path.homeDir;
|
||||
const TMP_DIR = OS.Constants.Path.tmpDir;
|
||||
|
||||
/**
|
||||
* This function implements the common constructor for
|
||||
* the Windows, Mac and Linux native app shells. It sets
|
||||
* the app unique name. It's meant to be called as
|
||||
* CommonNativeApp.call(this, ...) from the platform-specific
|
||||
* constructor.
|
||||
*
|
||||
* @param aApp {Object} the app object provided to the install function
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aCategories {Array} array of app categories
|
||||
* @param aRegistryDir {String} (optional) path to the registry
|
||||
*
|
||||
*/
|
||||
function CommonNativeApp(aApp, aManifest, aCategories, aRegistryDir) {
|
||||
let manifest = new ManifestHelper(aManifest, aApp.origin);
|
||||
|
||||
aApp.name = manifest.name;
|
||||
this.uniqueName = WebappOSUtils.getUniqueName(aApp);
|
||||
|
||||
this.appName = sanitize(manifest.name);
|
||||
this.appNameAsFilename = stripStringForFilename(this.appName);
|
||||
|
||||
if (aApp.updateManifest) {
|
||||
this.isPackaged = true;
|
||||
}
|
||||
|
||||
this.categories = aCategories.slice(0);
|
||||
|
||||
this.registryDir = aRegistryDir || OS.Constants.Path.profileDir;
|
||||
|
||||
this.app = aApp;
|
||||
|
||||
this._dryRun = false;
|
||||
try {
|
||||
if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
|
||||
this._dryRun = true;
|
||||
}
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
CommonNativeApp.prototype = {
|
||||
uniqueName: null,
|
||||
appName: null,
|
||||
appNameAsFilename: null,
|
||||
iconURI: null,
|
||||
developerName: null,
|
||||
shortDescription: null,
|
||||
categories: null,
|
||||
webappJson: null,
|
||||
runtimeFolder: null,
|
||||
manifest: null,
|
||||
registryDir: null,
|
||||
|
||||
/**
|
||||
* This function reads and parses the data from the app
|
||||
* manifest and stores it in the NativeApp object.
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
*
|
||||
*/
|
||||
_setData: function(aManifest) {
|
||||
let manifest = new ManifestHelper(aManifest, this.app.origin);
|
||||
let origin = Services.io.newURI(this.app.origin, null, null);
|
||||
|
||||
let biggestIcon = getBiggestIconURL(manifest.icons);
|
||||
try {
|
||||
let iconURI = Services.io.newURI(biggestIcon, null, null);
|
||||
if (iconURI.scheme == "data") {
|
||||
this.iconURI = iconURI;
|
||||
}
|
||||
} catch (ex) {}
|
||||
|
||||
if (!this.iconURI) {
|
||||
try {
|
||||
this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null);
|
||||
}
|
||||
catch (ex) {}
|
||||
}
|
||||
|
||||
if (manifest.developer) {
|
||||
if (manifest.developer.name) {
|
||||
let devName = sanitize(manifest.developer.name.substr(0, 128));
|
||||
if (devName) {
|
||||
this.developerName = devName;
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.developer.url) {
|
||||
this.developerUrl = manifest.developer.url;
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.description) {
|
||||
let firstLine = manifest.description.split("\n")[0];
|
||||
let shortDesc = firstLine.length <= 256
|
||||
? firstLine
|
||||
: firstLine.substr(0, 253) + "…";
|
||||
this.shortDescription = sanitize(shortDesc);
|
||||
} else {
|
||||
this.shortDescription = this.appName;
|
||||
}
|
||||
|
||||
if (manifest.version) {
|
||||
this.version = manifest.version;
|
||||
}
|
||||
|
||||
this.webappJson = {
|
||||
// The app registry is the Firefox profile from which the app
|
||||
// was installed.
|
||||
"registryDir": this.registryDir,
|
||||
"app": {
|
||||
"manifest": aManifest,
|
||||
"origin": this.app.origin,
|
||||
"manifestURL": this.app.manifestURL,
|
||||
"installOrigin": this.app.installOrigin,
|
||||
"categories": this.categories,
|
||||
"receipts": this.app.receipts,
|
||||
"installTime": this.app.installTime,
|
||||
}
|
||||
};
|
||||
|
||||
if (this.app.etag) {
|
||||
this.webappJson.app.etag = this.app.etag;
|
||||
}
|
||||
|
||||
if (this.app.packageEtag) {
|
||||
this.webappJson.app.packageEtag = this.app.packageEtag;
|
||||
}
|
||||
|
||||
if (this.app.updateManifest) {
|
||||
this.webappJson.app.updateManifest = this.app.updateManifest;
|
||||
}
|
||||
|
||||
this.runtimeFolder = OS.Constants.Path.libDir;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function retrieves the icon for an app.
|
||||
* If the retrieving fails, it uses the default chrome icon.
|
||||
*/
|
||||
_getIcon: function(aTmpDir) {
|
||||
try {
|
||||
// If the icon is in the zip package, we should modify the url
|
||||
// to point to the zip file (we can't use the app protocol yet
|
||||
// because the app isn't installed yet).
|
||||
if (this.iconURI.scheme == "app") {
|
||||
let zipUrl = OS.Path.toFileURI(OS.Path.join(aTmpDir,
|
||||
"application.zip"));
|
||||
|
||||
let filePath = this.iconURI.QueryInterface(Ci.nsIURL).filePath;
|
||||
|
||||
this.iconURI = Services.io.newURI("jar:" + zipUrl + "!" + filePath,
|
||||
null, null);
|
||||
}
|
||||
|
||||
|
||||
let [ mimeType, icon ] = yield downloadIcon(this.iconURI);
|
||||
yield this._processIcon(mimeType, icon, aTmpDir);
|
||||
}
|
||||
catch(e) {
|
||||
Cu.reportError("Failure retrieving icon: " + e);
|
||||
|
||||
let iconURI = Services.io.newURI(DEFAULT_ICON_URL, null, null);
|
||||
|
||||
let [ mimeType, icon ] = yield downloadIcon(iconURI);
|
||||
yield this._processIcon(mimeType, icon, aTmpDir);
|
||||
|
||||
// Set the iconURI property so that the user notification will have the
|
||||
// correct icon.
|
||||
this.iconURI = iconURI;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the profile to be used for this app.
|
||||
*/
|
||||
createProfile: function() {
|
||||
if (this._dryRun) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].
|
||||
getService(Ci.nsIToolkitProfileService);
|
||||
|
||||
try {
|
||||
let appProfile = profSvc.createDefaultProfileForApp(this.uniqueName,
|
||||
null, null);
|
||||
return appProfile.localDir;
|
||||
} catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
||||
#include WinNativeApp.js
|
||||
|
||||
#elifdef XP_MACOSX
|
||||
|
||||
#include MacNativeApp.js
|
||||
|
||||
#elifdef XP_UNIX
|
||||
|
||||
#include LinuxNativeApp.js
|
||||
|
||||
#endif
|
||||
|
||||
/* Helper Functions */
|
||||
|
||||
/**
|
||||
* Async write a data string into a file
|
||||
*
|
||||
* @param aPath the path to the file to write to
|
||||
* @param aData a string with the data to be written
|
||||
*/
|
||||
function writeToFile(aPath, aData) {
|
||||
return Task.spawn(function() {
|
||||
let data = new TextEncoder().encode(aData);
|
||||
|
||||
let file;
|
||||
try {
|
||||
file = yield OS.File.open(aPath, { truncate: true, write: true },
|
||||
{ unixMode: PERMS_FILE });
|
||||
yield file.write(data);
|
||||
} finally {
|
||||
yield file.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unprintable characters from a string.
|
||||
*/
|
||||
function sanitize(aStr) {
|
||||
let unprintableRE = new RegExp("[\\x00-\\x1F\\x7F]" ,"gi");
|
||||
return aStr.replace(unprintableRE, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips all non-word characters from the beginning and end of a string.
|
||||
* Strips invalid characters from the string.
|
||||
*
|
||||
*/
|
||||
function stripStringForFilename(aPossiblyBadFilenameString) {
|
||||
// Strip everything from the front up to the first [0-9a-zA-Z]
|
||||
let stripFrontRE = new RegExp("^\\W*", "gi");
|
||||
|
||||
// Strip white space characters starting from the last [0-9a-zA-Z]
|
||||
let stripBackRE = new RegExp("\\s*$", "gi");
|
||||
|
||||
// Strip invalid characters from the filename
|
||||
let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
|
||||
|
||||
let stripped = aPossiblyBadFilenameString.replace(stripFrontRE, "");
|
||||
stripped = stripped.replace(stripBackRE, "");
|
||||
stripped = stripped.replace(filenameRE, "");
|
||||
|
||||
// If the filename ends up empty, let's call it "webapp".
|
||||
if (stripped == "") {
|
||||
stripped = "webapp";
|
||||
}
|
||||
|
||||
return stripped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a unique name available in a folder (i.e., non-existent file)
|
||||
*
|
||||
* @param aPathSet a set of paths that represents the set of
|
||||
* directories where we want to write
|
||||
* @param aName string with the filename (minus the extension) desired
|
||||
* @param aExtension string with the file extension, including the dot
|
||||
|
||||
* @return file name or null if folder is unwritable or unique name
|
||||
* was not available
|
||||
*/
|
||||
function getAvailableFileName(aPathSet, aName, aExtension) {
|
||||
return Task.spawn(function*() {
|
||||
let name = aName + aExtension;
|
||||
|
||||
function checkUnique(aName) {
|
||||
return Task.spawn(function*() {
|
||||
for (let path of aPathSet) {
|
||||
if (yield OS.File.exists(OS.Path.join(path, aName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (yield checkUnique(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// If we're here, the plain name wasn't enough. Let's try modifying the name
|
||||
// by adding "(" + num + ")".
|
||||
for (let i = 2; i < 100; i++) {
|
||||
name = aName + " (" + i + ")" + aExtension;
|
||||
|
||||
if (yield checkUnique(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
throw "No available filename";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to remove files or directories.
|
||||
*
|
||||
* @param aPaths An array with paths to files to remove
|
||||
*/
|
||||
function removeFiles(aPaths) {
|
||||
for (let path of aPaths) {
|
||||
let file = getFile(path);
|
||||
|
||||
try {
|
||||
if (file.exists()) {
|
||||
file.followLinks = false;
|
||||
file.remove(true);
|
||||
}
|
||||
} catch(ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move (overwriting) the contents of one directory into another.
|
||||
*
|
||||
* @param srcPath A path to the source directory
|
||||
* @param destPath A path to the destination directory
|
||||
*/
|
||||
function moveDirectory(srcPath, destPath) {
|
||||
let srcDir = getFile(srcPath);
|
||||
let destDir = getFile(destPath);
|
||||
|
||||
let entries = srcDir.directoryEntries;
|
||||
let array = [];
|
||||
while (entries.hasMoreElements()) {
|
||||
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
||||
if (entry.isDirectory()) {
|
||||
yield moveDirectory(entry.path, OS.Path.join(destPath, entry.leafName));
|
||||
} else {
|
||||
entry.moveTo(destDir, entry.leafName);
|
||||
}
|
||||
}
|
||||
|
||||
// The source directory is now empty, remove it.
|
||||
yield OS.File.removeEmptyDir(srcPath);
|
||||
}
|
||||
|
||||
function escapeXML(aStr) {
|
||||
return aStr.toString()
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/* More helpers for handling the app icon */
|
||||
#include WebappsIconHelpers.js
|
@ -9,6 +9,7 @@ const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
|
||||
|
||||
@ -204,6 +205,16 @@ this.WebappOSUtils = {
|
||||
throw new Error("Unsupported apps platform");
|
||||
},
|
||||
|
||||
getPackagePath: function(aApp) {
|
||||
let packagePath = this.getInstallPath(aApp);
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
packagePath = OS.Path.join(packagePath, "Contents", "Resources");
|
||||
#endif
|
||||
|
||||
return packagePath;
|
||||
},
|
||||
|
||||
launch: function(aApp) {
|
||||
let uniqueName = this.getUniqueName(aApp);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
474
toolkit/webapps/WinNativeApp.js
Normal file
474
toolkit/webapps/WinNativeApp.js
Normal file
@ -0,0 +1,474 @@
|
||||
/* 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/. */
|
||||
|
||||
const PROGS_DIR = OS.Constants.Path.winStartMenuProgsDir;
|
||||
const APP_DATA_DIR = OS.Constants.Path.winAppDataDir;
|
||||
|
||||
/*************************************
|
||||
* Windows app installer
|
||||
*
|
||||
* The Windows installation process will generate the following files:
|
||||
*
|
||||
* ${FolderName} = sanitized app name + "-" + manifest url hash
|
||||
*
|
||||
* %APPDATA%/${FolderName}
|
||||
* - webapp.ini
|
||||
* - webapp.json
|
||||
* - ${AppName}.exe
|
||||
* - ${AppName}.lnk
|
||||
* / uninstall
|
||||
* - webapp-uninstaller.exe
|
||||
* - shortcuts_log.ini
|
||||
* - uninstall.log
|
||||
* / chrome/icons/default/
|
||||
* - default.ico
|
||||
*
|
||||
* After the app runs for the first time, a profiles/ folder will also be
|
||||
* created which will host the user profile for this app.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructor for the Windows native app shell
|
||||
*
|
||||
* @param aApp {Object} the app object provided to the install function
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aCategories {Array} array of app categories
|
||||
* @param aRegistryDir {String} (optional) path to the registry
|
||||
*/
|
||||
function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
|
||||
CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
|
||||
|
||||
if (this.isPackaged) {
|
||||
this.size = aApp.updateManifest.size / 1024;
|
||||
}
|
||||
|
||||
this.webapprt = this.appNameAsFilename + ".exe";
|
||||
this.configJson = "webapp.json";
|
||||
this.webappINI = "webapp.ini";
|
||||
this.iconPath = OS.Path.join("chrome", "icons", "default", "default.ico");
|
||||
this.uninstallDir = "uninstall";
|
||||
this.uninstallerFile = OS.Path.join(this.uninstallDir,
|
||||
"webapp-uninstaller.exe");
|
||||
this.shortcutLogsINI = OS.Path.join(this.uninstallDir, "shortcuts_log.ini");
|
||||
this.zipFile = "application.zip";
|
||||
|
||||
this.backupFiles = [ "chrome", this.configJson, this.webappINI, "uninstall" ];
|
||||
if (this.isPackaged) {
|
||||
this.backupFiles.push(this.zipFile);
|
||||
}
|
||||
|
||||
this.uninstallSubkeyStr = this.uniqueName;
|
||||
}
|
||||
|
||||
NativeApp.prototype = {
|
||||
__proto__: CommonNativeApp.prototype,
|
||||
size: null,
|
||||
|
||||
/**
|
||||
* Creates a native installation of the web app in the OS
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
install: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the application is already installed, this is a reinstallation.
|
||||
if (WebappOSUtils.getInstallPath(this.app)) {
|
||||
return yield this.prepareUpdate(aManifest, aZipPath);
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
let installDir = OS.Path.join(APP_DATA_DIR, this.uniqueName);
|
||||
|
||||
// Create a temporary installation directory.
|
||||
let dir = getFile(TMP_DIR, this.uniqueName);
|
||||
dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
||||
let tmpDir = dir.path;
|
||||
|
||||
// Perform the installation in the temp directory.
|
||||
try {
|
||||
yield this._createDirectoryStructure(tmpDir);
|
||||
yield this._getShortcutName(installDir);
|
||||
yield this._copyWebapprt(tmpDir);
|
||||
yield this._copyUninstaller(tmpDir);
|
||||
yield this._createConfigFiles(tmpDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(tmpDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Apply the installation.
|
||||
this._removeInstallation(true, installDir);
|
||||
|
||||
try {
|
||||
yield this._applyTempInstallation(tmpDir, installDir);
|
||||
} catch (ex) {
|
||||
this._removeInstallation(false, installDir);
|
||||
yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates an update in a temporary directory to be applied later.
|
||||
*
|
||||
* @param aManifest {Object} the manifest data provided by the web app
|
||||
* @param aZipPath {String} path to the zip file for packaged apps (undefined
|
||||
* for hosted apps)
|
||||
*/
|
||||
prepareUpdate: Task.async(function*(aManifest, aZipPath) {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setData(aManifest);
|
||||
|
||||
let installDir = WebappOSUtils.getInstallPath(this.app);
|
||||
if (!installDir) {
|
||||
throw ERR_NOT_INSTALLED;
|
||||
}
|
||||
|
||||
if (this.uniqueName != OS.Path.basename(installDir)) {
|
||||
// Bug 919799: If the app is still in the registry, migrate its data to
|
||||
// the new format.
|
||||
throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
|
||||
}
|
||||
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(updateDir);
|
||||
|
||||
// Perform the update in the "update" subdirectory.
|
||||
try {
|
||||
yield this._createDirectoryStructure(updateDir);
|
||||
yield this._getShortcutName(installDir);
|
||||
yield this._copyUninstaller(updateDir);
|
||||
yield this._createConfigFiles(updateDir);
|
||||
|
||||
if (aZipPath) {
|
||||
yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
|
||||
}
|
||||
|
||||
yield this._getIcon(updateDir);
|
||||
} catch (ex) {
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
throw ex;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Applies an update.
|
||||
*/
|
||||
applyUpdate: Task.async(function*() {
|
||||
if (this._dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
let installDir = WebappOSUtils.getInstallPath(this.app);
|
||||
let updateDir = OS.Path.join(installDir, "update");
|
||||
|
||||
yield this._getShortcutName(installDir);
|
||||
|
||||
let backupDir = yield this._backupInstallation(installDir);
|
||||
|
||||
try {
|
||||
yield this._applyTempInstallation(updateDir, installDir);
|
||||
} catch (ex) {
|
||||
yield this._restoreInstallation(backupDir, installDir);
|
||||
throw ex;
|
||||
} finally {
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
|
||||
}
|
||||
}),
|
||||
|
||||
_applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
|
||||
yield moveDirectory(aTmpDir, aInstallDir);
|
||||
|
||||
this._createShortcutFiles(aInstallDir);
|
||||
this._writeSystemKeys(aInstallDir);
|
||||
}),
|
||||
|
||||
_getShortcutName: Task.async(function*(aInstallDir) {
|
||||
let shortcutLogsINIfile = getFile(aInstallDir, this.shortcutLogsINI);
|
||||
|
||||
if (shortcutLogsINIfile.exists()) {
|
||||
// If it's a reinstallation (or an update) get the shortcut names
|
||||
// from the shortcut_log.ini file
|
||||
let parser = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
||||
getService(Ci.nsIINIParserFactory).
|
||||
createINIParser(shortcutLogsINIfile);
|
||||
this.shortcutName = parser.getString("STARTMENU", "Shortcut0");
|
||||
} else {
|
||||
// Check in both directories to see if a shortcut with the same name
|
||||
// already exists.
|
||||
this.shortcutName = yield getAvailableFileName([ PROGS_DIR, DESKTOP_DIR ],
|
||||
this.appNameAsFilename,
|
||||
".lnk");
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Remove the current installation
|
||||
*/
|
||||
_removeInstallation: function(keepProfile, aInstallDir) {
|
||||
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.uninstallSubkeyStr)) {
|
||||
uninstallKey.removeChild(this.uninstallSubkeyStr);
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
if (uninstallKey) {
|
||||
uninstallKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
let filesToRemove = [ OS.Path.join(DESKTOP_DIR, this.shortcutName),
|
||||
OS.Path.join(PROGS_DIR, this.shortcutName) ];
|
||||
|
||||
if (keepProfile) {
|
||||
for (let filePath of this.backupFiles) {
|
||||
filesToRemove.push(OS.Path.join(aInstallDir, filePath));
|
||||
}
|
||||
|
||||
filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
|
||||
} else {
|
||||
filesToRemove.push(aInstallDir);
|
||||
}
|
||||
|
||||
removeFiles(filesToRemove);
|
||||
},
|
||||
|
||||
_backupInstallation: Task.async(function*(aInstallDir) {
|
||||
let backupDir = OS.Path.join(aInstallDir, "backup");
|
||||
yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
|
||||
yield OS.File.makeDir(backupDir);
|
||||
|
||||
for (let filePath of this.backupFiles) {
|
||||
yield OS.File.move(OS.Path.join(aInstallDir, filePath),
|
||||
OS.Path.join(backupDir, filePath));
|
||||
}
|
||||
|
||||
return backupDir;
|
||||
}),
|
||||
|
||||
_restoreInstallation: function(aBackupDir, aInstallDir) {
|
||||
return moveDirectory(aBackupDir, aInstallDir);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the main directory structure.
|
||||
*/
|
||||
_createDirectoryStructure: Task.async(function*(aDir) {
|
||||
yield OS.File.makeDir(OS.Path.join(aDir, this.uninstallDir));
|
||||
|
||||
// Recursively create the icon path's directory structure.
|
||||
let path = aDir;
|
||||
let components = OS.Path.split(OS.Path.dirname(this.iconPath)).components;
|
||||
for (let component of components) {
|
||||
path = OS.Path.join(path, component);
|
||||
yield OS.File.makeDir(path);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy the webrt executable into the installation directory.
|
||||
*/
|
||||
_copyWebapprt: function(aDir) {
|
||||
return OS.File.copy(OS.Path.join(this.runtimeFolder, "webapprt-stub.exe"),
|
||||
OS.Path.join(aDir, this.webapprt));
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy the uninstaller executable into the installation directory.
|
||||
*/
|
||||
_copyUninstaller: function(aDir) {
|
||||
return OS.File.copy(OS.Path.join(this.runtimeFolder, "webapp-uninstaller.exe"),
|
||||
OS.Path.join(aDir, this.uninstallerFile));
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the configuration files into their destination folders.
|
||||
*/
|
||||
_createConfigFiles: function(aDir) {
|
||||
// ${InstallDir}/webapp.json
|
||||
yield writeToFile(OS.Path.join(aDir, this.configJson),
|
||||
JSON.stringify(this.webappJson));
|
||||
|
||||
let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
|
||||
getService(Ci.nsIINIParserFactory);
|
||||
|
||||
// ${InstallDir}/webapp.ini
|
||||
let webappINIfile = getFile(aDir, this.webappINI);
|
||||
|
||||
let writer = factory.createINIParser(webappINIfile)
|
||||
.QueryInterface(Ci.nsIINIParserWriter);
|
||||
writer.setString("Webapp", "Name", this.appName);
|
||||
writer.setString("Webapp", "Profile", this.uniqueName);
|
||||
writer.setString("Webapp", "Executable", this.appNameAsFilename);
|
||||
writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
|
||||
writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
|
||||
|
||||
let shortcutLogsINIfile = getFile(aDir, this.shortcutLogsINI);
|
||||
|
||||
writer = factory.createINIParser(shortcutLogsINIfile)
|
||||
.QueryInterface(Ci.nsIINIParserWriter);
|
||||
writer.setString("STARTMENU", "Shortcut0", this.shortcutName);
|
||||
writer.setString("DESKTOP", "Shortcut0", this.shortcutName);
|
||||
writer.setString("TASKBAR", "Migrated", "true");
|
||||
writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
|
||||
|
||||
// ${UninstallDir}/uninstall.log
|
||||
let uninstallContent =
|
||||
"File: \\webapp.ini\r\n" +
|
||||
"File: \\webapp.json\r\n" +
|
||||
"File: \\webapprt.old\r\n" +
|
||||
"File: \\chrome\\icons\\default\\default.ico";
|
||||
if (this.isPackaged) {
|
||||
uninstallContent += "\r\nFile: \\application.zip";
|
||||
}
|
||||
|
||||
yield writeToFile(OS.Path.join(aDir, this.uninstallDir, "uninstall.log"),
|
||||
uninstallContent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes the keys to the system registry that are necessary for the app
|
||||
* operation and uninstall process.
|
||||
*/
|
||||
_writeSystemKeys: function(aInstallDir) {
|
||||
let parentKey;
|
||||
let uninstallKey;
|
||||
let subKey;
|
||||
|
||||
try {
|
||||
parentKey = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(Ci.nsIWindowsRegKey);
|
||||
parentKey.open(parentKey.ROOT_KEY_CURRENT_USER,
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion",
|
||||
parentKey.ACCESS_WRITE);
|
||||
uninstallKey = parentKey.createChild("Uninstall", parentKey.ACCESS_WRITE)
|
||||
subKey = uninstallKey.createChild(this.uninstallSubkeyStr,
|
||||
uninstallKey.ACCESS_WRITE);
|
||||
|
||||
subKey.writeStringValue("DisplayName", this.appName);
|
||||
|
||||
let uninstallerPath = OS.Path.join(aInstallDir, this.uninstallerFile);
|
||||
|
||||
subKey.writeStringValue("UninstallString", '"' + uninstallerPath + '"');
|
||||
subKey.writeStringValue("InstallLocation", '"' + aInstallDir + '"');
|
||||
subKey.writeStringValue("AppFilename", this.appNameAsFilename);
|
||||
subKey.writeStringValue("DisplayIcon", OS.Path.join(aInstallDir,
|
||||
this.iconPath));
|
||||
|
||||
let date = new Date();
|
||||
let year = date.getYear().toString();
|
||||
let month = date.getMonth();
|
||||
if (month < 10) {
|
||||
month = "0" + month;
|
||||
}
|
||||
let day = date.getDate();
|
||||
if (day < 10) {
|
||||
day = "0" + day;
|
||||
}
|
||||
subKey.writeStringValue("InstallDate", year + month + day);
|
||||
if (this.version) {
|
||||
subKey.writeStringValue("DisplayVersion", this.version);
|
||||
}
|
||||
if (this.developerName) {
|
||||
subKey.writeStringValue("Publisher", this.developerName);
|
||||
}
|
||||
subKey.writeStringValue("URLInfoAbout", this.developerUrl);
|
||||
if (this.size) {
|
||||
subKey.writeIntValue("EstimatedSize", this.size);
|
||||
}
|
||||
|
||||
subKey.writeIntValue("NoModify", 1);
|
||||
subKey.writeIntValue("NoRepair", 1);
|
||||
} catch(ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
if(subKey) subKey.close();
|
||||
if(uninstallKey) uninstallKey.close();
|
||||
if(parentKey) parentKey.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a shortcut file inside the app installation folder and makes
|
||||
* two copies of it: one into the desktop and one into the start menu.
|
||||
*/
|
||||
_createShortcutFiles: function(aInstallDir) {
|
||||
let shortcut = getFile(aInstallDir, this.shortcutName).
|
||||
QueryInterface(Ci.nsILocalFileWin);
|
||||
|
||||
/* function nsILocalFileWin.setShortcut(targetFile, workingDir, args,
|
||||
description, iconFile, iconIndex) */
|
||||
|
||||
shortcut.setShortcut(getFile(aInstallDir, this.webapprt),
|
||||
getFile(aInstallDir),
|
||||
null,
|
||||
this.shortDescription,
|
||||
getFile(aInstallDir, this.iconPath),
|
||||
0);
|
||||
|
||||
shortcut.copyTo(getFile(DESKTOP_DIR), this.shortcutName);
|
||||
shortcut.copyTo(getFile(PROGS_DIR), this.shortcutName);
|
||||
|
||||
shortcut.followLinks = false;
|
||||
shortcut.remove(false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the icon from the imageStream as retrieved from
|
||||
* the URL by getIconForApp(). This will save the icon to the
|
||||
* topwindow.ico file.
|
||||
*
|
||||
* @param aMimeType the icon mimetype
|
||||
* @param aImageStream the stream for the image data
|
||||
* @param aDir the directory where the icon should be stored
|
||||
*/
|
||||
_processIcon: function(aMimeType, aImageStream, aDir) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let imgTools = Cc["@mozilla.org/image/tools;1"].
|
||||
createInstance(Ci.imgITools);
|
||||
|
||||
let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
|
||||
let iconStream = imgTools.encodeImage(imgContainer,
|
||||
"image/vnd.microsoft.icon",
|
||||
"format=bmp;bpp=32");
|
||||
|
||||
let tmpIconFile = getFile(aDir, this.iconPath);
|
||||
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(tmpIconFile);
|
||||
NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
|
||||
if (Components.isSuccessCode(aResult)) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject("Failure copying icon: " + aResult);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
@ -5,9 +5,11 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'NativeApp.jsm',
|
||||
'WebappOSUtils.jsm',
|
||||
'WebappsInstaller.jsm',
|
||||
]
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
|
||||
|
||||
if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
|
||||
DEFINES['MOZ_FENNEC'] = True
|
||||
|
8
toolkit/webapps/tests/chrome.ini
Normal file
8
toolkit/webapps/tests/chrome.ini
Normal file
@ -0,0 +1,8 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
|
||||
[test_hosted.xul]
|
||||
skip-if = os == "mac"
|
||||
[test_packaged.xul]
|
||||
skip-if = os == "mac"
|
50
toolkit/webapps/tests/head.js
Normal file
50
toolkit/webapps/tests/head.js
Normal file
@ -0,0 +1,50 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
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 wait(time) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
setTimeout(function() {
|
||||
deferred.resolve();
|
||||
}, time);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
311
toolkit/webapps/tests/test_hosted.xul
Normal file
311
toolkit/webapps/tests/test_hosted.xul
Normal file
@ -0,0 +1,311 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=898647
|
||||
-->
|
||||
<window title="Mozilla Bug 898647"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="head.js"/>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=898647"
|
||||
target="_blank">Mozilla Bug 898647</a>
|
||||
</body>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
/** Test for Bug 898647 **/
|
||||
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NativeApp.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
|
||||
let manifest = {
|
||||
name: "Sample hosted app",
|
||||
};
|
||||
|
||||
let app = {
|
||||
name: "Sample hosted app",
|
||||
manifestURL: "http://example.com/sample.manifest",
|
||||
manifest: manifest,
|
||||
origin: "http://example.com/",
|
||||
categories: [],
|
||||
installOrigin: "http://example.com/",
|
||||
receipts: [],
|
||||
installTime: Date.now(),
|
||||
};
|
||||
|
||||
let profileDir;
|
||||
let profilesIni;
|
||||
let installPath;
|
||||
|
||||
let installedFiles;
|
||||
let tempUpdatedFiles;
|
||||
let updatedFiles;
|
||||
|
||||
let cleanup;
|
||||
|
||||
if (navigator.platform.startsWith("Linux")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
let desktopINI = OS.Path.join(xdg_data_home, "applications",
|
||||
"owa-" + WebappOSUtils.getUniqueName(app) + ".desktop");
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "icon.png"),
|
||||
OS.Path.join(installPath, "webapprt-stub"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
desktopINI,
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "icon.png"),
|
||||
OS.Path.join(installPath, "update", "webapp.json"),
|
||||
OS.Path.join(installPath, "update", "webapp.ini"),
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "icon.png"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
desktopINI,
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(installPath, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.remove(desktopINI, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
} else if (navigator.platform.startsWith("Win")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.winAppDataDir, WebappOSUtils.getUniqueName(app));
|
||||
|
||||
let desktopShortcut = OS.Path.join(OS.Constants.Path.desktopDir, "Sample hosted app.lnk");
|
||||
let startMenuShortcut = OS.Path.join(OS.Constants.Path.winStartMenuProgsDir, "Sample hosted app.lnk");
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "Sample hosted app.exe"),
|
||||
OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "uninstall.log"),
|
||||
OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
|
||||
desktopShortcut,
|
||||
startMenuShortcut,
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "update", "webapp.json"),
|
||||
OS.Path.join(installPath, "update", "webapp.ini"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "uninstall.log"),
|
||||
desktopShortcut,
|
||||
startMenuShortcut,
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(installPath, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
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(WebappOSUtils.getUniqueName(app))) {
|
||||
uninstallKey.removeChild(WebappOSUtils.getUniqueName(app));
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
if (uninstallKey) {
|
||||
uninstallKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.remove(desktopShortcut, { ignoreAbsent: true });
|
||||
yield OS.File.remove(startMenuShortcut, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
} else if (navigator.platform.startsWith("Mac")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.macLocalApplicationsDir, "Sample hosted app.app");
|
||||
let appProfileDir = OS.Path.join(OS.Constants.Path.macUserLibDir, "Application Support",
|
||||
WebappOSUtils.getUniqueName(app));
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(appProfileDir, "webapp.json"),
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(installPath, "update", "webapp.json")
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(appProfileDir, "webapp.json"),
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
Task.spawn(function() {
|
||||
// Get to a clean state before the test
|
||||
yield cleanup();
|
||||
|
||||
let nativeApp = new NativeApp(app, manifest, app.categories);
|
||||
ok(nativeApp, "NativeApp object created");
|
||||
|
||||
info("Test update for an uninstalled application");
|
||||
try {
|
||||
yield nativeApp.prepareUpdate(manifest);
|
||||
ok(false, "Didn't thrown");
|
||||
} catch (ex) {
|
||||
is(ex, "The application isn't installed", "Exception thrown");
|
||||
}
|
||||
|
||||
profileDir = nativeApp.createProfile();
|
||||
ok(profileDir && profileDir.exists(), "Profile directory created");
|
||||
ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
|
||||
|
||||
// Install application
|
||||
info("Test installation");
|
||||
yield nativeApp.install(manifest);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Files correctly written");
|
||||
|
||||
let stat = yield OS.File.stat(installPath);
|
||||
let installTime = stat.lastModificationDate;
|
||||
|
||||
// Wait one second, otherwise the last modification date is the same.
|
||||
yield wait(1000);
|
||||
|
||||
// Reinstall application
|
||||
info("Test reinstallation");
|
||||
yield nativeApp.install(manifest);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
|
||||
|
||||
yield nativeApp.applyUpdate();
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
|
||||
ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
|
||||
|
||||
stat = yield OS.File.stat(installPath);
|
||||
installTime = stat.lastModificationDate;
|
||||
|
||||
// Wait one second, otherwise the last modification date is the same.
|
||||
yield wait(1000);
|
||||
|
||||
// Update application
|
||||
info("Test update");
|
||||
yield nativeApp.prepareUpdate(manifest);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
|
||||
|
||||
yield nativeApp.applyUpdate();
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
|
||||
ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
|
||||
|
||||
SimpleTest.finish();
|
||||
}).then(null, function(e) {
|
||||
ok(false, "Error during test: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
337
toolkit/webapps/tests/test_packaged.xul
Normal file
337
toolkit/webapps/tests/test_packaged.xul
Normal file
@ -0,0 +1,337 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=898647
|
||||
-->
|
||||
<window title="Mozilla Bug 898647"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="head.js"/>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=898647"
|
||||
target="_blank">Mozilla Bug 898647</a>
|
||||
</body>
|
||||
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
/** Test for Bug 898647 **/
|
||||
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/NativeApp.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
|
||||
let zipPath = OS.Path.join(OS.Constants.Path.profileDir, "sample.zip");
|
||||
|
||||
let manifest = {
|
||||
name: "Sample packaged app",
|
||||
version: "0.1a",
|
||||
size: 777,
|
||||
package_path: "/sample.zip",
|
||||
};
|
||||
|
||||
let app = {
|
||||
name: "Sample packaged app",
|
||||
manifestURL: "http://example.com/sample.manifest",
|
||||
manifest: manifest,
|
||||
updateManifest: manifest,
|
||||
origin: "http://example.com/",
|
||||
categories: [],
|
||||
installOrigin: "http://example.com/",
|
||||
receipts: [],
|
||||
installTime: Date.now(),
|
||||
};
|
||||
|
||||
let profileDir;
|
||||
let profilesIni;
|
||||
let installPath;
|
||||
|
||||
let installedFiles;
|
||||
let tempUpdatedFiles;
|
||||
let updatedFiles;
|
||||
|
||||
let cleanup;
|
||||
|
||||
if (navigator.platform.startsWith("Linux")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
let desktopINI = OS.Path.join(xdg_data_home, "applications",
|
||||
"owa-" + WebappOSUtils.getUniqueName(app) + ".desktop");
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "icon.png"),
|
||||
OS.Path.join(installPath, "webapprt-stub"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "application.zip"),
|
||||
desktopINI,
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "icon.png"),
|
||||
OS.Path.join(installPath, "update", "webapp.json"),
|
||||
OS.Path.join(installPath, "update", "webapp.ini"),
|
||||
OS.Path.join(installPath, "update", "application.zip"),
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "icon.png"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "application.zip"),
|
||||
desktopINI,
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(installPath, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.remove(desktopINI, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
} else if (navigator.platform.startsWith("Win")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.winAppDataDir, WebappOSUtils.getUniqueName(app));
|
||||
|
||||
let desktopShortcut = OS.Path.join(OS.Constants.Path.desktopDir, "Sample packaged app.lnk");
|
||||
let startMenuShortcut = OS.Path.join(OS.Constants.Path.winStartMenuProgsDir, "Sample packaged app.lnk");
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "Sample packaged app.exe"),
|
||||
OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "application.zip"),
|
||||
OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "uninstall.log"),
|
||||
OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
|
||||
desktopShortcut,
|
||||
startMenuShortcut,
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "update", "webapp.json"),
|
||||
OS.Path.join(installPath, "update", "webapp.ini"),
|
||||
OS.Path.join(installPath, "update", "application.zip"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
|
||||
OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
|
||||
OS.Path.join(installPath, "webapp.json"),
|
||||
OS.Path.join(installPath, "webapp.ini"),
|
||||
OS.Path.join(installPath, "application.zip"),
|
||||
OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
|
||||
OS.Path.join(installPath, "uninstall", "uninstall.log"),
|
||||
desktopShortcut,
|
||||
startMenuShortcut,
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(installPath, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
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(WebappOSUtils.getUniqueName(app))) {
|
||||
uninstallKey.removeChild(WebappOSUtils.getUniqueName(app));
|
||||
}
|
||||
} catch (e) {
|
||||
} finally {
|
||||
if (uninstallKey) {
|
||||
uninstallKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.remove(desktopShortcut, { ignoreAbsent: true });
|
||||
yield OS.File.remove(startMenuShortcut, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
} else if (navigator.platform.startsWith("Mac")) {
|
||||
installPath = OS.Path.join(OS.Constants.Path.macLocalApplicationsDir, "Sample packaged app.app");
|
||||
let appProfileDir = OS.Path.join(OS.Constants.Path.macUserLibDir, "Application Support",
|
||||
WebappOSUtils.getUniqueName(app));
|
||||
|
||||
installedFiles = [
|
||||
OS.Path.join(installPath, "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
|
||||
OS.Path.join(appProfileDir, "webapp.json"),
|
||||
];
|
||||
tempUpdatedFiles = [
|
||||
OS.Path.join(installPath, "update", "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(installPath, "update", "Contents", "Resources", "application.zip"),
|
||||
OS.Path.join(installPath, "update", "webapp.json"),
|
||||
];
|
||||
updatedFiles = [
|
||||
OS.Path.join(installPath, "Contents", "Info.plist"),
|
||||
OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
|
||||
OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
|
||||
OS.Path.join(appProfileDir, "webapp.json"),
|
||||
];
|
||||
|
||||
profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
|
||||
|
||||
cleanup = function() {
|
||||
return Task.spawn(function*() {
|
||||
if (profileDir) {
|
||||
yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
|
||||
}
|
||||
|
||||
yield OS.File.removeDir(installPath, { ignoreAbsent: true });
|
||||
|
||||
yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
});
|
||||
|
||||
Task.spawn(function() {
|
||||
// Get to a clean state before the test
|
||||
yield cleanup();
|
||||
|
||||
let zipFile = yield OS.File.open(zipPath, { create: true });
|
||||
yield zipFile.close();
|
||||
|
||||
let nativeApp = new NativeApp(app, manifest, app.categories);
|
||||
ok(nativeApp, "NativeApp object created");
|
||||
|
||||
info("Test update for an application that isn't installed");
|
||||
try {
|
||||
yield nativeApp.prepareUpdate(manifest, zipPath);
|
||||
ok(false, "Didn't thrown");
|
||||
} catch (ex) {
|
||||
is(ex, "The application isn't installed", "Exception thrown");
|
||||
}
|
||||
|
||||
profileDir = nativeApp.createProfile();
|
||||
ok(profileDir && profileDir.exists(), "Profile directory created");
|
||||
ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
|
||||
|
||||
// Install application
|
||||
info("Test installation");
|
||||
yield nativeApp.install(manifest, zipPath);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Files correctly written");
|
||||
|
||||
let stat = yield OS.File.stat(installPath);
|
||||
let installTime = stat.lastModificationDate;
|
||||
|
||||
// Wait one second, otherwise the last modification date is the same.
|
||||
yield wait(1000);
|
||||
|
||||
// Reinstall application
|
||||
info("Test reinstallation");
|
||||
|
||||
zipFile = yield OS.File.open(zipPath, { create: true });
|
||||
yield zipFile.close();
|
||||
|
||||
yield nativeApp.install(manifest, zipPath);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
|
||||
|
||||
yield nativeApp.applyUpdate();
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
|
||||
ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
|
||||
|
||||
stat = yield OS.File.stat(installPath);
|
||||
installTime = stat.lastModificationDate;
|
||||
|
||||
// Wait one second, otherwise the last modification date is the same.
|
||||
yield wait(1000);
|
||||
|
||||
// Update application
|
||||
info("Test update");
|
||||
|
||||
zipFile = yield OS.File.open(zipPath, { create: true });
|
||||
yield zipFile.close();
|
||||
|
||||
yield nativeApp.prepareUpdate(manifest, zipPath);
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
|
||||
|
||||
yield nativeApp.applyUpdate();
|
||||
while (!WebappOSUtils.isLaunchable(app)) {
|
||||
yield wait(1000);
|
||||
}
|
||||
ok(true, "App launchable");
|
||||
ok((yield checkFiles(installedFiles)), "Installation not corrupted");
|
||||
ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
|
||||
ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
|
||||
|
||||
SimpleTest.finish();
|
||||
}).then(null, function(e) {
|
||||
ok(false, "Error during test: " + e);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
@ -13,8 +13,6 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
// Initialize DOMApplicationRegistry by importing Webapps.jsm.
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
|
||||
Cu.import('resource://gre/modules/Payment.jsm');
|
||||
@ -22,8 +20,6 @@ Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
// Initialize window-independent handling of webapps- notifications.
|
||||
Cu.import("resource://webapprt/modules/WebappsHandler.jsm");
|
||||
Cu.import("resource://webapprt/modules/WebappRT.jsm");
|
||||
Cu.import("resource://webapprt/modules/WebRTCHandler.jsm");
|
||||
|
||||
@ -93,6 +89,19 @@ this.startup = function(window) {
|
||||
});
|
||||
}
|
||||
|
||||
let appUpdated = false;
|
||||
let updatePending = yield WebappRT.isUpdatePending();
|
||||
if (updatePending) {
|
||||
appUpdated = yield WebappRT.applyUpdate();
|
||||
}
|
||||
|
||||
yield WebappRT.loadConfig();
|
||||
|
||||
// Initialize DOMApplicationRegistry by importing Webapps.jsm.
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
// Initialize window-independent handling of webapps- notifications.
|
||||
Cu.import("resource://webapprt/modules/WebappManager.jsm");
|
||||
|
||||
// Wait for webapps registry loading.
|
||||
yield DOMApplicationRegistry.registryStarted;
|
||||
|
||||
@ -100,8 +109,7 @@ this.startup = function(window) {
|
||||
if (manifestURL) {
|
||||
// On firstrun, set permissions to their default values.
|
||||
// When the webapp runtime is updated, update the permissions.
|
||||
// TODO: Update the permissions when the application is updated.
|
||||
if (isFirstRunOrUpdate(Services.prefs)) {
|
||||
if (isFirstRunOrUpdate(Services.prefs) || appUpdated) {
|
||||
PermissionsInstaller.installPermissions(WebappRT.config.app, true);
|
||||
yield createBrandingFiles();
|
||||
}
|
||||
@ -135,5 +143,7 @@ this.startup = function(window) {
|
||||
documentElement.mozRequestFullScreen();
|
||||
}, true);
|
||||
}
|
||||
|
||||
WebappRT.startUpdateService();
|
||||
}).then(null, Cu.reportError.bind(Cu));
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["WebappsHandler"];
|
||||
this.EXPORTED_SYMBOLS = ["WebappManager"];
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
@ -14,10 +14,11 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/WebappsInstaller.jsm");
|
||||
Cu.import("resource://gre/modules/NativeApp.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
Cu.import("resource://webapprt/modules/WebappRT.jsm");
|
||||
|
||||
this.WebappsHandler = {
|
||||
this.WebappManager = {
|
||||
observe: function(subject, topic, data) {
|
||||
data = JSON.parse(data);
|
||||
data.mm = subject;
|
||||
@ -37,6 +38,13 @@ this.WebappsHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
update: function(aApp, aManifest, aZipPath) {
|
||||
let nativeApp = new NativeApp(aApp, aManifest,
|
||||
WebappRT.config.app.categories,
|
||||
WebappRT.config.registryDir);
|
||||
nativeApp.prepareUpdate(aManifest, aZipPath);
|
||||
},
|
||||
|
||||
doInstall: function(data, window) {
|
||||
let jsonManifest = data.isPackage ? data.app.updateManifest : data.app.manifest;
|
||||
let manifest = new ManifestHelper(jsonManifest, data.app.origin);
|
||||
@ -59,22 +67,22 @@ this.WebappsHandler = {
|
||||
|
||||
// Perform the install if the user allows it
|
||||
if (choice == 0) {
|
||||
let shell = WebappsInstaller.init(data);
|
||||
|
||||
if (shell) {
|
||||
let localDir = null;
|
||||
if (shell.appProfile) {
|
||||
localDir = shell.appProfile.localDir;
|
||||
}
|
||||
|
||||
DOMApplicationRegistry.confirmInstall(data, localDir,
|
||||
function (aManifest) {
|
||||
WebappsInstaller.install(data, aManifest);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
DOMApplicationRegistry.denyInstall(data);
|
||||
let nativeApp = new NativeApp(data.app, jsonManifest,
|
||||
WebappRT.config.app.categories,
|
||||
WebappRT.config.registryDir);
|
||||
let localDir;
|
||||
try {
|
||||
localDir = nativeApp.createProfile();
|
||||
} catch (ex) {
|
||||
DOMApplicationRegistry.denyInstall(aData);
|
||||
return;
|
||||
}
|
||||
|
||||
DOMApplicationRegistry.confirmInstall(data, localDir,
|
||||
function (aManifest, aZipPath) {
|
||||
nativeApp.install(aManifest, aZipPath);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
DOMApplicationRegistry.denyInstall(data);
|
||||
}
|
||||
@ -84,6 +92,9 @@ this.WebappsHandler = {
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
|
||||
Services.obs.addObserver(WebappsHandler, "webapps-ask-install", false);
|
||||
Services.obs.addObserver(WebappsHandler, "webapps-launch", false);
|
||||
Services.obs.addObserver(WebappsHandler, "webapps-uninstall", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-ask-install", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-launch", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-uninstall", false);
|
||||
Services.obs.addObserver(WebappManager, "webapps-update", false);
|
||||
|
||||
DOMApplicationRegistry.registerUpdateHandler(WebappManager.update);
|
@ -15,38 +15,20 @@ Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'NativeApp',
|
||||
'resource://gre/modules/NativeApp.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
|
||||
"@mozilla.org/AppsService;1",
|
||||
"nsIAppsService");
|
||||
|
||||
this.WebappRT = {
|
||||
_config: null,
|
||||
|
||||
get config() {
|
||||
if (this._config)
|
||||
return this._config;
|
||||
|
||||
let webappFile = FileUtils.getFile("AppRegD", ["webapp.json"]);
|
||||
|
||||
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
inputStream.init(webappFile, -1, 0, Ci.nsIFileInputStream.CLOSE_ON_EOF);
|
||||
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
|
||||
let config = json.decodeFromStream(inputStream, webappFile.fileSize);
|
||||
|
||||
return this._config = config;
|
||||
},
|
||||
|
||||
// This exists to support test mode, which installs webapps after startup.
|
||||
// Ideally we wouldn't have to have a setter, as tests can just delete
|
||||
// the getter and then set the property. But the object to which they set it
|
||||
// will have a reference to its global object, so our reference to it
|
||||
// will leak that object (per bug 780674). The setter enables us to clone
|
||||
// the new value so we don't actually retain a reference to it.
|
||||
set config(newVal) {
|
||||
this._config = JSON.parse(JSON.stringify(newVal));
|
||||
},
|
||||
|
||||
get launchURI() {
|
||||
let manifest = this.localeManifest;
|
||||
return manifest.fullLaunchPath();
|
||||
@ -65,4 +47,93 @@ this.WebappRT = {
|
||||
|
||||
return appsService.getAppLocalIdByManifestURL(manifestURL);
|
||||
},
|
||||
|
||||
loadConfig: function() {
|
||||
if (this.config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
|
||||
"webapp.json");
|
||||
this.config = yield AppsUtils.loadJSONAsync(webappJson);
|
||||
},
|
||||
|
||||
isUpdatePending: Task.async(function*() {
|
||||
let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
|
||||
"update", "webapp.json");
|
||||
|
||||
if (!(yield OS.File.exists(webappJson))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
applyUpdate: Task.async(function*() {
|
||||
let webappJson = OS.Path.join(Services.dirsvc.get("AppRegD", Ci.nsIFile).path,
|
||||
"update", "webapp.json");
|
||||
let config = yield AppsUtils.loadJSONAsync(webappJson);
|
||||
|
||||
let nativeApp = new NativeApp(config.app, config.app.manifest,
|
||||
config.app.categories,
|
||||
config.registryDir);
|
||||
try {
|
||||
yield nativeApp.applyUpdate();
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The update has been applied successfully, the new config file
|
||||
// is the config file that was in the update directory.
|
||||
this.config = config;
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
startUpdateService: function() {
|
||||
let manifestURL = WebappRT.config.app.manifestURL;
|
||||
// We used to install apps without storing their manifest URL.
|
||||
// Now we can't update them.
|
||||
if (!manifestURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for updates once a day.
|
||||
let timerManager = Cc["@mozilla.org/updates/timer-manager;1"].
|
||||
getService(Ci.nsIUpdateTimerManager);
|
||||
timerManager.registerTimer("updateTimer", () => {
|
||||
let window = Services.wm.getMostRecentWindow("webapprt:webapp");
|
||||
window.navigator.mozApps.mgmt.getAll().onsuccess = function() {
|
||||
let thisApp = null;
|
||||
for (let app of this.result) {
|
||||
if (app.manifestURL == manifestURL) {
|
||||
thisApp = app;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This shouldn't happen if the app is installed.
|
||||
if (!thisApp) {
|
||||
Cu.reportError("Couldn't find the app in the webapps registry");
|
||||
return;
|
||||
}
|
||||
|
||||
thisApp.ondownloadavailable = () => {
|
||||
// Download available, download it!
|
||||
thisApp.download();
|
||||
};
|
||||
|
||||
thisApp.ondownloadsuccess = () => {
|
||||
// Update downloaded, apply it!
|
||||
window.navigator.mozApps.mgmt.applyDownload(thisApp);
|
||||
};
|
||||
|
||||
thisApp.ondownloadapplied = () => {
|
||||
// Application updated, nothing to do.
|
||||
};
|
||||
|
||||
thisApp.checkForUpdate();
|
||||
}
|
||||
}, 24 * 60 * 60);
|
||||
},
|
||||
};
|
||||
|
@ -24,8 +24,8 @@ EXTRA_COMPONENTS += [
|
||||
EXTRA_JS_MODULES += [
|
||||
'RemoteDebugger.jsm',
|
||||
'Startup.jsm',
|
||||
'WebappManager.jsm',
|
||||
'WebappRT.jsm',
|
||||
'WebappsHandler.jsm',
|
||||
'WebRTCHandler.jsm',
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user