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