Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"]; |
michael@0 | 8 | |
michael@0 | 9 | const Cu = Components.utils; |
michael@0 | 10 | const Ci = Components.interfaces; |
michael@0 | 11 | const Cc = Components.classes; |
michael@0 | 12 | |
michael@0 | 13 | const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks"; |
michael@0 | 14 | const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks"; |
michael@0 | 15 | |
michael@0 | 16 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 17 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | XPCOMUtils.defineLazyModuleGetter(this, "Dict", |
michael@0 | 20 | "resource://gre/modules/Dict.jsm"); |
michael@0 | 21 | XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", |
michael@0 | 22 | "resource://gre/modules/PlacesUtils.jsm"); |
michael@0 | 23 | XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
michael@0 | 24 | "resource://gre/modules/NetUtil.jsm"); |
michael@0 | 25 | XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils", |
michael@0 | 26 | "resource://gre/modules/BookmarkHTMLUtils.jsm"); |
michael@0 | 27 | |
michael@0 | 28 | let gMigrators = null; |
michael@0 | 29 | let gProfileStartup = null; |
michael@0 | 30 | let gMigrationBundle = null; |
michael@0 | 31 | |
michael@0 | 32 | function getMigrationBundle() { |
michael@0 | 33 | if (!gMigrationBundle) { |
michael@0 | 34 | gMigrationBundle = Services.strings.createBundle( |
michael@0 | 35 | "chrome://browser/locale/migration/migration.properties"); |
michael@0 | 36 | } |
michael@0 | 37 | return gMigrationBundle; |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | /** |
michael@0 | 41 | * Figure out what is the default browser, and if there is a migrator |
michael@0 | 42 | * for it, return that migrator's internal name. |
michael@0 | 43 | * For the time being, the "internal name" of a migraotr is its contract-id |
michael@0 | 44 | * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie), |
michael@0 | 45 | * but it will soon be exposed properly. |
michael@0 | 46 | */ |
michael@0 | 47 | function getMigratorKeyForDefaultBrowser() { |
michael@0 | 48 | // Don't map Firefox to the Firefox migrator, because we don't |
michael@0 | 49 | // expect it to ever show up as an option in the wizard. |
michael@0 | 50 | // We may want to revise this if/when we use separate profiles |
michael@0 | 51 | // for each Firefox-update channel. |
michael@0 | 52 | const APP_DESC_TO_KEY = { |
michael@0 | 53 | "Internet Explorer": "ie", |
michael@0 | 54 | "Safari": "safari", |
michael@0 | 55 | "Google Chrome": "chrome", // Windows, Linux |
michael@0 | 56 | "Chrome": "chrome", // OS X |
michael@0 | 57 | }; |
michael@0 | 58 | |
michael@0 | 59 | let browserDesc = ""; |
michael@0 | 60 | try { |
michael@0 | 61 | let browserDesc = |
michael@0 | 62 | Cc["@mozilla.org/uriloader/external-protocol-service;1"]. |
michael@0 | 63 | getService(Ci.nsIExternalProtocolService). |
michael@0 | 64 | getApplicationDescription("http"); |
michael@0 | 65 | return APP_DESC_TO_KEY[browserDesc] || ""; |
michael@0 | 66 | } |
michael@0 | 67 | catch(ex) { |
michael@0 | 68 | Cu.reportError("Could not detect default browser: " + ex); |
michael@0 | 69 | } |
michael@0 | 70 | return ""; |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | /** |
michael@0 | 74 | * Shared prototype for migrators, implementing nsIBrowserProfileMigrator. |
michael@0 | 75 | * |
michael@0 | 76 | * To implement a migrator: |
michael@0 | 77 | * 1. Import this module. |
michael@0 | 78 | * 2. Create the prototype for the migrator, extending MigratorPrototype. |
michael@0 | 79 | * Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype); |
michael@0 | 80 | * 3. Set classDescription, contractID and classID for your migrator, and set |
michael@0 | 81 | * NSGetFactory appropriately. |
michael@0 | 82 | * 4. If the migrator supports multiple profiles, override the sourceProfiles |
michael@0 | 83 | * Here we default for single-profile migrator. |
michael@0 | 84 | * 5. Implement getResources(aProfile) (see below). |
michael@0 | 85 | * 6. If the migrator supports reading the home page of the source browser, |
michael@0 | 86 | * override |sourceHomePageURL| getter. |
michael@0 | 87 | * 7. For startup-only migrators, override |startupOnlyMigrator|. |
michael@0 | 88 | */ |
michael@0 | 89 | this.MigratorPrototype = { |
michael@0 | 90 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]), |
michael@0 | 91 | |
michael@0 | 92 | /** |
michael@0 | 93 | * OVERRIDE IF AND ONLY IF the source supports multiple profiles. |
michael@0 | 94 | * |
michael@0 | 95 | * Returns array of profiles (by names) from which data may be imported. |
michael@0 | 96 | * |
michael@0 | 97 | * Only profiles from which data can be imported should be listed. Otherwise |
michael@0 | 98 | * the behavior of the migration wizard isn't well-defined. |
michael@0 | 99 | * |
michael@0 | 100 | * For a single-profile source (e.g. safari, ie), this returns null, |
michael@0 | 101 | * and not an empty array. That is the default implementation. |
michael@0 | 102 | */ |
michael@0 | 103 | get sourceProfiles() null, |
michael@0 | 104 | |
michael@0 | 105 | /** |
michael@0 | 106 | * MUST BE OVERRIDDEN. |
michael@0 | 107 | * |
michael@0 | 108 | * Returns an array of "migration resources" objects for the given profile, |
michael@0 | 109 | * or for the "default" profile, if the migrator does not support multiple |
michael@0 | 110 | * profiles. |
michael@0 | 111 | * |
michael@0 | 112 | * Each migration resource should provide: |
michael@0 | 113 | * - a |type| getter, retunring any of the migration types (see |
michael@0 | 114 | * nsIBrowserProfileMigrator). |
michael@0 | 115 | * |
michael@0 | 116 | * - a |migrate| method, taking a single argument, aCallback(bool success), |
michael@0 | 117 | * for migrating the data for this resource. It may do its job |
michael@0 | 118 | * synchronously or asynchronously. Either way, it must call |
michael@0 | 119 | * aCallback(bool aSuccess) when it's done. In the case of an exception |
michael@0 | 120 | * thrown from |migrate|, it's taken as if aCallback(false) is called. |
michael@0 | 121 | * |
michael@0 | 122 | * Note: In the case of a simple asynchronous implementation, you may find |
michael@0 | 123 | * MigrationUtils.wrapMigrateFunction handy for handling aCallback easily. |
michael@0 | 124 | * |
michael@0 | 125 | * For each migration type listed in nsIBrowserProfileMigrator, multiple |
michael@0 | 126 | * migration resources may be provided. This practice is useful when the |
michael@0 | 127 | * data for a certain migration type is independently stored in few |
michael@0 | 128 | * locations. For example, the mac version of Safari stores its "reading list" |
michael@0 | 129 | * bookmarks in a separate property list. |
michael@0 | 130 | * |
michael@0 | 131 | * Note that the importation of a particular migration type is reported as |
michael@0 | 132 | * successful if _any_ of its resources succeeded to import (that is, called, |
michael@0 | 133 | * |aCallback(true)|). However, completion-status for a particular migration |
michael@0 | 134 | * type is reported to the UI only once all of its migrators have called |
michael@0 | 135 | * aCallback. |
michael@0 | 136 | * |
michael@0 | 137 | * @note The returned array should only include resources from which data |
michael@0 | 138 | * can be imported. So, for example, before adding a resource for the |
michael@0 | 139 | * BOOKMARKS migration type, you should check if you should check that the |
michael@0 | 140 | * bookmarks file exists. |
michael@0 | 141 | * |
michael@0 | 142 | * @param aProfile |
michael@0 | 143 | * The profile from which data may be imported, or an empty string |
michael@0 | 144 | * in the case of a single-profile migrator. |
michael@0 | 145 | * In the case of multiple-profiles migrator, it is guaranteed that |
michael@0 | 146 | * aProfile is a value returned by the sourceProfiles getter (see |
michael@0 | 147 | * above). |
michael@0 | 148 | */ |
michael@0 | 149 | getResources: function MP_getResources(aProfile) { |
michael@0 | 150 | throw new Error("getResources must be overridden"); |
michael@0 | 151 | }, |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now, |
michael@0 | 155 | * that is just the Firefox migrator, see bug 737381). Default: false. |
michael@0 | 156 | * |
michael@0 | 157 | * Startup-only migrators are different in two ways: |
michael@0 | 158 | * - they may only be used during startup. |
michael@0 | 159 | * - the user-profile is half baked during migration. The folder exists, |
michael@0 | 160 | * but it's only accessible through MigrationUtils.profileStartup. |
michael@0 | 161 | * The migrator can call MigrationUtils.profileStartup.doStartup |
michael@0 | 162 | * at any point in order to initialize the profile. |
michael@0 | 163 | */ |
michael@0 | 164 | get startupOnlyMigrator() false, |
michael@0 | 165 | |
michael@0 | 166 | /** |
michael@0 | 167 | * OVERRIDE IF AND ONLY IF your migrator supports importing the homepage. |
michael@0 | 168 | * @see nsIBrowserProfileMigrator |
michael@0 | 169 | */ |
michael@0 | 170 | get sourceHomePageURL() "", |
michael@0 | 171 | |
michael@0 | 172 | /** |
michael@0 | 173 | * DO NOT OVERRIDE - After deCOMing migration, the UI will just call |
michael@0 | 174 | * getResources. |
michael@0 | 175 | * |
michael@0 | 176 | * @see nsIBrowserProfileMigrator |
michael@0 | 177 | */ |
michael@0 | 178 | getMigrateData: function MP_getMigrateData(aProfile) { |
michael@0 | 179 | let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))]; |
michael@0 | 180 | return types.reduce(function(a, b) a |= b, 0); |
michael@0 | 181 | }, |
michael@0 | 182 | |
michael@0 | 183 | /** |
michael@0 | 184 | * DO NOT OVERRIDE - After deCOMing migration, the UI will just call |
michael@0 | 185 | * migrate for each resource. |
michael@0 | 186 | * |
michael@0 | 187 | * @see nsIBrowserProfileMigrator |
michael@0 | 188 | */ |
michael@0 | 189 | migrate: function MP_migrate(aItems, aStartup, aProfile) { |
michael@0 | 190 | let resources = this._getMaybeCachedResources(aProfile); |
michael@0 | 191 | if (resources.length == 0) |
michael@0 | 192 | throw new Error("migrate called for a non-existent source"); |
michael@0 | 193 | |
michael@0 | 194 | if (aItems != Ci.nsIBrowserProfileMigrator.ALL) |
michael@0 | 195 | resources = [r for each (r in resources) if (aItems & r.type)]; |
michael@0 | 196 | |
michael@0 | 197 | // Called either directly or through the bookmarks import callback. |
michael@0 | 198 | function doMigrate() { |
michael@0 | 199 | // TODO: use Map (for the items) and Set (for the resources) |
michael@0 | 200 | // once they are iterable. |
michael@0 | 201 | let resourcesGroupedByItems = new Dict(); |
michael@0 | 202 | resources.forEach(function(resource) { |
michael@0 | 203 | if (resourcesGroupedByItems.has(resource.type)) |
michael@0 | 204 | resourcesGroupedByItems.get(resource.type).push(resource); |
michael@0 | 205 | else |
michael@0 | 206 | resourcesGroupedByItems.set(resource.type, [resource]); |
michael@0 | 207 | }); |
michael@0 | 208 | |
michael@0 | 209 | if (resourcesGroupedByItems.count == 0) |
michael@0 | 210 | throw new Error("No items to import"); |
michael@0 | 211 | |
michael@0 | 212 | let notify = function(aMsg, aItemType) { |
michael@0 | 213 | Services.obs.notifyObservers(null, aMsg, aItemType); |
michael@0 | 214 | } |
michael@0 | 215 | |
michael@0 | 216 | notify("Migration:Started"); |
michael@0 | 217 | resourcesGroupedByItems.listkeys().forEach(function(migrationType) { |
michael@0 | 218 | let migrationTypeA = migrationType; |
michael@0 | 219 | let itemResources = resourcesGroupedByItems.get(migrationType); |
michael@0 | 220 | notify("Migration:ItemBeforeMigrate", migrationType); |
michael@0 | 221 | |
michael@0 | 222 | let itemSuccess = false; |
michael@0 | 223 | itemResources.forEach(function(resource) { |
michael@0 | 224 | let resourceDone = function(aSuccess) { |
michael@0 | 225 | let resourceIndex = itemResources.indexOf(resource); |
michael@0 | 226 | if (resourceIndex != -1) { |
michael@0 | 227 | itemResources.splice(resourceIndex, 1); |
michael@0 | 228 | itemSuccess |= aSuccess; |
michael@0 | 229 | if (itemResources.length == 0) { |
michael@0 | 230 | resourcesGroupedByItems.del(migrationType); |
michael@0 | 231 | notify(itemSuccess ? |
michael@0 | 232 | "Migration:ItemAfterMigrate" : "Migration:ItemError", |
michael@0 | 233 | migrationType); |
michael@0 | 234 | if (resourcesGroupedByItems.count == 0) |
michael@0 | 235 | notify("Migration:Ended"); |
michael@0 | 236 | } |
michael@0 | 237 | } |
michael@0 | 238 | }; |
michael@0 | 239 | |
michael@0 | 240 | Services.tm.mainThread.dispatch(function() { |
michael@0 | 241 | // If migrate throws, an error occurred, and the callback |
michael@0 | 242 | // (itemMayBeDone) might haven't been called. |
michael@0 | 243 | try { |
michael@0 | 244 | resource.migrate(resourceDone); |
michael@0 | 245 | } |
michael@0 | 246 | catch(ex) { |
michael@0 | 247 | Cu.reportError(ex); |
michael@0 | 248 | resourceDone(false); |
michael@0 | 249 | } |
michael@0 | 250 | }, Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 251 | }); |
michael@0 | 252 | }); |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) { |
michael@0 | 256 | MigrationUtils.profileStartup.doStartup(); |
michael@0 | 257 | |
michael@0 | 258 | // If we're about to migrate bookmarks, first import the default bookmarks. |
michael@0 | 259 | // Note We do not need to do so for the Firefox migrator |
michael@0 | 260 | // (=startupOnlyMigrator), as it just copies over the places database |
michael@0 | 261 | // from another profile. |
michael@0 | 262 | const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS; |
michael@0 | 263 | let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS); |
michael@0 | 264 | if (migratingBookmarks) { |
michael@0 | 265 | let browserGlue = Cc["@mozilla.org/browser/browserglue;1"]. |
michael@0 | 266 | getService(Ci.nsIObserver); |
michael@0 | 267 | browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, ""); |
michael@0 | 268 | |
michael@0 | 269 | // Note doMigrate doesn't care about the success of the import. |
michael@0 | 270 | let onImportComplete = function() { |
michael@0 | 271 | browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, ""); |
michael@0 | 272 | doMigrate(); |
michael@0 | 273 | }; |
michael@0 | 274 | BookmarkHTMLUtils.importFromURL( |
michael@0 | 275 | "resource:///defaults/profile/bookmarks.html", true).then( |
michael@0 | 276 | onImportComplete, onImportComplete); |
michael@0 | 277 | return; |
michael@0 | 278 | } |
michael@0 | 279 | } |
michael@0 | 280 | doMigrate(); |
michael@0 | 281 | }, |
michael@0 | 282 | |
michael@0 | 283 | /** |
michael@0 | 284 | * DO NOT OVERRIDE - After deCOMing migration, this code |
michael@0 | 285 | * won't be part of the migrator itself. |
michael@0 | 286 | * |
michael@0 | 287 | * @see nsIBrowserProfileMigrator |
michael@0 | 288 | */ |
michael@0 | 289 | get sourceExists() { |
michael@0 | 290 | if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration) |
michael@0 | 291 | return false; |
michael@0 | 292 | |
michael@0 | 293 | // For a single-profile source, check if any data is available. |
michael@0 | 294 | // For multiple-profiles source, make sure that at least one |
michael@0 | 295 | // profile is available. |
michael@0 | 296 | let exists = false; |
michael@0 | 297 | try { |
michael@0 | 298 | let profiles = this.sourceProfiles; |
michael@0 | 299 | if (!profiles) { |
michael@0 | 300 | let resources = this._getMaybeCachedResources(""); |
michael@0 | 301 | if (resources && resources.length > 0) |
michael@0 | 302 | exists = true; |
michael@0 | 303 | } |
michael@0 | 304 | else { |
michael@0 | 305 | exists = profiles.length > 0; |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | catch(ex) { |
michael@0 | 309 | Cu.reportError(ex); |
michael@0 | 310 | } |
michael@0 | 311 | return exists; |
michael@0 | 312 | }, |
michael@0 | 313 | |
michael@0 | 314 | /*** PRIVATE STUFF - DO NOT OVERRIDE ***/ |
michael@0 | 315 | _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) { |
michael@0 | 316 | if (this._resourcesByProfile) { |
michael@0 | 317 | if (aProfile in this._resourcesByProfile) |
michael@0 | 318 | return this._resourcesByProfile[aProfile]; |
michael@0 | 319 | } |
michael@0 | 320 | else { |
michael@0 | 321 | this._resourcesByProfile = { }; |
michael@0 | 322 | } |
michael@0 | 323 | return this._resourcesByProfile[aProfile] = this.getResources(aProfile); |
michael@0 | 324 | } |
michael@0 | 325 | }; |
michael@0 | 326 | |
michael@0 | 327 | this.MigrationUtils = Object.freeze({ |
michael@0 | 328 | resourceTypes: { |
michael@0 | 329 | SETTINGS: Ci.nsIBrowserProfileMigrator.SETTINGS, |
michael@0 | 330 | COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES, |
michael@0 | 331 | HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY, |
michael@0 | 332 | FORMDATA: Ci.nsIBrowserProfileMigrator.FORMDATA, |
michael@0 | 333 | PASSWORDS: Ci.nsIBrowserProfileMigrator.PASSWORDS, |
michael@0 | 334 | BOOKMARKS: Ci.nsIBrowserProfileMigrator.BOOKMARKS, |
michael@0 | 335 | OTHERDATA: Ci.nsIBrowserProfileMigrator.OTHERDATA, |
michael@0 | 336 | SESSION: Ci.nsIBrowserProfileMigrator.SESSION, |
michael@0 | 337 | }, |
michael@0 | 338 | |
michael@0 | 339 | /** |
michael@0 | 340 | * Helper for implementing simple asynchronous cases of migration resources' |
michael@0 | 341 | * |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method |
michael@0 | 342 | * just waits for some file to be read, for example, and then migrates |
michael@0 | 343 | * everything right away, you can wrap the async-function with this helper |
michael@0 | 344 | * and not worry about notifying the callback. |
michael@0 | 345 | * |
michael@0 | 346 | * For example, instead of writing: |
michael@0 | 347 | * setTimeout(function() { |
michael@0 | 348 | * try { |
michael@0 | 349 | * .... |
michael@0 | 350 | * aCallback(true); |
michael@0 | 351 | * } |
michael@0 | 352 | * catch() { |
michael@0 | 353 | * aCallback(false); |
michael@0 | 354 | * } |
michael@0 | 355 | * }, 0); |
michael@0 | 356 | * |
michael@0 | 357 | * You may write: |
michael@0 | 358 | * setTimeout(MigrationUtils.wrapMigrateFunction(function() { |
michael@0 | 359 | * if (importingFromMosaic) |
michael@0 | 360 | * throw Cr.NS_ERROR_UNEXPECTED; |
michael@0 | 361 | * }, aCallback), 0); |
michael@0 | 362 | * |
michael@0 | 363 | * ... and aCallback will be called with aSuccess=false when importing |
michael@0 | 364 | * from Mosaic, or with aSuccess=true otherwise. |
michael@0 | 365 | * |
michael@0 | 366 | * @param aFunction |
michael@0 | 367 | * the function that will be called sometime later. If aFunction |
michael@0 | 368 | * throws when it's called, aCallback(false) is called, otherwise |
michael@0 | 369 | * aCallback(true) is called. |
michael@0 | 370 | * @param aCallback |
michael@0 | 371 | * the callback function passed to |migrate|. |
michael@0 | 372 | * @return the wrapped function. |
michael@0 | 373 | */ |
michael@0 | 374 | wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) { |
michael@0 | 375 | return function() { |
michael@0 | 376 | let success = false; |
michael@0 | 377 | try { |
michael@0 | 378 | aFunction.apply(null, arguments); |
michael@0 | 379 | success = true; |
michael@0 | 380 | } |
michael@0 | 381 | catch(ex) { |
michael@0 | 382 | Cu.reportError(ex); |
michael@0 | 383 | } |
michael@0 | 384 | // Do not change this to call aCallback directly in try try & catch |
michael@0 | 385 | // blocks, because if aCallback throws, we may end up calling aCallback |
michael@0 | 386 | // twice. |
michael@0 | 387 | aCallback(success); |
michael@0 | 388 | } |
michael@0 | 389 | }, |
michael@0 | 390 | |
michael@0 | 391 | /** |
michael@0 | 392 | * Gets a string from the migration bundle. Shorthand for |
michael@0 | 393 | * nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for |
michael@0 | 394 | * nsIStringBundle.formatStringFromName if it is. |
michael@0 | 395 | * |
michael@0 | 396 | * This method also takes care of "bumped" keys (See bug 737381 comment 8 for |
michael@0 | 397 | * details). |
michael@0 | 398 | * |
michael@0 | 399 | * @param aKey |
michael@0 | 400 | * The key of the string to retrieve. |
michael@0 | 401 | * @param aReplacemts |
michael@0 | 402 | * [optioanl] Array of replacements to run on the retrieved string. |
michael@0 | 403 | * @return the retrieved string. |
michael@0 | 404 | * |
michael@0 | 405 | * @see nsIStringBundle |
michael@0 | 406 | */ |
michael@0 | 407 | getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) { |
michael@0 | 408 | const OVERRIDES = { |
michael@0 | 409 | "4_firefox": "4_firefox_history_and_bookmarks", |
michael@0 | 410 | "64_firefox": "64_firefox_other" |
michael@0 | 411 | }; |
michael@0 | 412 | aKey = OVERRIDES[aKey] || aKey; |
michael@0 | 413 | |
michael@0 | 414 | if (aReplacements === undefined) |
michael@0 | 415 | return getMigrationBundle().GetStringFromName(aKey); |
michael@0 | 416 | return getMigrationBundle().formatStringFromName( |
michael@0 | 417 | aKey, aReplacements, aReplacements.length); |
michael@0 | 418 | }, |
michael@0 | 419 | |
michael@0 | 420 | /** |
michael@0 | 421 | * Helper for creating a folder for imported bookmarks from a particular |
michael@0 | 422 | * migration source. The folder is created at the end of the given folder. |
michael@0 | 423 | * |
michael@0 | 424 | * @param aSourceNameStr |
michael@0 | 425 | * the source name (first letter capitalized). This is used |
michael@0 | 426 | * for reading the localized source name from the migration |
michael@0 | 427 | * bundle (e.g. if aSourceNameStr is Mosaic, this will try to read |
michael@0 | 428 | * sourceNameMosaic from the migration bundle). |
michael@0 | 429 | * @param aParentId |
michael@0 | 430 | * the item-id of the folder in which the new folder should be |
michael@0 | 431 | * created. |
michael@0 | 432 | * @return the item-id of the new folder. |
michael@0 | 433 | */ |
michael@0 | 434 | createImportedBookmarksFolder: |
michael@0 | 435 | function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) { |
michael@0 | 436 | let source = this.getLocalizedString("sourceName" + aSourceNameStr); |
michael@0 | 437 | let label = this.getLocalizedString("importedBookmarksFolder", [source]); |
michael@0 | 438 | return PlacesUtils.bookmarks.createFolder( |
michael@0 | 439 | aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX); |
michael@0 | 440 | }, |
michael@0 | 441 | |
michael@0 | 442 | get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(), |
michael@0 | 443 | |
michael@0 | 444 | /* |
michael@0 | 445 | * Returns the migrator for the given source, if any data is available |
michael@0 | 446 | * for this source, or null otherwise. |
michael@0 | 447 | * |
michael@0 | 448 | * @param aKey internal name of the migration source. |
michael@0 | 449 | * Supported values: ie (windows), |
michael@0 | 450 | * safari (mac/windows), |
michael@0 | 451 | * chrome (mac/windows/linux), |
michael@0 | 452 | * firefox. |
michael@0 | 453 | * |
michael@0 | 454 | * If null is returned, either no data can be imported |
michael@0 | 455 | * for the given migrator, or aMigratorKey is invalid (e.g. ie on mac, |
michael@0 | 456 | * or mosaic everywhere). This method should be used rather than direct |
michael@0 | 457 | * getService for future compatibility (see bug 718280). |
michael@0 | 458 | * |
michael@0 | 459 | * @return profile migrator implementing nsIBrowserProfileMigrator, if it can |
michael@0 | 460 | * import any data, null otherwise. |
michael@0 | 461 | */ |
michael@0 | 462 | getMigrator: function MU_getMigrator(aKey) { |
michael@0 | 463 | let migrator = null; |
michael@0 | 464 | if (this._migrators.has(aKey)) { |
michael@0 | 465 | migrator = this._migrators.get(aKey); |
michael@0 | 466 | } |
michael@0 | 467 | else { |
michael@0 | 468 | try { |
michael@0 | 469 | migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" + |
michael@0 | 470 | aKey].createInstance(Ci.nsIBrowserProfileMigrator); |
michael@0 | 471 | } |
michael@0 | 472 | catch(ex) { } |
michael@0 | 473 | this._migrators.set(aKey, migrator); |
michael@0 | 474 | } |
michael@0 | 475 | |
michael@0 | 476 | return migrator && migrator.sourceExists ? migrator : null; |
michael@0 | 477 | }, |
michael@0 | 478 | |
michael@0 | 479 | // Iterates the available migrators, in the most suitable |
michael@0 | 480 | // order for the running platform. |
michael@0 | 481 | get migrators() { |
michael@0 | 482 | let migratorKeysOrdered = [ |
michael@0 | 483 | #ifdef XP_WIN |
michael@0 | 484 | "ie", "chrome", "safari" |
michael@0 | 485 | #elifdef XP_MACOSX |
michael@0 | 486 | "safari", "chrome" |
michael@0 | 487 | #elifdef XP_UNIX |
michael@0 | 488 | "chrome" |
michael@0 | 489 | #endif |
michael@0 | 490 | ]; |
michael@0 | 491 | |
michael@0 | 492 | // If a supported default browser is found check it first |
michael@0 | 493 | // so that the wizard defaults to import from that browser. |
michael@0 | 494 | let defaultBrowserKey = getMigratorKeyForDefaultBrowser(); |
michael@0 | 495 | if (defaultBrowserKey) |
michael@0 | 496 | migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0); |
michael@0 | 497 | |
michael@0 | 498 | for (let migratorKey of migratorKeysOrdered) { |
michael@0 | 499 | let migrator = this.getMigrator(migratorKey); |
michael@0 | 500 | if (migrator) |
michael@0 | 501 | yield migrator; |
michael@0 | 502 | } |
michael@0 | 503 | }, |
michael@0 | 504 | |
michael@0 | 505 | // Whether or not we're in the process of startup migration |
michael@0 | 506 | get isStartupMigration() gProfileStartup != null, |
michael@0 | 507 | |
michael@0 | 508 | /** |
michael@0 | 509 | * In the case of startup migration, this is set to the nsIProfileStartup |
michael@0 | 510 | * instance passed to ProfileMigrator's migrate. |
michael@0 | 511 | * |
michael@0 | 512 | * @see showMigrationWizard |
michael@0 | 513 | */ |
michael@0 | 514 | get profileStartup() gProfileStartup, |
michael@0 | 515 | |
michael@0 | 516 | /** |
michael@0 | 517 | * Show the migration wizard. On mac, this may just focus the wizard if it's |
michael@0 | 518 | * already running, in which case aOpener and aParams are ignored. |
michael@0 | 519 | * |
michael@0 | 520 | * @param [optional] aOpener |
michael@0 | 521 | * the window that asks to open the wizard. |
michael@0 | 522 | * @param [optioanl] aParams |
michael@0 | 523 | * arguments for the migration wizard, in the form of an nsIArray. |
michael@0 | 524 | * This is passed as-is for the params argument of |
michael@0 | 525 | * nsIWindowWatcher.openWindow. |
michael@0 | 526 | */ |
michael@0 | 527 | showMigrationWizard: |
michael@0 | 528 | function MU_showMigrationWizard(aOpener, aParams) { |
michael@0 | 529 | let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no"; |
michael@0 | 530 | #ifdef XP_MACOSX |
michael@0 | 531 | if (!this.isStartupMigration) { |
michael@0 | 532 | let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard"); |
michael@0 | 533 | if (win) { |
michael@0 | 534 | win.focus(); |
michael@0 | 535 | return; |
michael@0 | 536 | } |
michael@0 | 537 | // On mac, the migration wiazrd should only be modal in the case of |
michael@0 | 538 | // startup-migration. |
michael@0 | 539 | features = "centerscreen,chrome,resizable=no"; |
michael@0 | 540 | } |
michael@0 | 541 | #endif |
michael@0 | 542 | |
michael@0 | 543 | Services.ww.openWindow(aOpener, |
michael@0 | 544 | "chrome://browser/content/migration/migration.xul", |
michael@0 | 545 | "_blank", |
michael@0 | 546 | features, |
michael@0 | 547 | aParams); |
michael@0 | 548 | }, |
michael@0 | 549 | |
michael@0 | 550 | /** |
michael@0 | 551 | * Show the migration wizard for startup-migration. This should only be |
michael@0 | 552 | * called by ProfileMigrator (see ProfileMigrator.js), which implements |
michael@0 | 553 | * nsIProfileMigrator. |
michael@0 | 554 | * |
michael@0 | 555 | * @param aProfileStartup |
michael@0 | 556 | * the nsIProfileStartup instance provided to ProfileMigrator.migrate. |
michael@0 | 557 | * @param [optional] aMigratorKey |
michael@0 | 558 | * If set, the migration wizard will import from the corresponding |
michael@0 | 559 | * migrator, bypassing the source-selection page. Otherwise, the |
michael@0 | 560 | * source-selection page will be displayed, either with the default |
michael@0 | 561 | * browser selected, if it could be detected and if there is a |
michael@0 | 562 | * migrator for it, or with the first option selected as a fallback |
michael@0 | 563 | * (The first option is hardcoded to be the most common browser for |
michael@0 | 564 | * the OS we run on. See migration.xul). |
michael@0 | 565 | * |
michael@0 | 566 | * @throws if aMigratorKey is invalid or if it points to a non-existent |
michael@0 | 567 | * source. |
michael@0 | 568 | */ |
michael@0 | 569 | startupMigration: |
michael@0 | 570 | function MU_startupMigrator(aProfileStartup, aMigratorKey) { |
michael@0 | 571 | if (!aProfileStartup) { |
michael@0 | 572 | throw new Error("an profile-startup instance is required for startup-migration"); |
michael@0 | 573 | } |
michael@0 | 574 | gProfileStartup = aProfileStartup; |
michael@0 | 575 | |
michael@0 | 576 | let skipSourcePage = false, migrator = null, migratorKey = ""; |
michael@0 | 577 | if (aMigratorKey) { |
michael@0 | 578 | migrator = this.getMigrator(aMigratorKey); |
michael@0 | 579 | if (!migrator) { |
michael@0 | 580 | // aMigratorKey must point to a valid source, so, if it doesn't |
michael@0 | 581 | // cleanup and throw. |
michael@0 | 582 | this.finishMigration(); |
michael@0 | 583 | throw new Error("startMigration was asked to open auto-migrate from " + |
michael@0 | 584 | "a non-existent source: " + aMigratorKey); |
michael@0 | 585 | } |
michael@0 | 586 | migratorKey = aMigratorKey; |
michael@0 | 587 | skipSourcePage = true; |
michael@0 | 588 | } |
michael@0 | 589 | else { |
michael@0 | 590 | let defaultBrowserKey = getMigratorKeyForDefaultBrowser(); |
michael@0 | 591 | if (defaultBrowserKey) { |
michael@0 | 592 | migrator = this.getMigrator(defaultBrowserKey); |
michael@0 | 593 | if (migrator) |
michael@0 | 594 | migratorKey = defaultBrowserKey; |
michael@0 | 595 | } |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | if (!migrator) { |
michael@0 | 599 | // If there's no migrator set so far, ensure that there is at least one |
michael@0 | 600 | // migrator available before opening the wizard. |
michael@0 | 601 | try { |
michael@0 | 602 | this.migrators.next(); |
michael@0 | 603 | } |
michael@0 | 604 | catch(ex) { |
michael@0 | 605 | this.finishMigration(); |
michael@0 | 606 | if (!(ex instanceof StopIteration)) |
michael@0 | 607 | throw ex; |
michael@0 | 608 | return; |
michael@0 | 609 | } |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); |
michael@0 | 613 | let keyCSTR = Cc["@mozilla.org/supports-cstring;1"]. |
michael@0 | 614 | createInstance(Ci.nsISupportsCString); |
michael@0 | 615 | keyCSTR.data = migratorKey; |
michael@0 | 616 | let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"]. |
michael@0 | 617 | createInstance(Ci.nsISupportsPRBool); |
michael@0 | 618 | skipImportSourcePageBool.data = skipSourcePage; |
michael@0 | 619 | params.appendElement(keyCSTR, false); |
michael@0 | 620 | params.appendElement(migrator, false); |
michael@0 | 621 | params.appendElement(aProfileStartup, false); |
michael@0 | 622 | params.appendElement(skipImportSourcePageBool, false); |
michael@0 | 623 | |
michael@0 | 624 | this.showMigrationWizard(null, params); |
michael@0 | 625 | }, |
michael@0 | 626 | |
michael@0 | 627 | /** |
michael@0 | 628 | * Cleans up references to migrators and nsIProfileInstance instances. |
michael@0 | 629 | */ |
michael@0 | 630 | finishMigration: function MU_finishMigration() { |
michael@0 | 631 | gMigrators = null; |
michael@0 | 632 | gProfileStartup = null; |
michael@0 | 633 | gMigrationBundle = null; |
michael@0 | 634 | } |
michael@0 | 635 | }); |