toolkit/webapps/LinuxNativeApp.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * Constructor for the Linux native app shell
michael@0 7 *
michael@0 8 * @param aApp {Object} the app object provided to the install function
michael@0 9 * @param aManifest {Object} the manifest data provided by the web app
michael@0 10 * @param aCategories {Array} array of app categories
michael@0 11 * @param aRegistryDir {String} (optional) path to the registry
michael@0 12 */
michael@0 13 function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
michael@0 14 CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
michael@0 15
michael@0 16 this.iconFile = "icon.png";
michael@0 17 this.webapprt = "webapprt-stub";
michael@0 18 this.configJson = "webapp.json";
michael@0 19 this.webappINI = "webapp.ini";
michael@0 20 this.zipFile = "application.zip";
michael@0 21
michael@0 22 this.backupFiles = [ this.iconFile, this.configJson, this.webappINI ];
michael@0 23 if (this.isPackaged) {
michael@0 24 this.backupFiles.push(this.zipFile);
michael@0 25 }
michael@0 26
michael@0 27 let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
michael@0 28 getService(Ci.nsIEnvironment).
michael@0 29 get("XDG_DATA_HOME");
michael@0 30 if (!xdg_data_home) {
michael@0 31 xdg_data_home = OS.Path.join(HOME_DIR, ".local", "share");
michael@0 32 }
michael@0 33
michael@0 34 // The desktop file name is: "owa-" + sanitized app name +
michael@0 35 // "-" + manifest url hash.
michael@0 36 this.desktopINI = OS.Path.join(xdg_data_home, "applications",
michael@0 37 "owa-" + this.uniqueName + ".desktop");
michael@0 38 }
michael@0 39
michael@0 40 NativeApp.prototype = {
michael@0 41 __proto__: CommonNativeApp.prototype,
michael@0 42
michael@0 43 /**
michael@0 44 * Creates a native installation of the web app in the OS
michael@0 45 *
michael@0 46 * @param aManifest {Object} the manifest data provided by the web app
michael@0 47 * @param aZipPath {String} path to the zip file for packaged apps (undefined
michael@0 48 * for hosted apps)
michael@0 49 */
michael@0 50 install: Task.async(function*(aManifest, aZipPath) {
michael@0 51 if (this._dryRun) {
michael@0 52 return;
michael@0 53 }
michael@0 54
michael@0 55 // If the application is already installed, this is a reinstallation.
michael@0 56 if (WebappOSUtils.getInstallPath(this.app)) {
michael@0 57 return yield this.prepareUpdate(aManifest, aZipPath);
michael@0 58 }
michael@0 59
michael@0 60 this._setData(aManifest);
michael@0 61
michael@0 62 // The installation directory name is: sanitized app name +
michael@0 63 // "-" + manifest url hash.
michael@0 64 let installDir = OS.Path.join(HOME_DIR, "." + this.uniqueName);
michael@0 65
michael@0 66 let dir = getFile(TMP_DIR, this.uniqueName);
michael@0 67 dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
michael@0 68 let tmpDir = dir.path;
michael@0 69
michael@0 70 // Create the installation in a temporary directory.
michael@0 71 try {
michael@0 72 this._copyPrebuiltFiles(tmpDir);
michael@0 73 yield this._createConfigFiles(tmpDir);
michael@0 74
michael@0 75 if (aZipPath) {
michael@0 76 yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
michael@0 77 }
michael@0 78
michael@0 79 yield this._getIcon(tmpDir);
michael@0 80 } catch (ex) {
michael@0 81 yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
michael@0 82 throw ex;
michael@0 83 }
michael@0 84
michael@0 85 // Apply the installation.
michael@0 86 this._removeInstallation(true, installDir);
michael@0 87
michael@0 88 try {
michael@0 89 yield this._applyTempInstallation(tmpDir, installDir);
michael@0 90 } catch (ex) {
michael@0 91 this._removeInstallation(false, installDir);
michael@0 92 yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
michael@0 93 throw ex;
michael@0 94 }
michael@0 95 }),
michael@0 96
michael@0 97 /**
michael@0 98 * Creates an update in a temporary directory to be applied later.
michael@0 99 *
michael@0 100 * @param aManifest {Object} the manifest data provided by the web app
michael@0 101 * @param aZipPath {String} path to the zip file for packaged apps (undefined
michael@0 102 * for hosted apps)
michael@0 103 */
michael@0 104 prepareUpdate: Task.async(function*(aManifest, aZipPath) {
michael@0 105 if (this._dryRun) {
michael@0 106 return;
michael@0 107 }
michael@0 108
michael@0 109 this._setData(aManifest);
michael@0 110
michael@0 111 let installDir = WebappOSUtils.getInstallPath(this.app);
michael@0 112 if (!installDir) {
michael@0 113 throw ERR_NOT_INSTALLED;
michael@0 114 }
michael@0 115
michael@0 116 let baseName = OS.Path.basename(installDir)
michael@0 117 let oldUniqueName = baseName.substring(1, baseName.length);
michael@0 118 if (this.uniqueName != oldUniqueName) {
michael@0 119 // Bug 919799: If the app is still in the registry, migrate its data to
michael@0 120 // the new format.
michael@0 121 throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
michael@0 122 }
michael@0 123
michael@0 124 let updateDir = OS.Path.join(installDir, "update");
michael@0 125 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 126 yield OS.File.makeDir(updateDir);
michael@0 127
michael@0 128 try {
michael@0 129 yield this._createConfigFiles(updateDir);
michael@0 130
michael@0 131 if (aZipPath) {
michael@0 132 yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
michael@0 133 }
michael@0 134
michael@0 135 yield this._getIcon(updateDir);
michael@0 136 } catch (ex) {
michael@0 137 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 138 throw ex;
michael@0 139 }
michael@0 140 }),
michael@0 141
michael@0 142 /**
michael@0 143 * Applies an update.
michael@0 144 */
michael@0 145 applyUpdate: Task.async(function*() {
michael@0 146 if (this._dryRun) {
michael@0 147 return;
michael@0 148 }
michael@0 149
michael@0 150 let installDir = WebappOSUtils.getInstallPath(this.app);
michael@0 151 let updateDir = OS.Path.join(installDir, "update");
michael@0 152
michael@0 153 let backupDir = yield this._backupInstallation(installDir);
michael@0 154
michael@0 155 try {
michael@0 156 yield this._applyTempInstallation(updateDir, installDir);
michael@0 157 } catch (ex) {
michael@0 158 yield this._restoreInstallation(backupDir, installDir);
michael@0 159 throw ex;
michael@0 160 } finally {
michael@0 161 yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
michael@0 162 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 163 }
michael@0 164 }),
michael@0 165
michael@0 166 _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
michael@0 167 yield moveDirectory(aTmpDir, aInstallDir);
michael@0 168
michael@0 169 this._createSystemFiles(aInstallDir);
michael@0 170 }),
michael@0 171
michael@0 172 _removeInstallation: function(keepProfile, aInstallDir) {
michael@0 173 let filesToRemove = [this.desktopINI];
michael@0 174
michael@0 175 if (keepProfile) {
michael@0 176 for (let filePath of this.backupFiles) {
michael@0 177 filesToRemove.push(OS.Path.join(aInstallDir, filePath));
michael@0 178 }
michael@0 179
michael@0 180 filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
michael@0 181 } else {
michael@0 182 filesToRemove.push(aInstallDir);
michael@0 183 }
michael@0 184
michael@0 185 removeFiles(filesToRemove);
michael@0 186 },
michael@0 187
michael@0 188 _backupInstallation: Task.async(function*(aInstallDir) {
michael@0 189 let backupDir = OS.Path.join(aInstallDir, "backup");
michael@0 190 yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
michael@0 191 yield OS.File.makeDir(backupDir);
michael@0 192
michael@0 193 for (let filePath of this.backupFiles) {
michael@0 194 yield OS.File.move(OS.Path.join(aInstallDir, filePath),
michael@0 195 OS.Path.join(backupDir, filePath));
michael@0 196 }
michael@0 197
michael@0 198 return backupDir;
michael@0 199 }),
michael@0 200
michael@0 201 _restoreInstallation: function(aBackupDir, aInstallDir) {
michael@0 202 return moveDirectory(aBackupDir, aInstallDir);
michael@0 203 },
michael@0 204
michael@0 205 _copyPrebuiltFiles: function(aDir) {
michael@0 206 let destDir = getFile(aDir);
michael@0 207 let stub = getFile(this.runtimeFolder, this.webapprt);
michael@0 208 stub.copyTo(destDir, null);
michael@0 209 },
michael@0 210
michael@0 211 /**
michael@0 212 * Translate marketplace categories to freedesktop.org categories.
michael@0 213 *
michael@0 214 * @link http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry
michael@0 215 *
michael@0 216 * @return an array of categories
michael@0 217 */
michael@0 218 _translateCategories: function() {
michael@0 219 let translations = {
michael@0 220 "books": "Education;Literature",
michael@0 221 "business": "Finance",
michael@0 222 "education": "Education",
michael@0 223 "entertainment": "Amusement",
michael@0 224 "sports": "Sports",
michael@0 225 "games": "Game",
michael@0 226 "health-fitness": "MedicalSoftware",
michael@0 227 "lifestyle": "Amusement",
michael@0 228 "music": "Audio;Music",
michael@0 229 "news-weather": "News",
michael@0 230 "photo-video": "Video;AudioVideo;Photography",
michael@0 231 "productivity": "Office",
michael@0 232 "shopping": "Amusement",
michael@0 233 "social": "Chat",
michael@0 234 "travel": "Amusement",
michael@0 235 "reference": "Science;Education;Documentation",
michael@0 236 "maps-navigation": "Maps",
michael@0 237 "utilities": "Utility"
michael@0 238 };
michael@0 239
michael@0 240 // The trailing semicolon is needed as written in the freedesktop specification
michael@0 241 let categories = "";
michael@0 242 for (let category of this.categories) {
michael@0 243 let catLower = category.toLowerCase();
michael@0 244 if (catLower in translations) {
michael@0 245 categories += translations[catLower] + ";";
michael@0 246 }
michael@0 247 }
michael@0 248
michael@0 249 return categories;
michael@0 250 },
michael@0 251
michael@0 252 _createConfigFiles: function(aDir) {
michael@0 253 // ${InstallDir}/webapp.json
michael@0 254 yield writeToFile(OS.Path.join(aDir, this.configJson),
michael@0 255 JSON.stringify(this.webappJson));
michael@0 256
michael@0 257 let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
michael@0 258
michael@0 259 // ${InstallDir}/webapp.ini
michael@0 260 let webappINIfile = getFile(aDir, this.webappINI);
michael@0 261
michael@0 262 let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
michael@0 263 getService(Ci.nsIINIParserFactory).
michael@0 264 createINIParser(webappINIfile).
michael@0 265 QueryInterface(Ci.nsIINIParserWriter);
michael@0 266 writer.setString("Webapp", "Name", this.appName);
michael@0 267 writer.setString("Webapp", "Profile", this.uniqueName);
michael@0 268 writer.setString("Webapp", "UninstallMsg", webappsBundle.formatStringFromName("uninstall.notification", [this.appName], 1));
michael@0 269 writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
michael@0 270 writer.writeFile();
michael@0 271 },
michael@0 272
michael@0 273 _createSystemFiles: function(aInstallDir) {
michael@0 274 let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
michael@0 275
michael@0 276 let webapprtPath = OS.Path.join(aInstallDir, this.webapprt);
michael@0 277
michael@0 278 // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
michael@0 279 let desktopINIfile = getFile(this.desktopINI);
michael@0 280 if (desktopINIfile.parent && !desktopINIfile.parent.exists()) {
michael@0 281 desktopINIfile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
michael@0 282 }
michael@0 283
michael@0 284 let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
michael@0 285 getService(Ci.nsIINIParserFactory).
michael@0 286 createINIParser(desktopINIfile).
michael@0 287 QueryInterface(Ci.nsIINIParserWriter);
michael@0 288 writer.setString("Desktop Entry", "Name", this.appName);
michael@0 289 writer.setString("Desktop Entry", "Comment", this.shortDescription);
michael@0 290 writer.setString("Desktop Entry", "Exec", '"' + webapprtPath + '"');
michael@0 291 writer.setString("Desktop Entry", "Icon", OS.Path.join(aInstallDir,
michael@0 292 this.iconFile));
michael@0 293 writer.setString("Desktop Entry", "Type", "Application");
michael@0 294 writer.setString("Desktop Entry", "Terminal", "false");
michael@0 295
michael@0 296 let categories = this._translateCategories();
michael@0 297 if (categories)
michael@0 298 writer.setString("Desktop Entry", "Categories", categories);
michael@0 299
michael@0 300 writer.setString("Desktop Entry", "Actions", "Uninstall;");
michael@0 301 writer.setString("Desktop Action Uninstall", "Name", webappsBundle.GetStringFromName("uninstall.label"));
michael@0 302 writer.setString("Desktop Action Uninstall", "Exec", webapprtPath + " -remove");
michael@0 303
michael@0 304 writer.writeFile();
michael@0 305
michael@0 306 desktopINIfile.permissions = PERMS_FILE | OS.Constants.libc.S_IXUSR;
michael@0 307 },
michael@0 308
michael@0 309 /**
michael@0 310 * Process the icon from the imageStream as retrieved from
michael@0 311 * the URL by getIconForApp().
michael@0 312 *
michael@0 313 * @param aMimeType ahe icon mimetype
michael@0 314 * @param aImageStream the stream for the image data
michael@0 315 * @param aDir the directory where the icon should be stored
michael@0 316 */
michael@0 317 _processIcon: function(aMimeType, aImageStream, aDir) {
michael@0 318 let deferred = Promise.defer();
michael@0 319
michael@0 320 let imgTools = Cc["@mozilla.org/image/tools;1"].
michael@0 321 createInstance(Ci.imgITools);
michael@0 322
michael@0 323 let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
michael@0 324 let iconStream = imgTools.encodeImage(imgContainer, "image/png");
michael@0 325
michael@0 326 let iconFile = getFile(aDir, this.iconFile);
michael@0 327 let outputStream = FileUtils.openSafeFileOutputStream(iconFile);
michael@0 328 NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
michael@0 329 if (Components.isSuccessCode(aResult)) {
michael@0 330 deferred.resolve();
michael@0 331 } else {
michael@0 332 deferred.reject("Failure copying icon: " + aResult);
michael@0 333 }
michael@0 334 });
michael@0 335
michael@0 336 return deferred.promise;
michael@0 337 }
michael@0 338 }

mercurial