mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 20:01:50 +00:00
Bug 718608 - Migration code shrink and cleanup: unified code for notifications and error handling, prepare for deCOM. r=mak. sr=gavin on the interface change. browser/components/build part was done by mak (r=bsmedberg.
This commit is contained in:
parent
c6838a7ad3
commit
bdcf891941
@ -30,7 +30,6 @@ endif
|
||||
LOCAL_INCLUDES = \
|
||||
-I$(srcdir)/../shell/src \
|
||||
-I$(srcdir)/../feeds/src \
|
||||
-I$(srcdir)/../places/src \
|
||||
-I$(srcdir)/../privatebrowsing/src \
|
||||
-I$(srcdir)/../about \
|
||||
-I$(srcdir)/../dirprovider \
|
||||
@ -53,10 +52,14 @@ endif
|
||||
|
||||
EXTRA_DSO_LDOPTS += \
|
||||
$(call EXPAND_LIBNAME_PATH,unicharutil_external_s,$(LIBXUL_DIST)/lib) \
|
||||
$(LIBXUL_DIST)/lib/$(LIB_PREFIX)xpcomglue_s.$(LIB_SUFFIX) \
|
||||
$(XPCOM_GLUE_LDOPTS) \
|
||||
$(MOZ_COMPONENT_LIBS) \
|
||||
$(NULL)
|
||||
|
||||
ifdef JS_SHARED_LIBRARY
|
||||
EXTRA_DSO_LDOPTS += $(MOZ_JS_LIBS)
|
||||
endif
|
||||
|
||||
LOCAL_INCLUDES += -I$(srcdir)/../migration/src
|
||||
SHARED_LIBRARY_LIBS += ../migration/src/$(LIB_PREFIX)migration_s.$(LIB_SUFFIX)
|
||||
|
||||
|
@ -1,44 +1,15 @@
|
||||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is The Browser Profile Migrator.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Ben Goodger.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2004
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Ben Goodger <ben@bengoodger.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const kIMig = Components.interfaces.nsIBrowserProfileMigrator;
|
||||
const kIPStartup = Components.interfaces.nsIProfileStartup;
|
||||
const kProfileMigratorContractIDPrefix = "@mozilla.org/profile/migrator;1?app=browser&type=";
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const kIMig = Ci.nsIBrowserProfileMigrator;
|
||||
const kIPStartup = Ci.nsIProfileStartup;
|
||||
|
||||
Cu.import("resource://gre/modules/MigrationUtils.jsm");
|
||||
|
||||
var MigrationWizard = {
|
||||
_source: "", // Source Profile Migrator ContractID suffix
|
||||
@ -87,6 +58,7 @@ var MigrationWizard = {
|
||||
os.removeObserver(this, "Migration:ItemAfterMigrate");
|
||||
os.removeObserver(this, "Migration:ItemError");
|
||||
os.removeObserver(this, "Migration:Ended");
|
||||
MigrationUtils.finishMigration();
|
||||
},
|
||||
|
||||
// 1 - Import Source
|
||||
@ -118,21 +90,13 @@ var MigrationWizard = {
|
||||
// Figure out what source apps are are available to import from:
|
||||
var group = document.getElementById("importSourceGroup");
|
||||
for (var i = 0; i < group.childNodes.length; ++i) {
|
||||
var suffix = group.childNodes[i].id;
|
||||
if (suffix != "nothing" && suffix != "fromfile") {
|
||||
var contractID = kProfileMigratorContractIDPrefix + suffix;
|
||||
try {
|
||||
var migrator = Components.classes[contractID].createInstance(kIMig);
|
||||
}
|
||||
catch (e) {
|
||||
dump("*** invalid contractID =" + contractID + "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (migrator.sourceExists) {
|
||||
var migratorKey = group.childNodes[i].id;
|
||||
if (migratorKey != "nothing" && migratorKey != "fromfile") {
|
||||
var migrator = MigrationUtils.getMigrator(migratorKey);
|
||||
if (migrator) {
|
||||
// Save this as the first selectable item, if we don't already have
|
||||
// one, or if it is the migrator that was passed to us.
|
||||
if (!selectedMigrator || this._source == suffix)
|
||||
if (!selectedMigrator || this._source == migratorKey)
|
||||
selectedMigrator = group.childNodes[i];
|
||||
} else {
|
||||
// Hide this option
|
||||
@ -173,8 +137,7 @@ var MigrationWizard = {
|
||||
|
||||
if (!this._migrator || (newSource != this._source)) {
|
||||
// Create the migrator for the selected source.
|
||||
var contractID = kProfileMigratorContractIDPrefix + newSource;
|
||||
this._migrator = Components.classes[contractID].createInstance(kIMig);
|
||||
this._migrator = MigrationUtils.getMigrator(newSource);
|
||||
|
||||
this._itemsFlags = kIMig.ALL;
|
||||
this._selectedProfile = null;
|
||||
@ -182,8 +145,10 @@ var MigrationWizard = {
|
||||
this._source = newSource;
|
||||
|
||||
// check for more than one source profile
|
||||
if (this._migrator.sourceHasMultipleProfiles)
|
||||
var sourceProfiles = this._migrator.sourceProfiles;
|
||||
if (sourceProfiles && sourceProfiles.length > 1) {
|
||||
this._wiz.currentPage.next = "selectProfile";
|
||||
}
|
||||
else {
|
||||
if (this._autoMigrate)
|
||||
this._wiz.currentPage.next = "homePageImport";
|
||||
@ -192,11 +157,8 @@ var MigrationWizard = {
|
||||
else
|
||||
this._wiz.currentPage.next = "importItems";
|
||||
|
||||
var sourceProfiles = this._migrator.sourceProfiles;
|
||||
if (sourceProfiles && sourceProfiles.length == 1) {
|
||||
var profileName = sourceProfiles.queryElementAt(0, Ci.nsISupportsString);
|
||||
this._selectedProfile = profileName.data;
|
||||
}
|
||||
if (sourceProfiles && sourceProfiles.length == 1)
|
||||
this._selectedProfile = sourceProfiles[0];
|
||||
else
|
||||
this._selectedProfile = "";
|
||||
}
|
||||
@ -220,9 +182,8 @@ var MigrationWizard = {
|
||||
var sourceProfiles = this._migrator.sourceProfiles;
|
||||
for (var i = 0; i < sourceProfiles.length; ++i) {
|
||||
var item = document.createElement("radio");
|
||||
var str = sourceProfiles.queryElementAt(i, Ci.nsISupportsString);
|
||||
item.id = str.data;
|
||||
item.setAttribute("label", str.data);
|
||||
item.id = sourceProfiles[i];
|
||||
item.setAttribute("label", sourceProfiles[i]);
|
||||
profiles.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
||||
interface nsIArray;
|
||||
interface nsIProfileStartup;
|
||||
|
||||
[scriptable, uuid(5f445759-86a8-4dd3-ab84-4fc5341d9d9d)]
|
||||
[scriptable, uuid(44993E0E-74E8-4BEC-9D66-AD8156E0A274)]
|
||||
interface nsIBrowserProfileMigrator : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -80,17 +80,12 @@ interface nsIBrowserProfileMigrator : nsISupports
|
||||
*/
|
||||
readonly attribute boolean sourceExists;
|
||||
|
||||
/**
|
||||
* Whether or not the import source implementing this interface
|
||||
* has multiple user profiles configured.
|
||||
*/
|
||||
readonly attribute boolean sourceHasMultipleProfiles;
|
||||
|
||||
/**
|
||||
* An enumeration of available profiles. If the import source does
|
||||
* not support profiles, this attribute is null.
|
||||
*/
|
||||
readonly attribute nsIArray sourceProfiles;
|
||||
readonly attribute jsval sourceProfiles;
|
||||
|
||||
/**
|
||||
* The import source homepage. Returns null if not present/available
|
||||
|
@ -1,69 +1,29 @@
|
||||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* vim: sw=2 ts=2 sts=2 et
|
||||
* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License
|
||||
* at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
* the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* The Original Code is the Browser Profile Migrator.
|
||||
*
|
||||
* The Initial Developer of the Original Code is the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Makoto Kato <m_kato@ga2.so-net.ne.jp> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
* This Source Code is subject to the terms of the Mozilla Public License
|
||||
* version 2.0 (the "License"). You can obtain a copy of the License at
|
||||
* http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
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";
|
||||
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
|
||||
|
||||
const BUNDLE_MIGRATION = "chrome://browser/locale/migration/migration.properties";
|
||||
|
||||
const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
|
||||
const S100NS_PER_MS = 10;
|
||||
|
||||
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");
|
||||
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");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "bookmarksSubfolderTitle", function () {
|
||||
// get "import from google chrome" string for folder
|
||||
let strbundle =
|
||||
Services.strings.createBundle(BUNDLE_MIGRATION);
|
||||
let sourceNameChrome = strbundle.GetStringFromName("sourceNameChrome");
|
||||
return strbundle.formatStringFromName("importedBookmarksFolder",
|
||||
[sourceNameChrome],
|
||||
1);
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
/**
|
||||
* Convert Chrome time format to Date object
|
||||
@ -112,223 +72,230 @@ function insertBookmarkItems(aFolderId, aItems)
|
||||
}
|
||||
}
|
||||
|
||||
function ChromeProfileMigrator()
|
||||
{
|
||||
|
||||
function ChromeProfileMigrator() {
|
||||
let chromeUserDataFolder = FileUtils.getDir(
|
||||
#ifdef XP_WIN
|
||||
"LocalAppData", ["Google", "Chrome", "User Data"]
|
||||
#elifdef XP_MACOSX
|
||||
"ULibDir", ["Application Support", "Google", "Chrome"]
|
||||
#else
|
||||
"Home", [".config", "google-chrome"]
|
||||
#endif
|
||||
, false);
|
||||
this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
|
||||
chromeUserDataFolder : null;
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype = {
|
||||
_paths: {
|
||||
bookmarks : null,
|
||||
cookies : null,
|
||||
history : null,
|
||||
prefs : null,
|
||||
userData : null,
|
||||
},
|
||||
ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
_homepageURL : null,
|
||||
_replaceBookmarks : false,
|
||||
_sourceProfile: null,
|
||||
_profilesCache: null,
|
||||
|
||||
/**
|
||||
* Notify to observers to start migration
|
||||
*
|
||||
* @param aType
|
||||
* notification type such as MIGRATOR.BOOKMARKS
|
||||
*/
|
||||
_notifyStart : function Chrome_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 Chrome_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 Chrome_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);
|
||||
ChromeProfileMigrator.prototype.getResources =
|
||||
function Chrome_getResources(aProfile) {
|
||||
if (this._chromeUserDataFolder) {
|
||||
let profileFolder = this._chromeUserDataFolder.clone();
|
||||
profileFolder.append(aProfile);
|
||||
if (profileFolder.exists()) {
|
||||
let possibleResources = [GetBookmarksResource(profileFolder),
|
||||
GetHistoryResource(profileFolder),
|
||||
GetCookiesResource(profileFolder)];
|
||||
return [r for each (r in possibleResources) if (r != null)];
|
||||
}
|
||||
}
|
||||
},
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrating bookmark items
|
||||
*/
|
||||
_migrateBookmarks : function Chrome_migrateBookmarks()
|
||||
{
|
||||
this._notifyStart(MIGRATOR.BOOKMARKS);
|
||||
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
|
||||
get: function Chrome_sourceProfiles() {
|
||||
if ("__sourceProfiles" in this)
|
||||
return this.__sourceProfiles;
|
||||
|
||||
if (!this._chromeUserDataFolder)
|
||||
return [];
|
||||
|
||||
let profiles;
|
||||
try {
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
_self : this,
|
||||
runBatched : function (aUserData) {
|
||||
let migrator = this._self;
|
||||
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(migrator._paths.bookmarks);
|
||||
// Local State is a JSON file that contains profile info.
|
||||
let localState = this._chromeUserDataFolder.clone();
|
||||
localState.append("Local State");
|
||||
if (!localState.exists())
|
||||
throw new Error("Chrome's 'Local State' file does not exist.");
|
||||
if (!localState.isReadable())
|
||||
throw new Error("Chrome's 'Local State' file could not be read.");
|
||||
|
||||
NetUtil.asyncFetch(file, function(aInputStream, aResultCode) {
|
||||
if (!Components.isSuccessCode(aResultCode)) {
|
||||
migrator._notifyCompleted(MIGRATOR.BOOKMARKS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse Chrome bookmark file that is JSON format
|
||||
let bookmarkJSON = NetUtil.readInputStreamToString(aInputStream,
|
||||
aInputStream.available(),
|
||||
{ charset : "UTF-8" });
|
||||
let roots = JSON.parse(bookmarkJSON).roots;
|
||||
|
||||
// Importing bookmark bar items
|
||||
if (roots.bookmark_bar.children &&
|
||||
roots.bookmark_bar.children.length > 0) {
|
||||
// Toolbar
|
||||
let parentId = PlacesUtils.toolbarFolderId;
|
||||
if (!migrator._replaceBookmarks) {
|
||||
parentId =
|
||||
PlacesUtils.bookmarks.createFolder(parentId,
|
||||
bookmarksSubfolderTitle,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
}
|
||||
insertBookmarkItems(parentId, roots.bookmark_bar.children);
|
||||
}
|
||||
|
||||
// Importing bookmark menu items
|
||||
if (roots.other.children &&
|
||||
roots.other.children.length > 0) {
|
||||
// Bookmark menu
|
||||
let parentId = PlacesUtils.bookmarksMenuFolderId;
|
||||
if (!migrator._replaceBookmarks) {
|
||||
parentId =
|
||||
PlacesUtils.bookmarks.createFolder(parentId,
|
||||
bookmarksSubfolderTitle,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
}
|
||||
insertBookmarkItems(parentId, roots.other.children);
|
||||
}
|
||||
|
||||
migrator._notifyCompleted(MIGRATOR.BOOKMARKS);
|
||||
});
|
||||
}
|
||||
}, null);
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(localState, -1, 0, 0);
|
||||
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" });
|
||||
let info_cache = JSON.parse(inputStream).profile.info_cache;
|
||||
if (info_cache)
|
||||
profiles = Object.keys(info_cache);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
this._notifyError(MIGRATOR.BOOKMARKS);
|
||||
this._notifyCompleted(MIGRATOR.BOOKMARKS);
|
||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
||||
let defaultProfileFolder = this._chromeUserDataFolder.clone();
|
||||
defaultProfileFolder.append("Default");
|
||||
if (defaultProfileFolder.exists())
|
||||
profiles = ["Default"];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Migrating history
|
||||
*/
|
||||
_migrateHistory : function Chrome_migrateHistory()
|
||||
{
|
||||
this._notifyStart(MIGRATOR.HISTORY);
|
||||
// Only list profiles from which any data can be imported
|
||||
return this.__sourceProfiles = profiles.filter(function(profileName) {
|
||||
let resources = this.getResources(profileName);
|
||||
return resources && resources.length > 0;
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
PlacesUtils.history.runInBatchMode({
|
||||
_self : this,
|
||||
runBatched : function (aUserData) {
|
||||
// access sqlite3 database of Chrome's history
|
||||
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(this._self._paths.history);
|
||||
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
|
||||
get: function Chrome_sourceHomePageURL() {
|
||||
let prefsFile = this._chromeUserDataFolder.clone();
|
||||
prefsFile.append("Preferences");
|
||||
if (prefsFile.exists()) {
|
||||
// XXX reading and parsing JSON is synchronous.
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(file, -1, 0, 0);
|
||||
try {
|
||||
return JSON.parse(
|
||||
NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" })
|
||||
).homepage;
|
||||
}
|
||||
catch(e) {
|
||||
Cu.reportError("Error parsing Chrome's preferences file: " + e);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
let dbConn = Services.storage.openUnsharedDatabase(file);
|
||||
let stmt = dbConn.createAsyncStatement(
|
||||
"SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
|
||||
function GetBookmarksResource(aProfileFolder) {
|
||||
let bookmarksFile = aProfileFolder.clone();
|
||||
bookmarksFile.append("Bookmarks");
|
||||
if (!bookmarksFile.exists())
|
||||
return null;
|
||||
|
||||
stmt.executeAsync({
|
||||
_asyncHistory : Cc["@mozilla.org/browser/history;1"]
|
||||
.getService(Ci.mozIAsyncHistory),
|
||||
_db : dbConn,
|
||||
_self : this._self,
|
||||
handleResult : function(aResults) {
|
||||
let places = [];
|
||||
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
||||
try {
|
||||
// if having typed_count, we changes transition type to typed.
|
||||
let transType = PlacesUtils.history.TRANSITION_LINK;
|
||||
if (row.getResultByName("typed_count") > 0)
|
||||
transType = PlacesUtils.history.TRANSITION_TYPED;
|
||||
return {
|
||||
type: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
|
||||
|
||||
places.push({
|
||||
uri: NetUtil.newURI(row.getResultByName("url")),
|
||||
title: row.getResultByName("title"),
|
||||
visits: [{
|
||||
transitionType: transType,
|
||||
visitDate: chromeTimeToDate(
|
||||
row.getResultByName(
|
||||
"last_visit_time")) * 1000,
|
||||
}],
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
migrate: function(aCallback) {
|
||||
NetUtil.asyncFetch(bookmarksFile, MigrationUtils.wrapMigrateFunction(
|
||||
function(aInputStream, aResultCode) {
|
||||
if (!Components.isSuccessCode(aResultCode))
|
||||
throw new Error("Could not read Bookmarks file");
|
||||
|
||||
// Parse Chrome bookmark file that is JSON format
|
||||
let bookmarkJSON = NetUtil.readInputStreamToString(
|
||||
aInputStream, aInputStream.available(), { charset : "UTF-8" });
|
||||
let roots = JSON.parse(bookmarkJSON).roots;
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
runBatched: function() {
|
||||
// Importing bookmark bar items
|
||||
if (roots.bookmark_bar.children &&
|
||||
roots.bookmark_bar.children.length > 0) {
|
||||
// Toolbar
|
||||
let parentId = PlacesUtils.toolbarFolderId;
|
||||
if (!MigrationUtils.isStartupMigration) {
|
||||
parentId = MigrationUtils.createImportedBookmarksFolder(
|
||||
"Chrome", parentId);
|
||||
}
|
||||
insertBookmarkItems(parentId, roots.bookmark_bar.children);
|
||||
}
|
||||
|
||||
try {
|
||||
this._asyncHistory.updatePlaces(places);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
// Importing bookmark menu items
|
||||
if (roots.other.children &&
|
||||
roots.other.children.length > 0) {
|
||||
// Bookmark menu
|
||||
let parentId = PlacesUtils.bookmarksMenuFolderId;
|
||||
if (!MigrationUtils.isStartupMigration) {
|
||||
parentId = MigrationUtils.createImportedBookmarksFolder(
|
||||
"Chrome", parentId);
|
||||
}
|
||||
insertBookmarkItems(parentId, roots.other.children);
|
||||
}
|
||||
},
|
||||
|
||||
handleError : function(aError) {
|
||||
Cu.reportError("Async statement execution returned with '" +
|
||||
aError.result + "', '" + aError.message + "'");
|
||||
},
|
||||
|
||||
handleCompletion : function(aReason) {
|
||||
this._db.asyncClose();
|
||||
this._self._notifyCompleted(MIGRATOR.HISTORY);
|
||||
}
|
||||
});
|
||||
stmt.finalize();
|
||||
}
|
||||
}, null);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
this._notifyError(MIGRATOR.HISTORY);
|
||||
this._notifyCompleted(MIGRATOR.HISTORY);
|
||||
}, null);
|
||||
}, aCallback));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrating cookies
|
||||
*/
|
||||
_migrateCookies : function Chrome_migrateCookies()
|
||||
{
|
||||
this._notifyStart(MIGRATOR.COOKIES);
|
||||
function GetHistoryResource(aProfileFolder) {
|
||||
let historyFile = aProfileFolder.clone();
|
||||
historyFile.append("History");
|
||||
if (!historyFile.exists())
|
||||
return null;
|
||||
|
||||
try {
|
||||
// Access sqlite3 database of Chrome's cookie
|
||||
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(this._paths.cookies);
|
||||
return {
|
||||
type: Ci.nsIBrowserProfileMigrator.HISTORY,
|
||||
|
||||
let dbConn = Services.storage.openUnsharedDatabase(file);
|
||||
migrate: function(aCallback) {
|
||||
let dbConn = Services.storage.openUnsharedDatabase(historyFile);
|
||||
let stmt = dbConn.createAsyncStatement(
|
||||
"SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
|
||||
|
||||
stmt.executeAsync({
|
||||
handleResult : function(aResults) {
|
||||
let places = [];
|
||||
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
||||
try {
|
||||
// if having typed_count, we changes transition type to typed.
|
||||
let transType = PlacesUtils.history.TRANSITION_LINK;
|
||||
if (row.getResultByName("typed_count") > 0)
|
||||
transType = PlacesUtils.history.TRANSITION_TYPED;
|
||||
|
||||
places.push({
|
||||
uri: NetUtil.newURI(row.getResultByName("url")),
|
||||
title: row.getResultByName("title"),
|
||||
visits: [{
|
||||
transitionType: transType,
|
||||
visitDate: chromeTimeToDate(
|
||||
row.getResultByName(
|
||||
"last_visit_time")) * 1000,
|
||||
}],
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
PlacesUtils.asyncHistory.updatePlaces(places);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
handleError : function(aError) {
|
||||
Cu.reportError("Async statement execution returned with '" +
|
||||
aError.result + "', '" + aError.message + "'");
|
||||
},
|
||||
|
||||
handleCompletion : function(aReason) {
|
||||
dbConn.asyncClose();
|
||||
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
|
||||
}
|
||||
});
|
||||
stmt.finalize();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function GetCookiesResource(aProfileFolder) {
|
||||
let cookiesFile = aProfileFolder.clone();
|
||||
cookiesFile.append("Cookies");
|
||||
if (!cookiesFile.exists())
|
||||
return null;
|
||||
|
||||
return {
|
||||
type: Ci.nsIBrowserProfileMigrator.COOKIES,
|
||||
|
||||
migrate: function(aCallback) {
|
||||
let dbConn = Services.storage.openUnsharedDatabase(cookiesFile);
|
||||
let stmt = dbConn.createAsyncStatement(
|
||||
"SELECT host_key, path, name, value, secure, httponly, expires_utc FROM cookies");
|
||||
|
||||
stmt.executeAsync({
|
||||
_db : dbConn,
|
||||
_self : this,
|
||||
handleResult : function(aResults) {
|
||||
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
||||
let host_key = row.getResultByName("host_key");
|
||||
@ -360,246 +327,17 @@ ChromeProfileMigrator.prototype = {
|
||||
},
|
||||
|
||||
handleCompletion : function(aReason) {
|
||||
this._db.asyncClose();
|
||||
this._self._notifyCompleted(MIGRATOR.COOKIES);
|
||||
dbConn.asyncClose();
|
||||
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
|
||||
},
|
||||
});
|
||||
stmt.finalize();
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
this._notifyError(MIGRATOR.COOKIES);
|
||||
this._notifyCompleted(MIGRATOR.COOKIES);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 name to migrate
|
||||
*/
|
||||
migrate : function Chrome_migrate(aItems, aStartup, aProfile)
|
||||
{
|
||||
if (aStartup) {
|
||||
aStartup.doStartup();
|
||||
this._replaceBookmarks = true;
|
||||
}
|
||||
|
||||
this._sourceProfile = aProfile;
|
||||
|
||||
Services.obs.notifyObservers(null, "Migration:Started", null);
|
||||
|
||||
// Reset panding 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 (--this._pendingCount == 0) {
|
||||
// When async imports are immeditelly 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
|
||||
* directory name of the profile
|
||||
* @param aDoingStartup
|
||||
* non-null if called during startup.
|
||||
* @return supported migration types
|
||||
*/
|
||||
getMigrateData: function Chrome_getMigrateData(aProfile, aDoingStartup)
|
||||
{
|
||||
this._sourceProfile = aProfile;
|
||||
let chromeProfileDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
chromeProfileDir.initWithPath(this._paths.userData + aProfile);
|
||||
|
||||
let result = 0;
|
||||
if (!chromeProfileDir.exists() || !chromeProfileDir.isReadable())
|
||||
return result;
|
||||
|
||||
// bookmark and preference are JSON format
|
||||
|
||||
try {
|
||||
let file = chromeProfileDir.clone();
|
||||
file.append("Bookmarks");
|
||||
if (file.exists()) {
|
||||
this._paths.bookmarks = file.path;
|
||||
result += MIGRATOR.BOOKMARKS;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
if (!this._paths.prefs) {
|
||||
let file = chromeProfileDir.clone();
|
||||
file.append("Preferences");
|
||||
this._paths.prefs = file.path;
|
||||
}
|
||||
|
||||
// history and cookies are SQLite database
|
||||
|
||||
try {
|
||||
let file = chromeProfileDir.clone();
|
||||
file.append("History");
|
||||
if (file.exists()) {
|
||||
this._paths.history = file.path;
|
||||
result += MIGRATOR.HISTORY;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
let file = chromeProfileDir.clone();
|
||||
file.append("Cookies");
|
||||
if (file.exists()) {
|
||||
this._paths.cookies = file.path;
|
||||
result += MIGRATOR.COOKIES;
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether we support migration of Chrome
|
||||
*
|
||||
* @return true if supported
|
||||
*/
|
||||
get sourceExists()
|
||||
{
|
||||
#ifdef XP_WIN
|
||||
this._paths.userData = Services.dirsvc.get("LocalAppData", Ci.nsIFile).path +
|
||||
"\\Google\\Chrome\\User Data\\";
|
||||
#elifdef XP_MACOSX
|
||||
this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path +
|
||||
"/Library/Application Support/Google/Chrome/";
|
||||
#else
|
||||
this._paths.userData = Services.dirsvc.get("Home", Ci.nsIFile).path +
|
||||
"/.config/google-chrome/";
|
||||
#endif
|
||||
let result = 0;
|
||||
try {
|
||||
let userDataDir = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
userDataDir.initWithPath(this._paths.userData);
|
||||
if (!userDataDir.exists() || !userDataDir.isReadable())
|
||||
return false;
|
||||
|
||||
let profiles = this.sourceProfiles;
|
||||
if (profiles.length < 1)
|
||||
return false;
|
||||
|
||||
// check that we can actually get data from the first profile
|
||||
result = this.getMigrateData(profiles.queryElementAt(0, Ci.nsISupportsString), false);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
return result > 0;
|
||||
},
|
||||
|
||||
get sourceHasMultipleProfiles()
|
||||
{
|
||||
return this.sourceProfiles.length > 1;
|
||||
},
|
||||
|
||||
get sourceProfiles()
|
||||
{
|
||||
let profiles = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
try {
|
||||
if (!this._profilesCache) {
|
||||
let localState = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
// Local State is a JSON file that contains profile info.
|
||||
localState.initWithPath(this._paths.userData + "Local State");
|
||||
if (!localState.exists())
|
||||
throw new Components.Exception("Chrome's 'Local State' file does not exist.",
|
||||
Cr.NS_ERROR_FILE_NOT_FOUND);
|
||||
if (!localState.isReadable())
|
||||
throw new Components.Exception("Chrome's 'Local State' file could not be read.",
|
||||
Cr.NS_ERROR_FILE_ACCESS_DENIED);
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(localState, -1, 0, 0);
|
||||
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" });
|
||||
this._profilesCache = JSON.parse(inputStream).profile.info_cache;
|
||||
}
|
||||
|
||||
for (let index in this._profilesCache) {
|
||||
let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
|
||||
str.data = index;
|
||||
profiles.appendElement(str, false);
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||
// if we weren't able to detect any profiles above, fallback to the Default profile.
|
||||
if (profiles.length < 1) {
|
||||
let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
|
||||
// the default profile name is "Default"
|
||||
str.data = "Default";
|
||||
profiles.appendElement(str, false);
|
||||
}
|
||||
}
|
||||
return profiles;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return home page URL
|
||||
*
|
||||
* @return home page URL
|
||||
*/
|
||||
get sourceHomePageURL()
|
||||
{
|
||||
try {
|
||||
if (this._homepageURL)
|
||||
return this._homepageURL;
|
||||
|
||||
if (!this._paths.prefs)
|
||||
this.getMigrateData(this._sourceProfile, false);
|
||||
|
||||
// XXX reading and parsing JSON is synchronous.
|
||||
let file = Cc[LOCAL_FILE_CID].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(this._paths.prefs);
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(file, -1, 0, 0);
|
||||
this._homepageURL = JSON.parse(
|
||||
NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" })).homepage;
|
||||
return this._homepageURL;
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIBrowserProfileMigrator
|
||||
]),
|
||||
|
||||
classDescription: "Chrome Profile Migrator",
|
||||
contractID: "@mozilla.org/profile/migrator;1?app=browser&type=chrome",
|
||||
classID: Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}")
|
||||
};
|
||||
ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
|
||||
ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
|
||||
ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
|
||||
|
||||
const NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);
|
||||
|
@ -59,7 +59,7 @@ ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
|
||||
CPPSRCS += nsSafariProfileMigrator.cpp \
|
||||
nsBrowserProfileMigratorUtils.cpp \
|
||||
$(NULL)
|
||||
endif
|
||||
endif
|
||||
|
||||
EXTRA_PP_COMPONENTS = \
|
||||
ProfileMigrator.js \
|
||||
@ -71,5 +71,9 @@ EXTRA_COMPONENTS = \
|
||||
BrowserProfileMigrators.manifest \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_JS_MODULES = \
|
||||
MigrationUtils.jsm \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
|
453
browser/components/migration/src/MigrationUtils.jsm
Normal file
453
browser/components/migration/src/MigrationUtils.jsm
Normal file
@ -0,0 +1,453 @@
|
||||
/* This Source Code is subject to the terms of the Mozilla Public License
|
||||
* version 2.0 (the "License"). You can obtain a copy of the License at
|
||||
* http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
let EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Dict",
|
||||
"resource://gre/modules/Dict.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
let gMigrators = null;
|
||||
let gProfileStartup = null;
|
||||
|
||||
/**
|
||||
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
|
||||
*
|
||||
* To implement a migrator:
|
||||
* 1. Import this module.
|
||||
* 2. Create the prototype for the migrator, extending MigratorPrototype.
|
||||
* Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
|
||||
* 3. Set classDescription, contractID and classID for your migrator, and set
|
||||
* NSGetFactory appropriately.
|
||||
* 4. If the migrator supports multiple profiles, override the sourceProfiles
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. If the migrator supports reading the home page of the source browser,
|
||||
* override |sourceHomePageURL| getter.
|
||||
* 7. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
let MigratorPrototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
|
||||
*
|
||||
* Returns array of profiles (by names) from which data may be imported.
|
||||
*
|
||||
* Only profiles from which data can be imported should be listed. Otherwise
|
||||
* the behavior of the migration wizard isn't well-defined.
|
||||
*
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*/
|
||||
get sourceProfiles() null,
|
||||
|
||||
/**
|
||||
* MUST BE OVERRIDDEN.
|
||||
*
|
||||
* Returns an array of "migration resources" objects for the given profile,
|
||||
* or for the "default" profile, if the migrator does not support multiple
|
||||
* profiles.
|
||||
*
|
||||
* Each migration resource should provide:
|
||||
* - a |type| getter, retunring any of the migration types (see
|
||||
* nsIBrowserProfileMigrator).
|
||||
*
|
||||
* - a |migrate| method, taking a single argument, aCallback(bool success),
|
||||
* for migrating the data for this resource. It may do its job
|
||||
* synchronously or asynchronously. Either way, it must call
|
||||
* aCallback(bool aSuccess) when it's done. In the case of an exception
|
||||
* thrown from |migrate|, it's taken as if aCallback(false) is called.
|
||||
*
|
||||
* Note: In the case of a simple asynchronous implementation, you may find
|
||||
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
|
||||
*
|
||||
* For each migration type listed in nsIBrowserProfileMigrator, multiple
|
||||
* migration resources may be provided. This practice is useful when the
|
||||
* data for a certain migration type is independently stored in few
|
||||
* locations. For example, the mac version of Safari stores its "reading list"
|
||||
* bookmarks in a separate property list.
|
||||
*
|
||||
* Note that the importation of a particular migration type is reported as
|
||||
* successful if _any_ of its resources succeeded to import (that is, called,
|
||||
* |aCallback(true)|). However, completion-status for a particular migration
|
||||
* type is reported to the UI only once all of its migrators have called
|
||||
* aCallback.
|
||||
*
|
||||
* @note The returned array should only include resources from which data
|
||||
* can be imported. So, for example, before adding a resource for the
|
||||
* BOOKMARKS migration type, you should check if you should check that the
|
||||
* bookmarks file exists.
|
||||
*
|
||||
* @param aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* In the case of multiple-profiles migrator, it is guaranteed that
|
||||
* aProfile is a value returned by the sourceProfiles getter (see
|
||||
* above).
|
||||
*/
|
||||
getResources: function MP_getResources(aProfile) {
|
||||
throw new Error("getResources must be overridden");
|
||||
},
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
|
||||
* that is just the Firefox migrator, see bug 737381). Default: false.
|
||||
*
|
||||
* Startup-only migrators are different in two ways:
|
||||
* - they may only be used during startup.
|
||||
* - the user-profile is half baked during migration. The folder exists,
|
||||
* but it's only accessible through MigrationUtils.profileStartup.
|
||||
* The migrator can call MigrationUtils.profileStartup.doStartup
|
||||
* at any point in order to initialize the profile.
|
||||
*/
|
||||
get startupOnlyMigrator() false,
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
get sourceHomePageURL() "",
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* getResources.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
getMigrateData: function MP_getMigrateData(aProfile) {
|
||||
let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))];
|
||||
return types.reduce(function(a, b) a |= b, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* migrate for each resource.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
migrate: function MP_migrate(aItems, aStartup, aProfile) {
|
||||
// Not using aStartup because it's going away soon.
|
||||
if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator)
|
||||
MigrationUtils.profileStartup.doStartup();
|
||||
|
||||
let resources = this._getMaybeCachedResources(aProfile);
|
||||
if (resources.length == 0)
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
|
||||
if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
|
||||
resources = [r for each (r in resources) if (aItems & r.type)];
|
||||
|
||||
// TODO: use Map (for the items) and Set (for the resources)
|
||||
// once they are iterable.
|
||||
let resourcesGroupedByItems = new Dict();
|
||||
resources.forEach(function(resource) {
|
||||
if (resourcesGroupedByItems.has(resource.type))
|
||||
resourcesGroupedByItems.get(resource.type).push(resource);
|
||||
else
|
||||
resourcesGroupedByItems.set(resource.type, [resource]);
|
||||
});
|
||||
|
||||
if (resourcesGroupedByItems.count == 0)
|
||||
throw new Error("No items to import");
|
||||
|
||||
let notify = function(aMsg, aItemType) {
|
||||
Services.obs.notifyObservers(null, aMsg, aItemType);
|
||||
}
|
||||
|
||||
notify("Migration:Started");
|
||||
resourcesGroupedByItems.listkeys().forEach(function(migrationType) {
|
||||
let migrationTypeA = migrationType;
|
||||
let itemResources = resourcesGroupedByItems.get(migrationType);
|
||||
notify("Migration:ItemBeforeMigrate", migrationType);
|
||||
|
||||
let itemSuccess = false;
|
||||
itemResources.forEach(function(resource) {
|
||||
let resourceDone = function(aSuccess) {
|
||||
let resourceIndex = itemResources.indexOf(resource);
|
||||
if (resourceIndex != -1) {
|
||||
itemResources.splice(resourceIndex, 1);
|
||||
itemSuccess |= aSuccess;
|
||||
if (itemResources.length == 0) {
|
||||
resourcesGroupedByItems.del(migrationType);
|
||||
notify(itemSuccess ?
|
||||
"Migration:ItemAfterMigrate" : "Migration:ItemError",
|
||||
migrationType);
|
||||
if (resourcesGroupedByItems.count == 0)
|
||||
notify("Migration:Ended");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.tm.mainThread.dispatch(function() {
|
||||
// If migrate throws, an error occurred, and the callback
|
||||
// (itemMayBeDone) might haven't been called.
|
||||
try {
|
||||
resource.migrate(resourceDone);
|
||||
}
|
||||
catch(ex) {
|
||||
Cu.reportError(ex);
|
||||
resourceDone(false);
|
||||
}
|
||||
}, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, this code
|
||||
* won't be part of the migrator itself.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
get sourceExists() {
|
||||
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
|
||||
return false;
|
||||
|
||||
// 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);
|
||||
},
|
||||
|
||||
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
_getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
|
||||
if (this._resourcesByProfile) {
|
||||
if (aProfile in this._resourcesByProfile)
|
||||
return this._resourcesByProfile[aProfile];
|
||||
}
|
||||
else {
|
||||
this._resourcesByProfile = { };
|
||||
}
|
||||
return this._resourcesByProfile[aProfile] = this.getResources(aProfile);
|
||||
}
|
||||
};
|
||||
|
||||
let MigrationUtils = Object.freeze({
|
||||
/**
|
||||
* Helper for implementing simple asynchronous cases of migration resources'
|
||||
* |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
|
||||
* just waits for some file to be read, for example, and then migrates
|
||||
* everything right away, you can wrap the async-function with this helper
|
||||
* and not worry about notifying the callback.
|
||||
*
|
||||
* For example, instead of writing:
|
||||
* setTimeout(function() {
|
||||
* try {
|
||||
* ....
|
||||
* aCallback(true);
|
||||
* }
|
||||
* catch() {
|
||||
* aCallback(false);
|
||||
* }
|
||||
* }, 0);
|
||||
*
|
||||
* You may write:
|
||||
* setTimeout(MigrationUtils.wrapMigrateFunction(function() {
|
||||
* if (importingFromMosaic)
|
||||
* throw Cr.NS_ERROR_UNEXPECTED;
|
||||
* }, aCallback), 0);
|
||||
*
|
||||
* ... and aCallback will be called with aSuccess=false when importing
|
||||
* from Mosaic, or with aSuccess=true otherwise.
|
||||
*
|
||||
* @param aFunction
|
||||
* the function that will be called sometime later. If aFunction
|
||||
* throws when it's called, aCallback(false) is called, otherwise
|
||||
* aCallback(true) is called.
|
||||
* @param aCallback
|
||||
* the callback function passed to |migrate|.
|
||||
* @return the wrapped function.
|
||||
*/
|
||||
wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) {
|
||||
return function() {
|
||||
let success = false;
|
||||
try {
|
||||
aFunction.apply(null, arguments);
|
||||
success = true;
|
||||
}
|
||||
catch(ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
// Do not change this to call aCallback directly in try try & catch
|
||||
// blocks, because if aCallback throws, we may end up calling aCallback
|
||||
// twice.
|
||||
aCallback(success);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper for creating a folder for imported bookmarks from a particular
|
||||
* migration source. The folder is created at the end of the given folder.
|
||||
*
|
||||
* @param aSourceNameStr
|
||||
* the source name (first letter capitalized). This is used
|
||||
* for reading the localized source name from the migration
|
||||
* bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
|
||||
* sourceNameMosaic from the migration bundle).
|
||||
* @param aParentId
|
||||
* the item-id of the folder in which the new folder should be
|
||||
* created.
|
||||
* @return the item-id of the new folder.
|
||||
*/
|
||||
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);
|
||||
return PlacesUtils.bookmarks.createFolder(
|
||||
aParentId, folderName, PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
},
|
||||
|
||||
get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(),
|
||||
|
||||
/*
|
||||
* Returns the migrator for the given source, if any data is available
|
||||
* for this source, or null otherwise.
|
||||
*
|
||||
* @param aKey internal name of the migration source.
|
||||
* Supported values: ie (windows),
|
||||
* safari (mac/windows),
|
||||
* chrome (mac/windows/linux).
|
||||
*
|
||||
* If null is returned, either no data can be imported
|
||||
* for the given migrator, or aMigratorKey is invalid (e.g. ie on mac,
|
||||
* or mosaic everywhere). This method should be used rather than direct
|
||||
* getService for future compatibility (see bug 718280).
|
||||
*
|
||||
* @return profile migrator implementing nsIBrowserProfileMigrator, if it can
|
||||
* import any data, null otherwise.
|
||||
*/
|
||||
getMigrator: function MU_getMigrator(aKey) {
|
||||
let migrator = null;
|
||||
if (this._migrators.has(aKey)) {
|
||||
migrator = this._migrators.get(aKey);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
|
||||
aKey].createInstance(Ci.nsIBrowserProfileMigrator);
|
||||
}
|
||||
catch(ex) {
|
||||
Cu.reportError("Could not get migrator '" + aKey + "' (" + ex + ")");
|
||||
}
|
||||
this._migrators.set(aKey, migrator);
|
||||
}
|
||||
|
||||
return migrator && migrator.sourceExists ? migrator : null;
|
||||
},
|
||||
|
||||
// Whether or not we're in the process of startup migration
|
||||
get isStartupMigration() gProfileStartup != null,
|
||||
|
||||
/**
|
||||
* In the case of startup migration, this is set to the nsIProfileStartup
|
||||
* instance passed to ProfileMigrator's migrate.
|
||||
*
|
||||
* @see showMigrationWizard
|
||||
*/
|
||||
get profileStartup() gProfileStartup,
|
||||
|
||||
/**
|
||||
* Start the migration wizard.
|
||||
*
|
||||
* Supplying a migrator will result in automatic migration. You should
|
||||
* make sure that the migrator for this key exists before passing
|
||||
* it (use getMigrator).
|
||||
*
|
||||
* @param [optional] aOpener
|
||||
* the window to which the wizard window is associated.
|
||||
* @param [optional] aProfileStartup
|
||||
* @see nsIProfileMigrator and nsIProfileStartup. This is used
|
||||
* for initializing the profile during migration and for indicating
|
||||
* startup-migration
|
||||
* @param [optional] aKey
|
||||
* A migration-source internal name (@see getMigrator) for an existent
|
||||
* source. This is ignored if aProfileStartup is not set, and required
|
||||
* if it is.
|
||||
* @param [optional] aSkipImportSourcePage
|
||||
* Whether or not to skip the migration-source selection page in the
|
||||
* wizard (ignored if aProfileStartup is not set). Default: false.
|
||||
*
|
||||
* @throws if aKey is set to an invalid or non-existent migration source.
|
||||
* @note aParentWindow is ignored on OS X, because wizards are not modal
|
||||
* on this platform.
|
||||
*/
|
||||
showMigrationWizard:
|
||||
function MU_showMigrationWizard(aOpener, aProfileStartup, aMigratorKey,
|
||||
aSkipImportSourcePage) {
|
||||
let features = "chrome,dialog,modal,centerscreen,titlebar";
|
||||
let params = null;
|
||||
if (!aProfileStartup) {
|
||||
#ifdef XP_MACOSX
|
||||
let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
|
||||
if (win) {
|
||||
win.focus();
|
||||
return;
|
||||
}
|
||||
features = "centerscreen,chrome,resizable=no";
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
if (!aMigratorKey)
|
||||
throw new Error("aMigratorKey must be set for startup migration");
|
||||
|
||||
let migrator = this.getMigrator(aMigratorKey);
|
||||
if (!migrator) {
|
||||
throw new Error("startMigration was asked to open auto-migrate from a non-existent source: " +
|
||||
aMigratorKey);
|
||||
}
|
||||
else {
|
||||
gProfileStartup = aProfileStartup;
|
||||
}
|
||||
|
||||
// By opening the wizard with a supplied migrator, it will
|
||||
// automatically migrate from it.
|
||||
params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
let keyCSTR = Cc["@mozilla.org/supports-cstring;1"].
|
||||
createInstance(Ci.nsISupportsCString);
|
||||
keyCSTR.data = aMigratorKey;
|
||||
let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Ci.nsISupportsPRBool);
|
||||
params.appendElement(keyCSTR, false);
|
||||
params.appendElement(migrator, false);
|
||||
params.appendElement(aProfileStartup, false);
|
||||
|
||||
if (aSkipImportSourcePage === true) {
|
||||
let wrappedBool = Cc["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Ci.nsISupportsPRBool);
|
||||
wrappedBool.data = true;
|
||||
params.appendElement(wrappedBool);
|
||||
}
|
||||
}
|
||||
|
||||
Services.ww.openWindow(null,
|
||||
"chrome://browser/content/migration/migration.xul",
|
||||
"_blank",
|
||||
features,
|
||||
params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleans up references to migrators and nsIProfileInstance instances.
|
||||
*/
|
||||
finishMigration: function MU_finishMigration() {
|
||||
gMigrators = null;
|
||||
gProfileStartup = null;
|
||||
}
|
||||
});
|
@ -9,65 +9,34 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/MigrationUtils.jsm");
|
||||
|
||||
function ProfileMigrator() {
|
||||
}
|
||||
|
||||
ProfileMigrator.prototype = {
|
||||
migrate: function PM_migrate(aStartup, aKey) {
|
||||
// By opening the wizard with a supplied migrator, it will automatically
|
||||
// migrate from it.
|
||||
let key = null, migrator = null;
|
||||
let skipImportSourcePage = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
if (aKey) {
|
||||
key = aKey;
|
||||
migrator = this._getMigratorIfSourceExists(key);
|
||||
if (!migrator) {
|
||||
let key = aKey;
|
||||
let skipSourcePage = false;
|
||||
if (key.length > 0) {
|
||||
if (!MigrationUtils.getMigrator(key)) {
|
||||
Cu.reportError("Invalid migrator key specified or source does not exist.");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the migrator was passed to us from the caller, use that migrator
|
||||
// and skip the import source page.
|
||||
skipImportSourcePage.data = true;
|
||||
} else {
|
||||
[key, migrator] = this._getDefaultMigrator();
|
||||
skipSourcePage = true;
|
||||
}
|
||||
else {
|
||||
key = this._getDefaultMigrator();
|
||||
}
|
||||
|
||||
if (!key)
|
||||
return;
|
||||
return;
|
||||
|
||||
let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
params.appendElement(this._toCString(key), false);
|
||||
params.appendElement(migrator, false);
|
||||
params.appendElement(aStartup, false);
|
||||
params.appendElement(skipImportSourcePage, false);
|
||||
|
||||
Services.ww.openWindow(null,
|
||||
"chrome://browser/content/migration/migration.xul",
|
||||
"_blank",
|
||||
"chrome,dialog,modal,centerscreen,titlebar",
|
||||
params);
|
||||
},
|
||||
|
||||
_toCString: function PM__toCString(aStr) {
|
||||
let cstr = Cc["@mozilla.org/supports-cstring;1"].
|
||||
createInstance(Ci.nsISupportsCString);
|
||||
cstr.data = aStr;
|
||||
return cstr;
|
||||
},
|
||||
|
||||
_getMigratorIfSourceExists: function PM__getMigratorIfSourceExists(aKey) {
|
||||
try {
|
||||
let cid = "@mozilla.org/profile/migrator;1?app=browser&type=" + aKey;
|
||||
let migrator = Cc[cid].createInstance(Ci.nsIBrowserProfileMigrator);
|
||||
if (migrator.sourceExists)
|
||||
return migrator;
|
||||
} catch (ex) {
|
||||
Cu.reportError("Could not get migrator: " + ex);
|
||||
}
|
||||
return null;
|
||||
MigrationUtils.showMigrationWizard(null, aStartup, key, skipSourcePage);
|
||||
},
|
||||
|
||||
// We don't yet support checking for the default browser on all platforms,
|
||||
@ -144,12 +113,12 @@ ProfileMigrator.prototype = {
|
||||
migratorsOrdered.sort(function(a, b) b == defaultBrowser ? 1 : 0);
|
||||
|
||||
for (let i = 0; i < migratorsOrdered.length; i++) {
|
||||
let migrator = this._getMigratorIfSourceExists(migratorsOrdered[i]);
|
||||
let migrator = MigrationUtils.getMigrator(migratorsOrdered[i]);
|
||||
if (migrator)
|
||||
return [migratorsOrdered[i], migrator];
|
||||
return migratorsOrdered[i];
|
||||
}
|
||||
|
||||
return ["", null];
|
||||
return "";
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProfileMigrator]),
|
||||
|
@ -98,6 +98,7 @@
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsIWindowsRegKey.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
#define kNotFound -1
|
||||
|
||||
@ -480,16 +481,9 @@ nsIEProfileMigrator::GetSourceExists(bool* aResult)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIEProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult)
|
||||
nsIEProfileMigrator::GetSourceProfiles(JS::Value* aResult)
|
||||
{
|
||||
*aResult = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsIEProfileMigrator::GetSourceProfiles(nsIArray** aResult)
|
||||
{
|
||||
*aResult = nsnull;
|
||||
*aResult = JSVAL_NULL;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,7 @@
|
||||
#include "nsToolkitCompsCID.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsTArray.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "mozilla/Util.h"
|
||||
|
||||
@ -197,17 +198,9 @@ nsSafariProfileMigrator::GetSourceExists(bool* aResult)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSafariProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult)
|
||||
nsSafariProfileMigrator::GetSourceProfiles(JS::Value* aResult)
|
||||
{
|
||||
// Safari only has one profile per-user.
|
||||
*aResult = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSafariProfileMigrator::GetSourceProfiles(nsIArray** aResult)
|
||||
{
|
||||
*aResult = nsnull;
|
||||
*aResult = JSVAL_NULL;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,8 @@
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
Components.utils.import("resource://gre/modules/MigrationUtils.jsm");
|
||||
|
||||
var PlacesOrganizer = {
|
||||
_places: null,
|
||||
_content: null,
|
||||
@ -385,20 +387,7 @@ var PlacesOrganizer = {
|
||||
* cookies, history, preferences, and bookmarks.
|
||||
*/
|
||||
importFromBrowser: function PO_importFromBrowser() {
|
||||
#ifdef XP_MACOSX
|
||||
// On Mac, the window is not modal
|
||||
let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
|
||||
if (win) {
|
||||
win.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
let features = "centerscreen,chrome,resizable=no";
|
||||
#else
|
||||
let features = "modal,centerscreen,chrome,resizable=no";
|
||||
#endif
|
||||
window.openDialog("chrome://browser/content/migration/migration.xul",
|
||||
"migration", features);
|
||||
MigrationUtils.showMigrationWizard(window);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2206,6 +2206,10 @@ XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "history",
|
||||
"@mozilla.org/browser/nav-history-service;1",
|
||||
"nsINavHistoryService");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory",
|
||||
"@mozilla.org/browser/history;1",
|
||||
"mozIAsyncHistory");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(PlacesUtils, "bhistory", function() {
|
||||
return PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user