Bug 737381 - The Firefox Migrator should use the new migration system. r=mak

This commit is contained in:
Asaf Romano 2012-04-14 12:50:44 +03:00
parent 904a38aeb8
commit c2761a5f3b
6 changed files with 163 additions and 469 deletions

View File

@ -215,16 +215,15 @@ var MigrationWizard = {
var dataSources = document.getElementById("dataSources");
while (dataSources.hasChildNodes())
dataSources.removeChild(dataSources.firstChild);
var bundle = document.getElementById("bundle");
var items = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
for (var i = 0; i < 16; ++i) {
var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0;
if (itemID > 0) {
var checkbox = document.createElement("checkbox");
checkbox.id = itemID;
checkbox.setAttribute("label", bundle.getString(itemID + "_" + this._source));
checkbox.setAttribute("label",
MigrationUtils.getLocalizedString(itemID + "_" + this._source));
dataSources.appendChild(checkbox);
if (!this._itemsFlags || this._itemsFlags & itemID)
checkbox.checked = true;
@ -274,13 +273,13 @@ var MigrationWizard = {
return;
}
var bundle = document.getElementById("brandBundle");
var brandBundle = document.getElementById("brandBundle");
// These strings don't exist when not using official branding. If that's
// the case, just skip this page.
try {
var pageTitle = bundle.getString("homePageMigrationPageTitle");
var pageDesc = bundle.getString("homePageMigrationDescription");
var mainStr = bundle.getString("homePageSingleStartMain");
var pageTitle = brandBundle.getString("homePageMigrationPageTitle");
var pageDesc = brandBundle.getString("homePageMigrationDescription");
var mainStr = brandBundle.getString("homePageSingleStartMain");
}
catch (e) {
this._wiz.advance();
@ -318,10 +317,9 @@ var MigrationWizard = {
var oldHomePageURL = this._migrator.sourceHomePageURL;
if (oldHomePageURL && source) {
var bundle2 = document.getElementById("bundle");
var appName = bundle2.getString(source);
var oldHomePageLabel = bundle.getFormattedString("homePageImport",
[appName]);
var appName = MigrationUtils.getLocalizedString(source);
var oldHomePageLabel =
brandBundle.getFormattedString("homePageImport", [appName]);
var oldHomePage = document.getElementById("oldHomePage");
oldHomePage.setAttribute("label", oldHomePageLabel);
oldHomePage.setAttribute("value", oldHomePageURL);
@ -372,8 +370,7 @@ var MigrationWizard = {
var items = document.getElementById(aID);
while (items.hasChildNodes())
items.removeChild(items.firstChild);
var bundle = document.getElementById("bundle");
var brandBundle = document.getElementById("brandBundle");
var itemID;
for (var i = 0; i < 16; ++i) {
@ -382,7 +379,8 @@ var MigrationWizard = {
var label = document.createElement("label");
label.id = itemID + "_migrated";
try {
label.setAttribute("value", bundle.getString(itemID + "_" + this._source));
label.setAttribute("value",
MigrationUtils.getLocalizedString(itemID + "_" + this._source));
items.appendChild(label);
}
catch (e) {

View File

@ -51,7 +51,6 @@
<script type="application/javascript" src="chrome://browser/content/migration/migration.js"/>
<stringbundle id="bundle" src="chrome://browser/locale/migration/migration.properties"/>
<stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
<wizardpage id="importSource" pageid="importSource" next="selectProfile"

View File

@ -20,7 +20,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
@ -176,7 +176,7 @@ function GetBookmarksResource(aProfileFolder) {
return null;
return {
type: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate: function(aCallback) {
NetUtil.asyncFetch(bookmarksFile, MigrationUtils.wrapMigrateFunction(
@ -227,7 +227,7 @@ function GetHistoryResource(aProfileFolder) {
return null;
return {
type: Ci.nsIBrowserProfileMigrator.HISTORY,
type: MigrationUtils.resourceTypes.HISTORY,
migrate: function(aCallback) {
let dbConn = Services.storage.openUnsharedDatabase(historyFile);
@ -288,7 +288,7 @@ function GetCookiesResource(aProfileFolder) {
return null;
return {
type: Ci.nsIBrowserProfileMigrator.COOKIES,
type: MigrationUtils.resourceTypes.COOKIES,
migrate: function(aCallback) {
let dbConn = Services.storage.openUnsharedDatabase(cookiesFile);

View File

@ -4,452 +4,91 @@
* version 2.0 (the "License"). You can obtain a copy of the License at
* http://mozilla.org/MPL/2.0/. */
"use strict";
/*
* Migrates from a Firefox profile in a lossy manner in order to clean up a user's profile. Data
* is only migrated where the benefits outweigh the potential problems caused by importing
* undesired/invalid configurations from the source profile.
* Migrates from a Firefox profile in a lossy manner in order to clean up a
* user's profile. Data is only migrated where the benefits outweigh the
* potential problems caused by importing undesired/invalid configurations
* from the source profile.
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const MIGRATOR = Ci.nsIBrowserProfileMigrator;
const LOCAL_FILE_CID = "@mozilla.org/file/local;1";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
function FirefoxProfileMigrator()
{
// profD is not available when the migrator is run during startup so use ProfDS.
this._paths.currentProfile = FileUtils.getDir("ProfDS", []);
function FirefoxProfileMigrator() { }
FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
FirefoxProfileMigrator.prototype.getResources = function() {
// Only allow migrating from the default (selected) profile since this will
// be the only one returned by the toolkit profile service after bug 214675.
let sourceProfile =
Components.classes["@mozilla.org/toolkit/profile-service;1"]
.getService(Components.interfaces.nsIToolkitProfileService)
.selectedProfile;
if (!sourceProfile)
return null;
let sourceProfileDir = sourceProfile.rootDir;
if (!sourceProfileDir || !sourceProfileDir.exists() ||
!sourceProfileDir.isReadable())
return null;
// Being a startup-only migrator, we can rely on
// MigrationUtils.profileStartup being set.
let currentProfileDir = MigrationUtils.profileStartup.directory;
// Surely data cannot be imported from the current profile.
if (sourceProfileDir.equals(currentProfileDir))
return null;
let getFileResource = function(aMigrationType, aFileNames) {
let files = [];
for (let fileName of aFileNames) {
let file = sourceProfileDir.clone();
file.append(fileName);
// File resources are monolithic. We don't make partial copies since
// they are not expected to work alone.
if (!file.exists())
return null;
files.push(file);
}
return {
type: aMigrationType,
migrate: function(aCallback) {
for (let file of files) {
file.copyTo(currentProfileDir, "");
}
aCallback(true);
}
};
};
let types = MigrationUtils.resourceTypes;
let places = getFileResource(types.HISTORY, ["places.sqlite"]);
let cookies = getFileResource(types.COOKIES, ["cookies.sqlite"]);
let passwords = getFileResource(types.PASSWORDS,
["signons.sqlite", "key3.db"]);
let formData = getFileResource(types.FORMDATA, ["formhistory.sqlite"]);
let bookmarksBackups = getFileResource(types.OTHERDATA,
[PlacesUtils.backups.profileRelativeFolderPath]);
return [r for each (r in [places, cookies, passwords, formData,
bookmarksBackups]) if (r)];
}
FirefoxProfileMigrator.prototype = {
_paths: {
bookmarks: null,
cookies: null,
currentProfile: null, // The currently running (destination) profile.
encryptionKey: null,
formData: null,
history: null,
passwords: null,
},
_homepageURL : null,
_replaceBookmarks : false,
_sourceProfile: null,
_profilesCache: null,
/**
* Notify to observers to start migration
*
* @param aType
* notification type such as MIGRATOR.BOOKMARKS
*/
_notifyStart : function Firefox_notifyStart(aType)
{
Services.obs.notifyObservers(null, "Migration:ItemBeforeMigrate", aType);
this._pendingCount++;
},
/**
* Notify observers that a migration error occured with an item
*
* @param aType
* notification type such as MIGRATOR.BOOKMARKS
*/
_notifyError : function Firefox_notifyError(aType)
{
Services.obs.notifyObservers(null, "Migration:ItemError", aType);
},
/**
* Notify to observers to finish migration for item
* If all items are finished, it sends migration end notification.
*
* @param aType
* notification type such as MIGRATOR.BOOKMARKS
*/
_notifyCompleted : function Firefox_notifyIfCompleted(aType)
{
Services.obs.notifyObservers(null, "Migration:ItemAfterMigrate", aType);
if (--this._pendingCount == 0) {
// All items are migrated, so we have to send end notification.
Services.obs.notifyObservers(null, "Migration:Ended", null);
}
},
/**
* The directory used for bookmark backups
* @return directory of the bookmark backups
*/
get _bookmarks_backup_folder()
{
let bookmarksBackupRelativePath = PlacesUtils.backups.profileRelativeFolderPath;
let bookmarksBackupDir = this._sourceProfile.clone().QueryInterface(Ci.nsILocalFile);
bookmarksBackupDir.appendRelativePath(bookmarksBackupRelativePath);
return bookmarksBackupDir;
},
/**
* Migrating bookmark items
*/
_migrateBookmarks : function Firefox_migrateBookmarks()
{
this._notifyStart(MIGRATOR.BOOKMARKS);
try {
let srcBackupsDir = this._bookmarks_backup_folder;
let backupFolder = this._paths.currentProfile.clone();
backupFolder.append(srcBackupsDir.leafName);
if (!backupFolder.exists())
srcBackupsDir.copyTo(this._paths.currentProfile, null);
} catch (e) {
Cu.reportError(e);
// Don't notify about backup migration errors since actual bookmarks are
// migrated with history.
} finally {
this._notifyCompleted(MIGRATOR.BOOKMARKS);
}
},
/**
* Migrating history
*/
_migrateHistory : function Firefox_migrateHistory()
{
this._notifyStart(MIGRATOR.HISTORY);
try {
// access sqlite3 database of history
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
file.initWithPath(this._paths.history);
file.copyTo(this._paths.currentProfile, null);
} catch (e) {
Cu.reportError(e);
this._notifyError(MIGRATOR.HISTORY);
} finally {
this._notifyCompleted(MIGRATOR.HISTORY);
}
},
/**
* Migrating cookies
*
* @note Cookie permissions are not migrated since they may be inadvertently set leading to
* website login problems.
*/
_migrateCookies : function Firefox_migrateCookies()
{
this._notifyStart(MIGRATOR.COOKIES);
try {
// Access sqlite3 database of cookies
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
file.initWithPath(this._paths.cookies);
file.copyTo(this._paths.currentProfile, null);
} catch (e) {
Cu.reportError(e);
this._notifyError(MIGRATOR.COOKIES);
} finally {
this._notifyCompleted(MIGRATOR.COOKIES);
}
},
/**
* Migrating passwords
*/
_migratePasswords : function Firefox_migratePasswords()
{
this._notifyStart(MIGRATOR.PASSWORDS);
try {
// Access sqlite3 database of passwords
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
file.initWithPath(this._paths.passwords);
file.copyTo(this._paths.currentProfile, null);
let encryptionKey = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
encryptionKey.initWithPath(this._paths.encryptionKey);
encryptionKey.copyTo(this._paths.currentProfile, null);
} catch (e) {
Cu.reportError(e);
this._notifyError(MIGRATOR.PASSWORDS);
} finally {
this._notifyCompleted(MIGRATOR.PASSWORDS);
}
},
/**
* Migrating form history (satchel)
*/
_migrateFormData : function Firefox_migrateFormData()
{
this._notifyStart(MIGRATOR.FORMDATA);
try {
// Access sqlite3 database of form history.
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
file.initWithPath(this._paths.formData);
file.copyTo(this._paths.currentProfile, null);
} catch (e) {
Cu.reportError(e);
this._notifyError(MIGRATOR.FORMDATA);
} finally {
this._notifyCompleted(MIGRATOR.FORMDATA);
}
},
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
get: function() true
});
/**
* nsIBrowserProfileMigrator interface implementation
*/
/**
* Let's migrate all items
*
* @param aItems
* list of data items to migrate.
* @param aStartup
* non-null if called during startup.
* @param aProfile
* profile directory path to migrate from
*/
migrate : function Firefox_migrate(aItems, aStartup, aProfile)
{
if (aStartup) {
this._replaceBookmarks = true;
}
// Ensure that aProfile is not the current profile.
if (this._paths.currentProfile.path === this._sourceProfile.path) {
throw new Exception("Source and destination profiles are the same");
return;
}
Services.obs.notifyObservers(null, "Migration:Started", null);
// Reset pending count. If this count becomes 0, "Migration:Ended"
// notification is sent
this._pendingCount = 1;
if (aItems & MIGRATOR.HISTORY)
this._migrateHistory();
if (aItems & MIGRATOR.COOKIES)
this._migrateCookies();
if (aItems & MIGRATOR.BOOKMARKS)
this._migrateBookmarks();
if (aItems & MIGRATOR.PASSWORDS)
this._migratePasswords();
// The password manager encryption key must be copied before startup.
if (aStartup) {
aStartup.doStartup();
}
if (aItems & MIGRATOR.FORMDATA)
this._migrateFormData();
if (--this._pendingCount == 0) {
// When async imports are immediately completed unfortunately,
// this will be called.
// Usually, this notification is sent by _notifyCompleted()
Services.obs.notifyObservers(null, "Migration:Ended", null);
}
},
/**
* return supported migration types
*
* @param aProfile
* the profile path to migrate from
* @param aDoingStartup
* non-null if called during startup.
* @return supported migration types
*
* @todo Bug 715315 - make sure source databases are not in-use
*/
getMigrateData: function Firefox_getMigrateData(aProfile, aDoingStartup)
{
this._sourceProfile = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
this._sourceProfile.initWithPath(aProfile);
let result = 0;
// Ensure that aProfile is not the current profile.
if (this._paths.currentProfile.path === this._sourceProfile.path)
return result;
if (!this._sourceProfile.exists() || !this._sourceProfile.isReadable()) {
Cu.reportError("source profile directory doesn't exist or is not readable");
return result;
}
// Migration initiated from the UI is not supported.
if (!aDoingStartup)
return result;
// Bookmark backups in JSON format
try {
let file = this._bookmarks_backup_folder;
if (file.exists()) {
this._paths.bookmarks = file.path;
result += MIGRATOR.BOOKMARKS;
}
} catch (e) {
Cu.reportError(e);
}
// Bookmarks, history and favicons
try {
let file = this._sourceProfile.clone();
file.append("places.sqlite");
if (file.exists()) {
this._paths.history = file.path;
result += MIGRATOR.HISTORY;
result |= MIGRATOR.BOOKMARKS;
}
} catch (e) {
Cu.reportError(e);
}
// Cookies
try {
let file = this._sourceProfile.clone();
file.append("cookies.sqlite");
if (file.exists()) {
this._paths.cookies = file.path;
result += MIGRATOR.COOKIES;
}
} catch (e) {
Cu.reportError(e);
}
// Passwords & encryption key
try {
let passwords = this._sourceProfile.clone();
passwords.append("signons.sqlite");
let encryptionKey = this._sourceProfile.clone();
encryptionKey.append("key3.db");
if (passwords.exists() && encryptionKey.exists()) {
this._paths.passwords = passwords.path;
this._paths.encryptionKey = encryptionKey.path;
result += MIGRATOR.PASSWORDS;
}
} catch (e) {
Cu.reportError(e);
}
// Form data
try {
let file = this._sourceProfile.clone();
file.append("formhistory.sqlite");
if (file.exists()) {
this._paths.formData = file.path;
result += MIGRATOR.FORMDATA;
}
} catch (e) {
Cu.reportError(e);
}
return result;
},
/**
* Whether we support migration of Firefox
*
* @return true if supported
*/
get sourceExists()
{
let userData = Services.dirsvc.get("DefProfRt", Ci.nsIFile).path;
let result = 0;
try {
let userDataDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
userDataDir.initWithPath(userData);
if (!userDataDir.exists() || !userDataDir.isReadable())
return false;
let profiles = this.sourceProfiles;
if (profiles.length < 1)
return false;
// Check that we can get data from at least one profile since profile selection has not
// happened yet.
for (let i = 0; i < profiles.length; i++) {
result = this.getMigrateData(profiles.queryElementAt(i, Ci.nsISupportsString), true);
if (result)
break;
}
} catch (e) {
Cu.reportError(e);
}
return result > 0;
},
get sourceHasMultipleProfiles()
{
return this.sourceProfiles.length > 1;
},
get sourceProfiles()
{
try
{
if (!this._profilesCache)
{
this._profilesCache = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
.getService(Ci.nsIToolkitProfileService);
// Only allow migrating from the default (selected) profile since this will be the only one
// returned by the toolkit profile service after bug 214675 has landed.
var profile = profileService.selectedProfile;
if (profile.rootDir.path === this._paths.currentProfile.path)
return null;
let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
str.data = profile.rootDir.path;
this._profilesCache.appendElement(str, false);
}
} catch (e) {
Cu.reportError("Error detecting Firefox profiles: " + e);
}
return this._profilesCache;
},
/**
* Return home page URL
*
* @return home page URL
*
* @todo Bug 715348 will migrate preferences such as the homepage
*/
get sourceHomePageURL()
{
try {
if (this._homepageURL)
return this._homepageURL;
} catch (e) {
Cu.reportError(e);
}
return "";
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIBrowserProfileMigrator
]),
classDescription: "Firefox Profile Migrator",
contractID: "@mozilla.org/profile/migrator;1?app=browser&type=firefox",
classID: Components.ID("{91185366-ba97-4438-acba-48deaca63386}")
};
FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
FirefoxProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
FirefoxProfileMigrator.prototype.classID = Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
const NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);

View File

@ -20,6 +20,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
let gMigrators = null;
let gProfileStartup = null;
let gMigrationBundle = null;
function getMigrationBundle() {
if (!gMigrationBundle) {
gMigrationBundle = Services.strings.createBundle(
"chrome://browser/locale/migration/migration.properties");
}
return gMigrationBundle;
}
/**
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
@ -224,9 +233,22 @@ let MigratorPrototype = {
// For a single-profile source, check if any data is available.
// For multiple-profiles source, make sure that at least one
// profile is available.
let profiles = this.sourceProfiles;
return (!profiles && this.getResources("")) ||
(profiles && profiles.length > 0);
let exists = false;
try {
let profiles = this.sourceProfiles;
if (!profiles) {
let resources = this._getMaybeCachedResources("");
if (resources && resources.length > 0)
exists = true;
}
else {
exists = profiles.length > 0;
}
}
catch(ex) {
Cu.reportError(ex);
}
return exists;
},
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
@ -243,6 +265,16 @@ let MigratorPrototype = {
};
let MigrationUtils = Object.freeze({
resourceTypes: {
SETTINGS: Ci.nsIBrowserProfileMigrator.SETTINGS,
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
FORMDATA: Ci.nsIBrowserProfileMigrator.FORMDATA,
PASSWORDS: Ci.nsIBrowserProfileMigrator.PASSWORDS,
BOOKMARKS: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
OTHERDATA: Ci.nsIBrowserProfileMigrator.OTHERDATA
},
/**
* Helper for implementing simple asynchronous cases of migration resources'
* |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
@ -295,6 +327,34 @@ let MigrationUtils = Object.freeze({
}
},
/**
* Gets a string from the migration bundle. Shorthand for
* nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for
* nsIStringBundle.formatStringFromName if it is.
*
* This method also takes care of "bumped" keys (See bug 737381 comment 8 for
* details).
*
* @param aKey
* The key of the string to retrieve.
* @param aReplacemts
* [optioanl] Array of replacements to run on the retrieved string.
* @return the retrieved string.
*
* @see nsIStringBundle
*/
getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) {
const OVERRIDES = {
"4_firefox": "4_firefox_history_and_bookmarks"
};
aKey = OVERRIDES[aKey] || aKey;
if (aReplacements === undefined)
return getMigrationBundle().GetStringFromName(aKey);
return getMigrationBundle().formatStringFromName(
aKey, aReplacements, aReplacements.length);
},
/**
* Helper for creating a folder for imported bookmarks from a particular
* migration source. The folder is created at the end of the given folder.
@ -311,13 +371,10 @@ let MigrationUtils = Object.freeze({
*/
createImportedBookmarksFolder:
function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) {
let bundle = Services.strings.createBundle(
"chrome://browser/locale/migration/migration.properties");
let sourceName = bundle.GetStringFromName("sourceName" + aSourceNameStr);
let folderName = bundle.formatStringFromName("importedBookmarksFolder",
[sourceName], 1);
let source = this.getLocalizedString("sourceName" + aSourceNameStr);
let label = this.getLocalizedString("importedBookmarksFolder", [source]);
return PlacesUtils.bookmarks.createFolder(
aParentId, folderName, PlacesUtils.bookmarks.DEFAULT_INDEX);
aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX);
},
get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(),
@ -455,5 +512,6 @@ let MigrationUtils = Object.freeze({
finishMigration: function MU_finishMigration() {
gMigrators = null;
gProfileStartup = null;
gMigrationBundle = null;
}
});

View File

@ -26,7 +26,7 @@ importedSafariBookmarks=From Safari
4_ie=Browsing History
4_safari=Browsing History
4_chrome=Browsing History
4_firefox=Browsing History
4_firefox_history_and_bookmarks=Browsing History and Bookmarks
8_ie=Saved Form History
8_safari=Saved Form History
@ -46,4 +46,4 @@ importedSafariBookmarks=From Safari
64_ie=Other Data
64_safari=Other Data
64_chrome=Other Data
64_firefox=Bookmarks Backups