Sat, 03 Jan 2015 20:18:00 +0100
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 | } |