toolkit/webapps/MacNativeApp.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 const USER_LIB_DIR = OS.Constants.Path.macUserLibDir;
michael@0 6 const LOCAL_APP_DIR = OS.Constants.Path.macLocalApplicationsDir;
michael@0 7
michael@0 8 /**
michael@0 9 * Constructor for the Mac native app shell
michael@0 10 *
michael@0 11 * @param aApp {Object} the app object provided to the install function
michael@0 12 * @param aManifest {Object} the manifest data provided by the web app
michael@0 13 * @param aCategories {Array} array of app categories
michael@0 14 * @param aRegistryDir {String} (optional) path to the registry
michael@0 15 */
michael@0 16 function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
michael@0 17 CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
michael@0 18
michael@0 19 // The ${ProfileDir} is: sanitized app name + "-" + manifest url hash
michael@0 20 this.appProfileDir = OS.Path.join(USER_LIB_DIR, "Application Support",
michael@0 21 this.uniqueName);
michael@0 22 this.configJson = "webapp.json";
michael@0 23
michael@0 24 this.contentsDir = "Contents";
michael@0 25 this.macOSDir = OS.Path.join(this.contentsDir, "MacOS");
michael@0 26 this.resourcesDir = OS.Path.join(this.contentsDir, "Resources");
michael@0 27 this.iconFile = OS.Path.join(this.resourcesDir, "appicon.icns");
michael@0 28 this.zipFile = OS.Path.join(this.resourcesDir, "application.zip");
michael@0 29 }
michael@0 30
michael@0 31 NativeApp.prototype = {
michael@0 32 __proto__: CommonNativeApp.prototype,
michael@0 33 /*
michael@0 34 * The _rootInstallDir property is the path of the directory where we install
michael@0 35 * apps. In production code, it's "/Applications". In tests, it's
michael@0 36 * "~/Applications" because on build machines we don't have enough privileges
michael@0 37 * to write to the global "/Applications" directory.
michael@0 38 */
michael@0 39 _rootInstallDir: LOCAL_APP_DIR,
michael@0 40
michael@0 41 /**
michael@0 42 * Creates a native installation of the web app in the OS
michael@0 43 *
michael@0 44 * @param aManifest {Object} the manifest data provided by the web app
michael@0 45 * @param aZipPath {String} path to the zip file for packaged apps (undefined
michael@0 46 * for hosted apps)
michael@0 47 */
michael@0 48 install: Task.async(function*(aManifest, aZipPath) {
michael@0 49 if (this._dryRun) {
michael@0 50 return;
michael@0 51 }
michael@0 52
michael@0 53 // If the application is already installed, this is a reinstallation.
michael@0 54 if (WebappOSUtils.getInstallPath(this.app)) {
michael@0 55 return yield this.prepareUpdate(aManifest, aZipPath);
michael@0 56 }
michael@0 57
michael@0 58 this._setData(aManifest);
michael@0 59
michael@0 60 let localAppDir = getFile(this._rootInstallDir);
michael@0 61 if (!localAppDir.isWritable()) {
michael@0 62 throw("Not enough privileges to install apps");
michael@0 63 }
michael@0 64
michael@0 65 let destinationName = yield getAvailableFileName([ this._rootInstallDir ],
michael@0 66 this.appNameAsFilename,
michael@0 67 ".app");
michael@0 68
michael@0 69 let installDir = OS.Path.join(this._rootInstallDir, destinationName);
michael@0 70
michael@0 71 let dir = getFile(TMP_DIR, this.appNameAsFilename + ".app");
michael@0 72 dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
michael@0 73 let tmpDir = dir.path;
michael@0 74
michael@0 75 try {
michael@0 76 yield this._createDirectoryStructure(tmpDir);
michael@0 77 this._copyPrebuiltFiles(tmpDir);
michael@0 78 yield this._createConfigFiles(tmpDir);
michael@0 79
michael@0 80 if (aZipPath) {
michael@0 81 yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
michael@0 82 }
michael@0 83
michael@0 84 yield this._getIcon(tmpDir);
michael@0 85 } catch (ex) {
michael@0 86 yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
michael@0 87 throw ex;
michael@0 88 }
michael@0 89
michael@0 90 this._removeInstallation(true, installDir);
michael@0 91
michael@0 92 try {
michael@0 93 // Move the temp installation directory to the /Applications directory
michael@0 94 yield this._applyTempInstallation(tmpDir, installDir);
michael@0 95 } catch (ex) {
michael@0 96 this._removeInstallation(false, installDir);
michael@0 97 yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
michael@0 98 throw ex;
michael@0 99 }
michael@0 100 }),
michael@0 101
michael@0 102 /**
michael@0 103 * Creates an update in a temporary directory to be applied later.
michael@0 104 *
michael@0 105 * @param aManifest {Object} the manifest data provided by the web app
michael@0 106 * @param aZipPath {String} path to the zip file for packaged apps (undefined
michael@0 107 * for hosted apps)
michael@0 108 */
michael@0 109 prepareUpdate: Task.async(function*(aManifest, aZipPath) {
michael@0 110 if (this._dryRun) {
michael@0 111 return;
michael@0 112 }
michael@0 113
michael@0 114 this._setData(aManifest);
michael@0 115
michael@0 116 let [ oldUniqueName, installDir ] = WebappOSUtils.getLaunchTarget(this.app);
michael@0 117 if (!installDir) {
michael@0 118 throw ERR_NOT_INSTALLED;
michael@0 119 }
michael@0 120
michael@0 121 if (this.uniqueName != oldUniqueName) {
michael@0 122 // Bug 919799: If the app is still in the registry, migrate its data to
michael@0 123 // the new format.
michael@0 124 throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
michael@0 125 }
michael@0 126
michael@0 127 let updateDir = OS.Path.join(installDir, "update");
michael@0 128 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 129 yield OS.File.makeDir(updateDir);
michael@0 130
michael@0 131 try {
michael@0 132 yield this._createDirectoryStructure(updateDir);
michael@0 133 this._copyPrebuiltFiles(updateDir);
michael@0 134 yield this._createConfigFiles(updateDir);
michael@0 135
michael@0 136 if (aZipPath) {
michael@0 137 yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
michael@0 138 }
michael@0 139
michael@0 140 yield this._getIcon(updateDir);
michael@0 141 } catch (ex) {
michael@0 142 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 143 throw ex;
michael@0 144 }
michael@0 145 }),
michael@0 146
michael@0 147 /**
michael@0 148 * Applies an update.
michael@0 149 */
michael@0 150 applyUpdate: Task.async(function*() {
michael@0 151 if (this._dryRun) {
michael@0 152 return;
michael@0 153 }
michael@0 154
michael@0 155 let installDir = WebappOSUtils.getInstallPath(this.app);
michael@0 156 let updateDir = OS.Path.join(installDir, "update");
michael@0 157
michael@0 158 let backupDir = yield this._backupInstallation(installDir);
michael@0 159
michael@0 160 try {
michael@0 161 // Move the update directory to the /Applications directory
michael@0 162 yield this._applyTempInstallation(updateDir, installDir);
michael@0 163 } catch (ex) {
michael@0 164 yield this._restoreInstallation(backupDir, installDir);
michael@0 165 throw ex;
michael@0 166 } finally {
michael@0 167 yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
michael@0 168 yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
michael@0 169 }
michael@0 170 }),
michael@0 171
michael@0 172 _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
michael@0 173 yield OS.File.move(OS.Path.join(aTmpDir, this.configJson),
michael@0 174 OS.Path.join(this.appProfileDir, this.configJson));
michael@0 175
michael@0 176 yield moveDirectory(aTmpDir, aInstallDir);
michael@0 177 }),
michael@0 178
michael@0 179 _removeInstallation: function(keepProfile, aInstallDir) {
michael@0 180 let filesToRemove = [ aInstallDir ];
michael@0 181
michael@0 182 if (!keepProfile) {
michael@0 183 filesToRemove.push(this.appProfileDir);
michael@0 184 }
michael@0 185
michael@0 186 removeFiles(filesToRemove);
michael@0 187 },
michael@0 188
michael@0 189 _backupInstallation: Task.async(function*(aInstallDir) {
michael@0 190 let backupDir = OS.Path.join(aInstallDir, "backup");
michael@0 191 yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
michael@0 192 yield OS.File.makeDir(backupDir);
michael@0 193
michael@0 194 yield moveDirectory(OS.Path.join(aInstallDir, this.contentsDir),
michael@0 195 backupDir);
michael@0 196 yield OS.File.move(OS.Path.join(this.appProfileDir, this.configJson),
michael@0 197 OS.Path.join(backupDir, this.configJson));
michael@0 198
michael@0 199 return backupDir;
michael@0 200 }),
michael@0 201
michael@0 202 _restoreInstallation: Task.async(function*(aBackupDir, aInstallDir) {
michael@0 203 yield OS.File.move(OS.Path.join(aBackupDir, this.configJson),
michael@0 204 OS.Path.join(this.appProfileDir, this.configJson));
michael@0 205 yield moveDirectory(aBackupDir,
michael@0 206 OS.Path.join(aInstallDir, this.contentsDir));
michael@0 207 }),
michael@0 208
michael@0 209 _createDirectoryStructure: Task.async(function*(aDir) {
michael@0 210 yield OS.File.makeDir(this.appProfileDir,
michael@0 211 { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
michael@0 212
michael@0 213 yield OS.File.makeDir(OS.Path.join(aDir, this.contentsDir),
michael@0 214 { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
michael@0 215
michael@0 216 yield OS.File.makeDir(OS.Path.join(aDir, this.macOSDir),
michael@0 217 { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
michael@0 218
michael@0 219 yield OS.File.makeDir(OS.Path.join(aDir, this.resourcesDir),
michael@0 220 { unixMode: PERMS_DIRECTORY, ignoreExisting: true });
michael@0 221 }),
michael@0 222
michael@0 223 _copyPrebuiltFiles: function(aDir) {
michael@0 224 let destDir = getFile(aDir, this.macOSDir);
michael@0 225 let stub = getFile(this.runtimeFolder, "webapprt-stub");
michael@0 226 stub.copyTo(destDir, "webapprt");
michael@0 227 },
michael@0 228
michael@0 229 _createConfigFiles: function(aDir) {
michael@0 230 // ${ProfileDir}/webapp.json
michael@0 231 yield writeToFile(OS.Path.join(aDir, this.configJson),
michael@0 232 JSON.stringify(this.webappJson));
michael@0 233
michael@0 234 // ${InstallDir}/Contents/MacOS/webapp.ini
michael@0 235 let applicationINI = getFile(aDir, this.macOSDir, "webapp.ini");
michael@0 236
michael@0 237 let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
michael@0 238 getService(Ci.nsIINIParserFactory).
michael@0 239 createINIParser(applicationINI).
michael@0 240 QueryInterface(Ci.nsIINIParserWriter);
michael@0 241 writer.setString("Webapp", "Name", this.appName);
michael@0 242 writer.setString("Webapp", "Profile", this.uniqueName);
michael@0 243 writer.writeFile();
michael@0 244 applicationINI.permissions = PERMS_FILE;
michael@0 245
michael@0 246 // ${InstallDir}/Contents/Info.plist
michael@0 247 let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
michael@0 248 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
michael@0 249 <plist version="1.0">\n\
michael@0 250 <dict>\n\
michael@0 251 <key>CFBundleDevelopmentRegion</key>\n\
michael@0 252 <string>English</string>\n\
michael@0 253 <key>CFBundleDisplayName</key>\n\
michael@0 254 <string>' + escapeXML(this.appName) + '</string>\n\
michael@0 255 <key>CFBundleExecutable</key>\n\
michael@0 256 <string>webapprt</string>\n\
michael@0 257 <key>CFBundleIconFile</key>\n\
michael@0 258 <string>appicon</string>\n\
michael@0 259 <key>CFBundleIdentifier</key>\n\
michael@0 260 <string>' + escapeXML(this.uniqueName) + '</string>\n\
michael@0 261 <key>CFBundleInfoDictionaryVersion</key>\n\
michael@0 262 <string>6.0</string>\n\
michael@0 263 <key>CFBundleName</key>\n\
michael@0 264 <string>' + escapeXML(this.appName) + '</string>\n\
michael@0 265 <key>CFBundlePackageType</key>\n\
michael@0 266 <string>APPL</string>\n\
michael@0 267 <key>CFBundleVersion</key>\n\
michael@0 268 <string>0</string>\n\
michael@0 269 <key>NSHighResolutionCapable</key>\n\
michael@0 270 <true/>\n\
michael@0 271 <key>NSPrincipalClass</key>\n\
michael@0 272 <string>GeckoNSApplication</string>\n\
michael@0 273 <key>FirefoxBinary</key>\n\
michael@0 274 #expand <string>__MOZ_MACBUNDLE_ID__</string>\n\
michael@0 275 </dict>\n\
michael@0 276 </plist>';
michael@0 277
michael@0 278 yield writeToFile(OS.Path.join(aDir, this.contentsDir, "Info.plist"),
michael@0 279 infoPListContent);
michael@0 280 },
michael@0 281
michael@0 282 /**
michael@0 283 * Process the icon from the imageStream as retrieved from
michael@0 284 * the URL by getIconForApp(). This will bundle the icon to the
michael@0 285 * app package at Contents/Resources/appicon.icns.
michael@0 286 *
michael@0 287 * @param aMimeType the icon mimetype
michael@0 288 * @param aImageStream the stream for the image data
michael@0 289 * @param aDir the directory where the icon should be stored
michael@0 290 */
michael@0 291 _processIcon: function(aMimeType, aIcon, aDir) {
michael@0 292 let deferred = Promise.defer();
michael@0 293
michael@0 294 function conversionDone(aSubject, aTopic) {
michael@0 295 if (aTopic == "process-finished") {
michael@0 296 deferred.resolve();
michael@0 297 } else {
michael@0 298 deferred.reject("Failure converting icon, exit code: " + aSubject.exitValue);
michael@0 299 }
michael@0 300 }
michael@0 301
michael@0 302 let process = Cc["@mozilla.org/process/util;1"].
michael@0 303 createInstance(Ci.nsIProcess);
michael@0 304 let sipsFile = getFile("/usr/bin/sips");
michael@0 305
michael@0 306 process.init(sipsFile);
michael@0 307 process.runAsync(["-s", "format", "icns",
michael@0 308 aIcon.path,
michael@0 309 "--out", OS.Path.join(aDir, this.iconFile),
michael@0 310 "-z", "128", "128"],
michael@0 311 9, conversionDone);
michael@0 312
michael@0 313 return deferred.promise;
michael@0 314 }
michael@0 315 }

mercurial