summaryrefslogtreecommitdiffstats
path: root/application/palemoon/components/migration
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/components/migration')
-rw-r--r--application/palemoon/components/migration/BrowserProfileMigrators.manifest14
-rw-r--r--application/palemoon/components/migration/ChromeProfileMigrator.js463
-rw-r--r--application/palemoon/components/migration/FirefoxProfileMigrator.js117
-rw-r--r--application/palemoon/components/migration/IEProfileMigrator.js521
-rw-r--r--application/palemoon/components/migration/MigrationUtils.jsm644
-rw-r--r--application/palemoon/components/migration/ProfileMigrator.js21
-rw-r--r--application/palemoon/components/migration/SafariProfileMigrator.js416
-rw-r--r--application/palemoon/components/migration/content/migration.js474
-rw-r--r--application/palemoon/components/migration/content/migration.xul96
-rw-r--r--application/palemoon/components/migration/jar.mn7
-rw-r--r--application/palemoon/components/migration/moz.build53
-rw-r--r--application/palemoon/components/migration/nsIBrowserProfileMigrator.idl63
-rw-r--r--application/palemoon/components/migration/nsIEHistoryEnumerator.cpp139
-rw-r--r--application/palemoon/components/migration/nsIEHistoryEnumerator.h37
14 files changed, 3065 insertions, 0 deletions
diff --git a/application/palemoon/components/migration/BrowserProfileMigrators.manifest b/application/palemoon/components/migration/BrowserProfileMigrators.manifest
new file mode 100644
index 000000000..d7fec75e3
--- /dev/null
+++ b/application/palemoon/components/migration/BrowserProfileMigrators.manifest
@@ -0,0 +1,14 @@
+component {6F8BB968-C14F-4D6F-9733-6C6737B35DCE} ProfileMigrator.js
+contract @mozilla.org/toolkit/profile-migrator;1 {6F8BB968-C14F-4D6F-9733-6C6737B35DCE}
+component {4cec1de4-1671-4fc3-a53e-6c539dc77a26} ChromeProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=chrome {4cec1de4-1671-4fc3-a53e-6c539dc77a26}
+component {91185366-ba97-4438-acba-48deaca63386} FirefoxProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=firefox {91185366-ba97-4438-acba-48deaca63386}
+#ifdef HAS_IE_MIGRATOR
+component {3d2532e3-4932-4774-b7ba-968f5899d3a4} IEProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=ie {3d2532e3-4932-4774-b7ba-968f5899d3a4}
+#endif
+#ifdef HAS_SAFARI_MIGRATOR
+component {4b609ecf-60b2-4655-9df4-dc149e474da1} SafariProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=safari {4b609ecf-60b2-4655-9df4-dc149e474da1}
+#endif
diff --git a/application/palemoon/components/migration/ChromeProfileMigrator.js b/application/palemoon/components/migration/ChromeProfileMigrator.js
new file mode 100644
index 000000000..61955fbb4
--- /dev/null
+++ b/application/palemoon/components/migration/ChromeProfileMigrator.js
@@ -0,0 +1,463 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et */
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
+
+const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
+const S100NS_PER_MS = 10;
+
+const AUTH_TYPE = {
+ SCHEME_HTML: 0,
+ SCHEME_BASIC: 1,
+ SCHEME_DIGEST: 2
+};
+
+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:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
+ "resource://gre/modules/OSCrypto.jsm");
+
+/**
+ * Convert Chrome time format to Date object
+ *
+ * @param aTime
+ * Chrome time
+ * @return converted Date object
+ * @note Google Chrome uses FILETIME / 10 as time.
+ * FILETIME is based on same structure of Windows.
+ */
+function chromeTimeToDate(aTime)
+{
+ return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000);
+}
+
+/**
+ * Insert bookmark items into specific folder.
+ *
+ * @param aFolderId
+ * id of folder where items will be inserted
+ * @param aItems
+ * bookmark items to be inserted
+ */
+function insertBookmarkItems(aFolderId, aItems)
+{
+ for (let i = 0; i < aItems.length; i++) {
+ let item = aItems[i];
+
+ try {
+ if (item.type == "url") {
+ PlacesUtils.bookmarks.insertBookmark(aFolderId,
+ NetUtil.newURI(item.url),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ item.name);
+ } else if (item.type == "folder") {
+ let newFolderId =
+ PlacesUtils.bookmarks.createFolder(aFolderId,
+ item.name,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+
+ insertBookmarkItems(newFolderId, item.children);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+}
+
+
+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 = Object.create(MigratorPrototype);
+
+ChromeProfileMigrator.prototype.getResources =
+ function Chrome_getResources(aProfile) {
+ if (this._chromeUserDataFolder) {
+ let profileFolder = this._chromeUserDataFolder.clone();
+ profileFolder.append(aProfile.id);
+ if (profileFolder.exists()) {
+ let possibleResources = [GetBookmarksResource(profileFolder),
+ GetHistoryResource(profileFolder),
+ GetCookiesResource(profileFolder),
+#ifdef XP_WIN
+ GetWindowsPasswordsResource(profileFolder)
+#endif
+ ];
+ return [r for each (r in possibleResources) if (r != null)];
+ }
+ }
+ return [];
+ };
+
+Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
+ get: function Chrome_sourceProfiles() {
+ if ("__sourceProfiles" in this)
+ return this.__sourceProfiles;
+
+ if (!this._chromeUserDataFolder)
+ return [];
+
+ let profiles = [];
+ try {
+ // 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.");
+
+ 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;
+ for (let profileFolderName in info_cache) {
+ let profileFolder = this._chromeUserDataFolder.clone();
+ profileFolder.append(profileFolderName);
+ profiles.push({
+ id: profileFolderName,
+ name: info_cache[profileFolderName].name || profileFolderName,
+ });
+ }
+ } catch (e) {
+ 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 = [{
+ id: "Default",
+ name: "Default",
+ }];
+ }
+ }
+
+ // Only list profiles from which any data can be imported
+ return this.__sourceProfiles = profiles.filter(function(profile) {
+ let resources = this.getResources(profile);
+ return resources && resources.length > 0;
+ }, this);
+ }
+});
+
+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 "";
+ }
+});
+
+function GetBookmarksResource(aProfileFolder) {
+ let bookmarksFile = aProfileFolder.clone();
+ bookmarksFile.append("Bookmarks");
+ if (!bookmarksFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ 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);
+ }
+
+ // 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);
+ }
+ }
+ }, null);
+ }, aCallback));
+ }
+ };
+}
+
+function GetHistoryResource(aProfileFolder) {
+ let historyFile = aProfileFolder.clone();
+ historyFile.append("History");
+ if (!historyFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ 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: MigrationUtils.resourceTypes.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({
+ handleResult : function(aResults) {
+ for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
+ let host_key = row.getResultByName("host_key");
+ if (host_key.match(/^\./)) {
+ // 1st character of host_key may be ".", so we have to remove it
+ host_key = host_key.substr(1);
+ }
+
+ try {
+ let expiresUtc =
+ chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
+ Services.cookies.add(host_key,
+ row.getResultByName("path"),
+ row.getResultByName("name"),
+ row.getResultByName("value"),
+ row.getResultByName("secure"),
+ row.getResultByName("httponly"),
+ false,
+ parseInt(expiresUtc));
+ } 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 GetWindowsPasswordsResource(aProfileFolder) {
+ let loginFile = aProfileFolder.clone();
+ loginFile.append("Login Data");
+ if (!loginFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ migrate(aCallback) {
+ let dbConn = Services.storage.openUnsharedDatabase(loginFile);
+ let stmt = dbConn.createAsyncStatement(`
+ SELECT origin_url, action_url, username_element, username_value,
+ password_element, password_value, signon_realm, scheme, date_created,
+ times_used FROM logins WHERE blacklisted_by_user = 0`);
+ let crypto = new OSCrypto();
+ let utf8Converter = Cc["@mozilla.org/intl/utf8converterservice;1"].getService(Ci.nsIUTF8ConverterService);
+
+ stmt.executeAsync({
+ _rowToLoginInfo(row) {
+ let loginInfo = {
+ username: utf8Converter.convertURISpecToUTF8(row.getResultByName("username_value"), "UTF-8"),
+ password: utf8Converter.convertURISpecToUTF8(
+ crypto.decryptData(crypto.arrayToString(row.getResultByName("password_value")), null),
+ "UTF-8"),
+ hostName: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
+ submitURL: null,
+ httpRealm: null,
+ usernameElement: row.getResultByName("username_element"),
+ passwordElement: row.getResultByName("password_element"),
+ timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
+ timesUsed: row.getResultByName("times_used") + 0,
+ };
+
+ switch (row.getResultByName("scheme")) {
+ case AUTH_TYPE.SCHEME_HTML:
+ loginInfo.submitURL = NetUtil.newURI(row.getResultByName("action_url")).prePath;
+ break;
+ case AUTH_TYPE.SCHEME_BASIC:
+ case AUTH_TYPE.SCHEME_DIGEST:
+ // signon_realm format is URIrealm, so we need remove URI
+ loginInfo.httpRealm = row.getResultByName("signon_realm")
+ .substring(loginInfo.hostName.length + 1);
+ break;
+ default:
+ throw new Error("Login data scheme type not supported: " +
+ row.getResultByName("scheme"));
+ }
+
+ return loginInfo;
+ },
+
+ handleResult(aResults) {
+ for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
+ try {
+ let loginInfo = this._rowToLoginInfo(row);
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+
+ login.init(loginInfo.hostName, loginInfo.submitURL, loginInfo.httpRealm,
+ loginInfo.username, loginInfo.password, loginInfo.usernameElement,
+ loginInfo.passwordElement);
+ login.QueryInterface(Ci.nsILoginMetaInfo);
+ login.timeCreated = loginInfo.timeCreated;
+ login.timeLastUsed = loginInfo.timeCreated;
+ login.timePasswordChanged = loginInfo.timeCreated;
+ login.timesUsed = loginInfo.timesUsed;
+
+ // Add the login only if there's not an existing entry
+ let logins = Services.logins.findLogins({}, login.hostname,
+ login.formSubmitURL,
+ login.httpRealm);
+
+ // Bug 1187190: Password changes should be propagated depending on timestamps.
+ if (!logins.some(l => login.matches(l, true))) {
+ Services.logins.addLogin(login);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ handleError(aError) {
+ Cu.reportError("Async statement execution returned with '" +
+ aError.result + "', '" + aError.message + "'");
+ },
+
+ handleCompletion(aReason) {
+ dbConn.asyncClose();
+ aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
+ crypto.finalize();
+ },
+ });
+ stmt.finalize();
+ }
+ };
+}
+
+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}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);
diff --git a/application/palemoon/components/migration/FirefoxProfileMigrator.js b/application/palemoon/components/migration/FirefoxProfileMigrator.js
new file mode 100644
index 000000000..ab4ae55fb
--- /dev/null
+++ b/application/palemoon/components/migration/FirefoxProfileMigrator.js
@@ -0,0 +1,117 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et */
+ /* 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/. */
+
+"use strict";
+
+/*
+ * Migrates from a Pale Moon 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.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/MigrationUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+
+function FirefoxProfileMigrator() { }
+
+FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+FirefoxProfileMigrator.prototype._getAllProfiles = function () {
+ let allProfiles = new Map();
+ let profiles =
+ Components.classes["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Components.interfaces.nsIToolkitProfileService)
+ .profiles;
+ while (profiles.hasMoreElements()) {
+ let profile = profiles.getNext().QueryInterface(Components.interfaces.nsIToolkitProfile);
+ let rootDir = profile.rootDir;
+
+ if (rootDir.exists() && rootDir.isReadable() &&
+ !rootDir.equals(MigrationUtils.profileStartup.directory)) {
+ allProfiles.set(profile.name, rootDir);
+ }
+ }
+ return allProfiles;
+};
+
+function sorter(a, b) {
+ return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
+}
+
+Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
+ get: function() {
+ return [{id: x, name: x} for (x of this._getAllProfiles().keys())].sort(sorter);
+ }
+});
+
+FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
+ let sourceProfileDir = aProfile ? this._getAllProfiles().get(aProfile.id) :
+ Components.classes["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Components.interfaces.nsIToolkitProfileService)
+ .selectedProfile.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);
+
+ if (file.exists()) {
+ files.push(file);
+ }
+ }
+ if (!files.length) {
+ return null;
+ }
+ 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", "places.sqlite-wal"]);
+ let cookies = getFileResource(types.COOKIES, ["cookies.sqlite", "cookies.sqlite-wal"]);
+ let passwords = getFileResource(types.PASSWORDS,
+ ["signons.sqlite", "logins.json", "key3.db"]);
+ let formData = getFileResource(types.FORMDATA, ["formhistory.sqlite"]);
+ let bookmarksBackups = getFileResource(types.OTHERDATA,
+ [PlacesBackups.profileRelativeFolderPath]);
+ let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
+
+ return [r for each (r in [places, cookies, passwords, formData,
+ dictionary, bookmarksBackups]) if (r)];
+}
+
+Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
+ get: function() true
+});
+
+
+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}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);
diff --git a/application/palemoon/components/migration/IEProfileMigrator.js b/application/palemoon/components/migration/IEProfileMigrator.js
new file mode 100644
index 000000000..a46deae68
--- /dev/null
+++ b/application/palemoon/components/migration/IEProfileMigrator.js
@@ -0,0 +1,521 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
+const kRegMultiSz = 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:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+Cu.importGlobalProperties(["File"]);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers.
+
+let CtypesHelpers = {
+ _structs: {},
+ _functions: {},
+ _libs: {},
+
+ /**
+ * Must be invoked once before first use of any of the provided helpers.
+ */
+ initialize: function CH_initialize() {
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const BOOL = ctypes.int;
+
+ this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
+ {wYear: WORD},
+ {wMonth: WORD},
+ {wDayOfWeek: WORD},
+ {wDay: WORD},
+ {wHour: WORD},
+ {wMinute: WORD},
+ {wSecond: WORD},
+ {wMilliseconds: WORD}
+ ]);
+
+ this._structs.FILETIME = new ctypes.StructType('FILETIME', [
+ {dwLowDateTime: DWORD},
+ {dwHighDateTime: DWORD}
+ ]);
+
+ try {
+ this._libs.kernel32 = ctypes.open("Kernel32");
+ this._functions.FileTimeToSystemTime =
+ this._libs.kernel32.declare("FileTimeToSystemTime",
+ ctypes.default_abi,
+ BOOL,
+ this._structs.FILETIME.ptr,
+ this._structs.SYSTEMTIME.ptr);
+ } catch (ex) {
+ this.finalize();
+ }
+ },
+
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize: function CH_finalize() {
+ this._structs = {};
+ this._functions = {};
+ for each (let lib in this._libs) {
+ try {
+ lib.close();
+ } catch (ex) {}
+ }
+ this._libs = {};
+ },
+
+ /**
+ * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct.
+ *
+ * @param aTimeHi
+ * Least significant DWORD.
+ * @param aTimeLo
+ * Most significant DWORD.
+ * @return a Date object representing the converted datetime.
+ */
+ fileTimeToDate: function CH_fileTimeToDate(aTimeHi, aTimeLo) {
+ let fileTime = this._structs.FILETIME();
+ fileTime.dwLowDateTime = aTimeLo;
+ fileTime.dwHighDateTime = aTimeHi;
+ let systemTime = this._structs.SYSTEMTIME();
+ let result = this._functions.FileTimeToSystemTime(fileTime.address(),
+ systemTime.address());
+ if (result == 0)
+ throw new Error(ctypes.winLastError);
+
+ return new Date(systemTime.wYear,
+ systemTime.wMonth - 1,
+ systemTime.wDay,
+ systemTime.wHour,
+ systemTime.wMinute,
+ systemTime.wSecond,
+ systemTime.wMilliseconds);
+ }
+};
+
+/**
+ * Checks whether an host is an IP (v4 or v6) address.
+ *
+ * @param aHost
+ * The host to check.
+ * @return whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+ try {
+ Services.eTLD.getBaseDomainFromHost(aHost);
+ } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+ return true;
+ } catch (e) {}
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Resources
+
+function Bookmarks() {
+}
+
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() !!this._favoritesFolder,
+
+ __favoritesFolder: null,
+ get _favoritesFolder() {
+ if (!this.__favoritesFolder) {
+ let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
+ if (favoritesFolder.exists() && favoritesFolder.isReadable())
+ this.__favoritesFolder = favoritesFolder;
+ }
+ return this.__favoritesFolder;
+ },
+
+ __toolbarFolderName: null,
+ get _toolbarFolderName() {
+ if (!this.__toolbarFolderName) {
+ // Retrieve the name of IE's favorites subfolder that holds the bookmarks
+ // in the toolbar. This was previously stored in the registry and changed
+ // in IE7 to always be called "Links".
+ let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\Toolbar",
+ "LinksFolderName");
+ this.__toolbarFolderName = folderName || "Links";
+ }
+ return this.__toolbarFolderName;
+ },
+
+ migrate: function B_migrate(aCallback) {
+ PlacesUtils.bookmarks.runInBatchMode({
+ runBatched: (function migrateBatched() {
+ // Import to the bookmarks menu.
+ let destFolderId = PlacesUtils.bookmarksMenuFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ destFolderId =
+ MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
+ }
+
+ this._migrateFolder(this._favoritesFolder, destFolderId);
+
+ aCallback(true);
+ }).bind(this)
+ }, null);
+ },
+
+ _migrateFolder: function B__migrateFolder(aSourceFolder, aDestFolderId) {
+ // TODO (bug 741993): the favorites order is stored in the Registry, at
+ // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
+ // Until we support it, bookmarks are imported in alphabetical order.
+ let entries = aSourceFolder.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+
+ // Make sure that entry.path == entry.target to not follow .lnk folder
+ // shortcuts which could lead to infinite cycles.
+ if (entry.isDirectory() && entry.path == entry.target) {
+ let destFolderId;
+ if (entry.leafName == this._toolbarFolderName &&
+ entry.parent.equals(this._favoritesFolder)) {
+ // Import to the bookmarks toolbar.
+ destFolderId = PlacesUtils.toolbarFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ destFolderId =
+ MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
+ }
+ }
+ else {
+ // Import to a new folder.
+ destFolderId =
+ PlacesUtils.bookmarks.createFolder(aDestFolderId, entry.leafName,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ }
+
+ if (entry.isReadable()) {
+ // Recursively import the folder.
+ this._migrateFolder(entry, destFolderId);
+ }
+ }
+ else {
+ // Strip the .url extension, to both check this is a valid link file,
+ // and get the associated title.
+ let matches = entry.leafName.match(/(.+)\.url$/i);
+ if (matches) {
+ let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
+ getService(Ci.nsIFileProtocolHandler);
+ let uri = fileHandler.readURLFile(entry);
+ let title = matches[1];
+
+ PlacesUtils.bookmarks.insertBookmark(aDestFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title);
+ }
+ }
+ }
+ }
+};
+
+function History() {
+}
+
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ get exists() true,
+
+ __typedURLs: null,
+ get _typedURLs() {
+ if (!this.__typedURLs) {
+ // The list of typed URLs is a sort of annotation stored in the registry.
+ // Currently, IE stores 25 entries and this value is not configurable,
+ // but we just keep reading up to the first non-existing entry to support
+ // possible future bumps of this limit.
+ this.__typedURLs = {};
+ let registry = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ registry.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\TypedURLs",
+ Ci.nsIWindowsRegKey.ACCESS_READ);
+ for (let entry = 1; registry.hasValue("url" + entry); entry++) {
+ let url = registry.readStringValue("url" + entry);
+ this.__typedURLs[url] = true;
+ }
+ } catch (ex) {
+ } finally {
+ registry.close();
+ }
+ }
+ return this.__typedURLs;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ let places = [];
+ let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
+ createInstance(Ci.nsISimpleEnumerator);
+ while (historyEnumerator.hasMoreElements()) {
+ let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
+ let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
+ // MSIE stores some types of URLs in its history that we don't handle,
+ // like HTMLHelp and others. Since we don't properly map handling for
+ // all of them we just avoid importing them.
+ if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+
+ let title = entry.get("title");
+ // Embed visits have no title and don't need to be imported.
+ if (title.length == 0) {
+ continue;
+ }
+
+ // The typed urls are already fixed-up, so we can use them for comparison.
+ let transitionType = this._typedURLs[uri.spec] ?
+ Ci.nsINavHistoryService.TRANSITION_TYPED :
+ Ci.nsINavHistoryService.TRANSITION_LINK;
+ let lastVisitTime = entry.get("time");
+
+ places.push(
+ { uri: uri,
+ title: title,
+ visits: [{ transitionType: transitionType,
+ visitDate: lastVisitTime }]
+ }
+ );
+ }
+
+ // Check whether there is any history to import.
+ if (places.length == 0) {
+ aCallback(true);
+ return;
+ }
+
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+};
+
+function Cookies() {
+}
+
+Cookies.prototype = {
+ type: MigrationUtils.resourceTypes.COOKIES,
+
+ get exists() !!this._cookiesFolder,
+
+ __cookiesFolder: null,
+ get _cookiesFolder() {
+ // Cookies are stored in txt files, in a Cookies folder whose path varies
+ // across the different OS versions. CookD takes care of most of these
+ // cases, though, in Windows Vista/7, UAC makes a difference.
+ // If UAC is enabled, the most common destination is CookD/Low. Though,
+ // if the user runs the application in administrator mode or disables UAC,
+ // cookies are stored in the original CookD destination. Cause running the
+ // browser in administrator mode is unsafe and discouraged, we just care
+ // about the UAC state.
+ if (!this.__cookiesFolder) {
+ let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
+ if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
+ // Check if UAC is enabled.
+ if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+ cookiesFolder.append("Low");
+ }
+ this.__cookiesFolder = cookiesFolder;
+ }
+ }
+ return this.__cookiesFolder;
+ },
+
+ migrate: function C_migrate(aCallback) {
+ CtypesHelpers.initialize();
+
+ let cookiesGenerator = (function genCookie() {
+ let success = false;
+ let entries = this._cookiesFolder.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+ // Skip eventual bogus entries.
+ if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+ continue;
+
+ this._readCookieFile(entry, function(aSuccess) {
+ // Importing even a single cookie file is considered a success.
+ if (aSuccess)
+ success = true;
+ try {
+ cookiesGenerator.next();
+ } catch (ex) {}
+ });
+
+ yield;
+ }
+
+ CtypesHelpers.finalize();
+
+ aCallback(success);
+ }).apply(this);
+ cookiesGenerator.next();
+ },
+
+ _readCookieFile: function C__readCookieFile(aFile, aCallback) {
+ let fileReader = Cc["@mozilla.org/files/filereader;1"].
+ createInstance(Ci.nsIDOMFileReader);
+ fileReader.addEventListener("loadend", (function onLoadEnd() {
+ fileReader.removeEventListener("loadend", onLoadEnd, false);
+
+ if (fileReader.readyState != fileReader.DONE) {
+ Cu.reportError("Could not read cookie contents: " + fileReader.error);
+ aCallback(false);
+ return;
+ }
+
+ let success = true;
+ try {
+ this._parseCookieBuffer(fileReader.result);
+ } catch (ex) {
+ Components.utils.reportError("Unable to migrate cookie: " + ex);
+ success = false;
+ } finally {
+ aCallback(success);
+ }
+ }).bind(this), false);
+ fileReader.readAsText(new File(aFile));
+ },
+
+ /**
+ * Parses a cookie file buffer and returns an array of the contained cookies.
+ *
+ * The cookie file format is a newline-separated-values with a "*" used as
+ * delimeter between multiple records.
+ * Each cookie has the following fields:
+ * - name
+ * - value
+ * - host/path
+ * - flags
+ * - Expiration time most significant integer
+ * - Expiration time least significant integer
+ * - Creation time most significant integer
+ * - Creation time least significant integer
+ * - Record delimiter "*"
+ *
+ * @note All the times are in FILETIME format.
+ */
+ _parseCookieBuffer: function C__parseCookieBuffer(aTextBuffer) {
+ // Note the last record is an empty string.
+ let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
+ for (let record of records) {
+ let [name, value, hostpath, flags,
+ expireTimeLo, expireTimeHi] = record.split("\n");
+
+ // IE stores deleted cookies with a zero-length value, skip them.
+ if (value.length == 0)
+ continue;
+
+ let hostLen = hostpath.indexOf("/");
+ let host = hostpath.substr(0, hostLen);
+ let path = hostpath.substr(hostLen);
+
+ // For a non-null domain, assume it's what Mozilla considers
+ // a domain cookie. See bug 222343.
+ if (host.length > 0) {
+ // Fist delete any possible extant matching host cookie.
+ Services.cookies.remove(host, name, path, false);
+ // Now make it a domain cookie.
+ if (host[0] != "." && !hostIsIPAddress(host))
+ host = "." + host;
+ }
+
+ let expireTime = CtypesHelpers.fileTimeToDate(Number(expireTimeHi),
+ Number(expireTimeLo));
+ Services.cookies.add(host,
+ path,
+ name,
+ value,
+ Number(flags) & 0x1, // secure
+ false, // httpOnly
+ false, // session
+ expireTime);
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Migrator
+
+function IEProfileMigrator()
+{
+}
+
+IEProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+IEProfileMigrator.prototype.getResources = function IE_getResources() {
+ let resources = [
+ new Bookmarks()
+ , new History()
+ , new Cookies()
+ ];
+ return [r for each (r in resources) if (r.exists)];
+};
+
+Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function IE_get_sourceHomePageURL() {
+ let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ kMainKey, "Default_Page_URL");
+ let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Start Page");
+ // If the user didn't customize the Start Page, he is still on the default
+ // page, that may be considered the equivalent of our about:home. There's
+ // no reason to retain it, since it is heavily targeted to IE.
+ let homepage = startPage != defaultStartPage ? startPage : "";
+
+ // IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
+ // are in addition to the Start Page, and no empty entries are possible,
+ // thus a Start Page is always defined if any of these exists, though it
+ // may be the default one.
+ let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Secondary Start Pages");
+ if (secondaryPages) {
+ if (homepage)
+ secondaryPages.unshift(homepage);
+ homepage = secondaryPages.join("|");
+ }
+
+ return homepage;
+ }
+});
+
+IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
+IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
+IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);
diff --git a/application/palemoon/components/migration/MigrationUtils.jsm b/application/palemoon/components/migration/MigrationUtils.jsm
new file mode 100644
index 000000000..fcd73a798
--- /dev/null
+++ b/application/palemoon/components/migration/MigrationUtils.jsm
@@ -0,0 +1,644 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
+const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
+
+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");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
+ "resource://gre/modules/BookmarkHTMLUtils.jsm");
+
+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;
+}
+
+/**
+ * Figure out what is the default browser, and if there is a migrator
+ * for it, return that migrator's internal name.
+ * For the time being, the "internal name" of a migraotr is its contract-id
+ * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
+ * but it will soon be exposed properly.
+ */
+function getMigratorKeyForDefaultBrowser() {
+ const APP_DESC_TO_KEY = {
+ "Internet Explorer": "ie",
+ "Safari": "safari",
+ "Pale Moon web browser": "firefox",
+ "Google Chrome": "chrome", // Windows, Linux
+ "Chrome": "chrome", // OS X
+ };
+
+ let browserDesc = "";
+ try {
+ let browserDesc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService).
+ getApplicationDescription("http");
+ return APP_DESC_TO_KEY[browserDesc] || "";
+ }
+ catch(ex) {
+ Cu.reportError("Could not detect default browser: " + ex);
+ }
+ return "";
+}
+
+/**
+ * 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|.
+ */
+this.MigratorPrototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
+
+ /**
+ * OVERRIDE IF AND ONLY IF the source supports multiple profiles.
+ *
+ * Returns array of profile objects from which data may be imported. The object
+ * should have the following keys:
+ * id - a unique string identifier for the profile
+ * name - a pretty name to display to the user in the UI
+ *
+ * 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) {
+ 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)];
+
+ // Called either directly or through the bookmarks import callback.
+ function doMigrate() {
+ // 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);
+ });
+ });
+ }
+
+ if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
+ MigrationUtils.profileStartup.doStartup();
+
+ // If we're about to migrate bookmarks, first import the default bookmarks.
+ // Note We do not need to do so for the Firefox migrator
+ // (=startupOnlyMigrator), as it just copies over the places database
+ // from another profile.
+ const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS;
+ let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS);
+ if (migratingBookmarks) {
+ let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+ browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
+
+ // Note doMigrate doesn't care about the success of the import.
+ let onImportComplete = function() {
+ browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
+ doMigrate();
+ };
+ BookmarkHTMLUtils.importFromURL(
+ "resource:///defaults/profile/bookmarks.html", true).then(
+ onImportComplete, onImportComplete);
+ return;
+ }
+ }
+ doMigrate();
+ },
+
+ /**
+ * 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 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 ***/
+ _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
+ let profileKey = aProfile ? aProfile.id : "";
+ if (this._resourcesByProfile) {
+ if (profileKey in this._resourcesByProfile)
+ return this._resourcesByProfile[profileKey];
+ }
+ else {
+ this._resourcesByProfile = { };
+ }
+ return this._resourcesByProfile[profileKey] = this.getResources(aProfile);
+ }
+};
+
+this.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
+ * 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);
+ }
+ },
+
+ /**
+ * 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",
+ "64_firefox": "64_firefox_other"
+ };
+ 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.
+ *
+ * @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 source = this.getLocalizedString("sourceName" + aSourceNameStr);
+ let label = this.getLocalizedString("importedBookmarksFolder", [source]);
+ return PlacesUtils.bookmarks.createFolder(
+ aParentId, label, 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),
+ * firefox.
+ *
+ * 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) { }
+ this._migrators.set(aKey, migrator);
+ }
+
+ return migrator && migrator.sourceExists ? migrator : null;
+ },
+
+ // Iterates the available migrators, in the most suitable
+ // order for the running platform.
+ get migrators() {
+ let migratorKeysOrdered = [
+#ifdef XP_WIN
+ "firefox", "ie", "chrome", "safari"
+#elifdef XP_MACOSX
+ "firefox", "safari", "chrome"
+#elifdef XP_UNIX
+ "firefox", "chrome"
+#endif
+ ];
+
+ // If a supported default browser is found check it first
+ // so that the wizard defaults to import from that browser.
+ let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
+ if (defaultBrowserKey)
+ migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0);
+
+ for (let migratorKey of migratorKeysOrdered) {
+ let migrator = this.getMigrator(migratorKey);
+ if (migrator)
+ yield migrator;
+ }
+ },
+
+ // 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,
+
+ /**
+ * Show the migration wizard. On mac, this may just focus the wizard if it's
+ * already running, in which case aOpener and aParams are ignored.
+ *
+ * @param [optional] aOpener
+ * the window that asks to open the wizard.
+ * @param [optional] aParams
+ * arguments for the migration wizard, in the form of an nsIArray.
+ * This is passed as-is for the params argument of
+ * nsIWindowWatcher.openWindow.
+ */
+ showMigrationWizard:
+ function MU_showMigrationWizard(aOpener, aParams) {
+ let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
+#ifdef XP_MACOSX
+ if (!this.isStartupMigration) {
+ let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
+ if (win) {
+ win.focus();
+ return;
+ }
+ // On mac, the migration wiazrd should only be modal in the case of
+ // startup-migration.
+ features = "centerscreen,chrome,resizable=no";
+ }
+#endif
+
+ Services.ww.openWindow(aOpener,
+ "chrome://browser/content/migration/migration.xul",
+ "_blank",
+ features,
+ aParams);
+ },
+
+ /**
+ * Show the migration wizard for startup-migration. This should only be
+ * called by ProfileMigrator (see ProfileMigrator.js), which implements
+ * nsIProfileMigrator.
+ *
+ * @param aProfileStartup
+ * the nsIProfileStartup instance provided to ProfileMigrator.migrate.
+ * @param [optional] aMigratorKey
+ * If set, the migration wizard will import from the corresponding
+ * migrator, bypassing the source-selection page. Otherwise, the
+ * source-selection page will be displayed, either with the default
+ * browser selected, if it could be detected and if there is a
+ * migrator for it, or with the first option selected as a fallback
+ * (The first option is hardcoded to be the most common browser for
+ * the OS we run on. See migration.xul).
+ * @param [optional] aProfileToMigrate
+ * If set, the migration wizard will import from the profile indicated.
+ *
+ * @throws if aMigratorKey is invalid or if it points to a non-existent
+ * source.
+ */
+ startupMigration:
+ function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
+ if (!aProfileStartup) {
+ throw new Error("a profile-startup instance is required for startup-migration");
+ }
+ gProfileStartup = aProfileStartup;
+
+ let skipSourcePage = false, migrator = null, migratorKey = "";
+ if (aMigratorKey) {
+ migrator = this.getMigrator(aMigratorKey);
+ if (!migrator) {
+ // aMigratorKey must point to a valid source, so, if it doesn't
+ // cleanup and throw.
+ this.finishMigration();
+ throw new Error("startMigration was asked to open auto-migrate from " +
+ "a non-existent source: " + aMigratorKey);
+ }
+ migratorKey = aMigratorKey;
+ skipSourcePage = true;
+ }
+ else {
+ let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
+ if (defaultBrowserKey) {
+ migrator = this.getMigrator(defaultBrowserKey);
+ if (migrator)
+ migratorKey = defaultBrowserKey;
+ }
+ }
+
+ if (!migrator) {
+ // If there's no migrator set so far, ensure that there is at least one
+ // migrator available before opening the wizard.
+ try {
+ this.migrators.next();
+ }
+ catch(ex) {
+ this.finishMigration();
+ if (!(ex instanceof StopIteration))
+ throw ex;
+ return;
+ }
+ }
+
+ let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ let keyCSTR = Cc["@mozilla.org/supports-cstring;1"].
+ createInstance(Ci.nsISupportsCString);
+ keyCSTR.data = migratorKey;
+ let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ skipImportSourcePageBool.data = skipSourcePage;
+ let profileToMigrate = null;
+ if (aProfileToMigrate) {
+ profileToMigrate = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ profileToMigrate.data = aProfileToMigrate;
+ }
+ params.appendElement(keyCSTR, false);
+ params.appendElement(migrator, false);
+ params.appendElement(aProfileStartup, false);
+ params.appendElement(skipImportSourcePageBool, false);
+ params.appendElement(profileToMigrate, false);
+
+ this.showMigrationWizard(null, params);
+ },
+
+ /**
+ * Cleans up references to migrators and nsIProfileInstance instances.
+ */
+ finishMigration: function MU_finishMigration() {
+ gMigrators = null;
+ gProfileStartup = null;
+ gMigrationBundle = null;
+ }
+});
diff --git a/application/palemoon/components/migration/ProfileMigrator.js b/application/palemoon/components/migration/ProfileMigrator.js
new file mode 100644
index 000000000..f67823bae
--- /dev/null
+++ b/application/palemoon/components/migration/ProfileMigrator.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/MigrationUtils.jsm");
+
+function ProfileMigrator() {
+}
+
+ProfileMigrator.prototype = {
+ migrate: MigrationUtils.startupMigration.bind(MigrationUtils),
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProfileMigrator]),
+ classDescription: "Profile Migrator",
+ contractID: "@mozilla.org/toolkit/profile-migrator;1",
+ classID: Components.ID("6F8BB968-C14F-4D6F-9733-6C6737B35DCE")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ProfileMigrator]);
diff --git a/application/palemoon/components/migration/SafariProfileMigrator.js b/application/palemoon/components/migration/SafariProfileMigrator.js
new file mode 100644
index 000000000..70804793d
--- /dev/null
+++ b/application/palemoon/components/migration/SafariProfileMigrator.js
@@ -0,0 +1,416 @@
+/* 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/. */
+
+"use strict";
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PropertyListUtils",
+ "resource://gre/modules/PropertyListUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function Bookmarks(aBookmarksFile) {
+ this._file = aBookmarksFile;
+}
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ migrate: function B_migrate(aCallback) {
+ PropertyListUtils.read(this._file,
+ MigrationUtils.wrapMigrateFunction(function migrateBookmarks(aDict) {
+ if (!aDict)
+ throw new Error("Could not read Bookmarks.plist");
+
+ let children = aDict.get("Children");;
+ if (!children)
+ throw new Error("Invalid Bookmarks.plist format");
+
+ PlacesUtils.bookmarks.runInBatchMode({
+ runBatched: function() {
+ let collection = aDict.get("Title") == "com.apple.ReadingList" ?
+ this.READING_LIST_COLLECTION : this.ROOT_COLLECTION;
+ this._migrateCollection(children, collection);
+ }.bind(this)
+ }, null);
+ }.bind(this), aCallback));
+ },
+
+ // Bookmarks collections in Safari. Constants for migrateCollection.
+ ROOT_COLLECTION: 0,
+ MENU_COLLECTION: 1,
+ TOOLBAR_COLLECTION: 2,
+ READING_LIST_COLLECTION: 3,
+
+ /**
+ * Recursively migrate a Safari collection of bookmarks.
+ *
+ * @param aEntries
+ * the collection's children
+ * @param aCollection
+ * one of the values above.
+ */
+ _migrateCollection: function B__migrateCollection(aEntries, aCollection) {
+ // A collection of bookmarks in Safari resembles places roots. In the
+ // property list files (Bookmarks.plist, ReadingList.plist) they are
+ // stored as regular bookmarks folders, and thus can only be distinguished
+ // from by their names and places in the hierarchy.
+
+ let entriesFiltered = [];
+ if (aCollection == this.ROOT_COLLECTION) {
+ for (let entry of aEntries) {
+ let type = entry.get("WebBookmarkType");
+ if (type == "WebBookmarkTypeList" && entry.has("Children")) {
+ let title = entry.get("Title");
+ let children = entry.get("Children");
+ if (title == "BookmarksBar")
+ this._migrateCollection(children, this.TOOLBAR_COLLECTION);
+ else if (title == "BookmarksMenu")
+ this._migrateCollection(children, this.MENU_COLLECTION);
+ else if (title == "com.apple.ReadingList")
+ this._migrateCollection(children, this.READING_LIST_COLLECTION);
+ else if (entry.get("ShouldOmitFromUI") !== true)
+ entriesFiltered.push(entry);
+ }
+ else if (type == "WebBookmarkTypeLeaf") {
+ entriesFiltered.push(entry);
+ }
+ }
+ }
+ else {
+ entriesFiltered = aEntries;
+ }
+
+ if (entriesFiltered.length == 0)
+ return;
+
+ let folder = -1;
+ switch (aCollection) {
+ case this.ROOT_COLLECTION: {
+ // In Safari, it is possible (though quite cumbersome) to move
+ // bookmarks to the bookmarks root, which is the parent folder of
+ // all bookmarks "collections". That is somewhat in parallel with
+ // both the places root and the unfiled-bookmarks root.
+ // Because the former is only an implementation detail in our UI,
+ // the unfiled root seems to be the best choice.
+ folder = PlacesUtils.unfiledBookmarksFolderId;
+ break;
+ }
+ case this.MENU_COLLECTION: {
+ folder = PlacesUtils.bookmarksMenuFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ folder = MigrationUtils.createImportedBookmarksFolder("Safari",
+ folder);
+ }
+ break;
+ }
+ case this.TOOLBAR_COLLECTION: {
+ folder = PlacesUtils.toolbarFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ folder = MigrationUtils.createImportedBookmarksFolder("Safari",
+ folder);
+ }
+ break;
+ }
+ case this.READING_LIST_COLLECTION: {
+ // Reading list items are imported as regular bookmarks.
+ // They are imported under their own folder, created either under the
+ // bookmarks menu (in the case of startup migration).
+ folder = PlacesUtils.bookmarks.createFolder(
+ PlacesUtils.bookmarksMenuFolderId,
+ MigrationUtils.getLocalizedString("importedSafariReadingList"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ break;
+ }
+ default:
+ throw new Error("Unexpected value for aCollection!");
+ }
+
+ this._migrateEntries(entriesFiltered, folder);
+ },
+
+ // migrate the given array of safari bookmarks to the given places
+ // folder.
+ _migrateEntries: function B__migrateEntries(aEntries, aFolderId) {
+ for (let entry of aEntries) {
+ let type = entry.get("WebBookmarkType");
+ if (type == "WebBookmarkTypeList" && entry.has("Children")) {
+ let title = entry.get("Title");
+ let folderId = PlacesUtils.bookmarks.createFolder(
+ aFolderId, title, PlacesUtils.bookmarks.DEFAULT_INDEX);
+
+ // Empty folders may not have a children array.
+ if (entry.has("Children"))
+ this._migrateEntries(entry.get("Children"), folderId, false);
+ }
+ else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
+ let title, uri;
+ if (entry.has("URIDictionary"))
+ title = entry.get("URIDictionary").get("title");
+
+ try {
+ uri = NetUtil.newURI(entry.get("URLString"));
+ }
+ catch(ex) {
+ Cu.reportError("Invalid uri set for Safari bookmark: " + entry.get("URLString"));
+ }
+ if (uri) {
+ PlacesUtils.bookmarks.insertBookmark(aFolderId, uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX, title);
+ }
+ }
+ }
+ }
+};
+
+function History(aHistoryFile) {
+ this._file = aHistoryFile;
+}
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ // Helper method for converting the visit date property to a PRTime value.
+ // The visit date is stored as a string, so it's not read as a Date
+ // object by PropertyListUtils.
+ _parseCocoaDate: function H___parseCocoaDate(aCocoaDateStr) {
+ let asDouble = parseFloat(aCocoaDateStr);
+ if (!isNaN(asDouble)) {
+ // reference date of NSDate.
+ let date = new Date("1 January 2001, GMT");
+ date.setMilliseconds(asDouble * 1000);
+ return date * 1000;
+ }
+ return 0;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ PropertyListUtils.read(this._file, function migrateHistory(aDict) {
+ try {
+ if (!aDict)
+ throw new Error("Could not read history property list");
+ if (!aDict.has("WebHistoryDates"))
+ throw new Error("Unexpected history-property list format");
+
+ // Safari's History file contains only top-level urls. It does not
+ // distinguish between typed urls and linked urls.
+ let transType = PlacesUtils.history.TRANSITION_LINK;
+
+ let places = [];
+ let entries = aDict.get("WebHistoryDates");
+ for (let entry of entries) {
+ if (entry.has("lastVisitedDate")) {
+ let visitDate = this._parseCocoaDate(entry.get("lastVisitedDate"));
+ places.push({ uri: NetUtil.newURI(entry.get("")),
+ title: entry.get("title"),
+ visits: [{ transitionType: transType,
+ visitDate: visitDate }] });
+ }
+ }
+ if (places.length > 0) {
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+ else {
+ aCallback(false);
+ }
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ aCallback(false);
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * Safari's preferences property list is independently used for three purposes:
+ * (a) importation of preferences
+ * (b) importation of search strings
+ * (c) retrieving the home page.
+ *
+ * So, rather than reading it three times, it's cached and managed here.
+ */
+function MainPreferencesPropertyList(aPreferencesFile) {
+ this._file = aPreferencesFile;
+ this._callbacks = [];
+}
+MainPreferencesPropertyList.prototype = {
+ /**
+ * @see PropertyListUtils.read
+ */
+ read: function MPPL_read(aCallback) {
+ if ("_dict" in this) {
+ aCallback(this._dict);
+ return;
+ }
+
+ let alreadyReading = this._callbacks.length > 0;
+ this._callbacks.push(aCallback);
+ if (!alreadyReading) {
+ PropertyListUtils.read(this._file, function readPrefs(aDict) {
+ this._dict = aDict;
+ for (let callback of this._callbacks) {
+ try {
+ callback(aDict);
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ }
+ }
+ this._callbacks.splice(0);
+ }.bind(this));
+ }
+ },
+
+ // Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
+ // it's replaced with an async method.
+ _readSync: function MPPL__readSync() {
+ if ("_dict" in this)
+ return this._dict;
+
+ let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ inputStream.init(this._file, -1, -1, 0);
+ let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ binaryStream.setInputStream(inputStream);
+ let bytes = binaryStream.readByteArray(inputStream.available());
+ this._dict = PropertyListUtils._readFromArrayBufferSync(
+ Uint8Array(bytes).buffer);
+ return this._dict;
+ }
+};
+
+function SearchStrings(aMainPreferencesPropertyListInstance) {
+ this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
+}
+SearchStrings.prototype = {
+ type: MigrationUtils.resourceTypes.OTHERDATA,
+
+ migrate: function SS_migrate(aCallback) {
+ this._mainPreferencesPropertyList.read(MigrationUtils.wrapMigrateFunction(
+ function migrateSearchStrings(aDict) {
+ if (!aDict)
+ throw new Error("Could not get preferences dictionary");
+
+ if (aDict.has("RecentSearchStrings")) {
+ let recentSearchStrings = aDict.get("RecentSearchStrings");
+ if (recentSearchStrings && recentSearchStrings.length > 0) {
+ let changes = [{op: "add",
+ fieldname: "searchbar-history",
+ value: searchString}
+ for (searchString of recentSearchStrings)];
+ FormHistory.update(changes);
+ }
+ }
+ }.bind(this), aCallback));
+ }
+};
+
+function SafariProfileMigrator() {
+}
+
+SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+SafariProfileMigrator.prototype.getResources = function SM_getResources() {
+ let profileDir =
+#ifdef XP_MACOSX
+ FileUtils.getDir("ULibDir", ["Safari"], false);
+#else
+ FileUtils.getDir("AppData", ["Apple Computer", "Safari"], false);
+#endif
+ if (!profileDir.exists())
+ return null;
+
+ let resources = [];
+ let pushProfileFileResource = function(aFileName, aConstructor) {
+ let file = profileDir.clone();
+ file.append(aFileName);
+ if (file.exists())
+ resources.push(new aConstructor(file));
+ };
+
+ pushProfileFileResource("History.plist", History);
+ pushProfileFileResource("Bookmarks.plist", Bookmarks);
+
+ // The Reading List feature was introduced at the same time in Windows and
+ // Mac versions of Safari. Not surprisingly, they are stored in the same
+ // format in both versions. Surpsingly, only on Windows there is a
+ // separate property list for it. This isn't #ifdefed out on mac, because
+ // Apple may fix this at some point.
+ pushProfileFileResource("ReadingList.plist", Bookmarks);
+
+ let prefsDir =
+#ifdef XP_MACOSX
+ FileUtils.getDir("UsrPrfs", [], false);
+#else
+ FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
+#endif
+
+ let prefs = this.mainPreferencesPropertyList;
+ if (prefs) {
+ resources.push(new SearchStrings(prefs));
+ }
+
+ return resources;
+};
+
+Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
+ get: function get_mainPreferencesPropertyList() {
+ if (this._mainPreferencesPropertyList === undefined) {
+ let file =
+#ifdef XP_MACOSX
+ FileUtils.getDir("UsrPrfs", [], false);
+#else
+ FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
+#endif
+ if (file.exists()) {
+ file.append("com.apple.Safari.plist");
+ if (file.exists()) {
+ return this._mainPreferencesPropertyList =
+ new MainPreferencesPropertyList(file);
+ }
+ }
+ return this._mainPreferencesPropertyList = null;
+ }
+ return this._mainPreferencesPropertyList;
+ }
+});
+
+Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function get_sourceHomePageURL() {
+ if (this.mainPreferencesPropertyList) {
+ let dict = this.mainPreferencesPropertyList._readSync();
+ if (dict.has("HomePage"))
+ return dict.get("HomePage");
+ }
+ return "";
+ }
+});
+
+SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
+SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";
+SafariProfileMigrator.prototype.classID = Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SafariProfileMigrator]);
diff --git a/application/palemoon/components/migration/content/migration.js b/application/palemoon/components/migration/content/migration.js
new file mode 100644
index 000000000..512197b8b
--- /dev/null
+++ b/application/palemoon/components/migration/content/migration.js
@@ -0,0 +1,474 @@
+/* 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 Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const kIMig = Ci.nsIBrowserProfileMigrator;
+const kIPStartup = Ci.nsIProfileStartup;
+
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+var MigrationWizard = {
+ _source: "", // Source Profile Migrator ContractID suffix
+ _itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield)
+ _selectedProfile: null, // Selected Profile name to import from
+ _wiz: null,
+ _migrator: null,
+ _autoMigrate: null,
+
+ init: function ()
+ {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(this, "Migration:Started", false);
+ os.addObserver(this, "Migration:ItemBeforeMigrate", false);
+ os.addObserver(this, "Migration:ItemAfterMigrate", false);
+ os.addObserver(this, "Migration:ItemError", false);
+ os.addObserver(this, "Migration:Ended", false);
+
+ this._wiz = document.documentElement;
+
+ if ("arguments" in window && window.arguments.length > 1) {
+ this._source = window.arguments[0];
+ this._migrator = window.arguments[1] instanceof kIMig ?
+ window.arguments[1] : null;
+ this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup);
+ this._skipImportSourcePage = window.arguments[3];
+ if (this._migrator && window.arguments[4]) {
+ let sourceProfiles = this._migrator.sourceProfiles;
+ this._selectedProfile = sourceProfiles.find(profile => profile.id == window.arguments[4]);
+ }
+
+ if (this._autoMigrate) {
+ // Show the "nothing" option in the automigrate case to provide an
+ // easily identifiable way to avoid migration and create a new profile.
+ var nothing = document.getElementById("nothing");
+ nothing.hidden = false;
+ }
+ }
+
+ this.onImportSourcePageShow();
+ },
+
+ uninit: function ()
+ {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "Migration:Started");
+ os.removeObserver(this, "Migration:ItemBeforeMigrate");
+ os.removeObserver(this, "Migration:ItemAfterMigrate");
+ os.removeObserver(this, "Migration:ItemError");
+ os.removeObserver(this, "Migration:Ended");
+ MigrationUtils.finishMigration();
+ },
+
+ // 1 - Import Source
+ onImportSourcePageShow: function ()
+ {
+ this._wiz.canRewind = false;
+
+ var selectedMigrator = null;
+
+ // 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 migratorKey = group.childNodes[i].id;
+ if (migratorKey != "nothing") {
+ 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 == migratorKey)
+ selectedMigrator = group.childNodes[i];
+ } else {
+ // Hide this option
+ group.childNodes[i].hidden = true;
+ }
+ }
+ }
+
+ if (selectedMigrator)
+ group.selectedItem = selectedMigrator;
+ else {
+ // We didn't find a migrator, notify the user
+ document.getElementById("noSources").hidden = false;
+
+ this._wiz.canAdvance = false;
+
+ document.getElementById("importBookmarks").hidden = true;
+ document.getElementById("importAll").hidden = true;
+ }
+
+ // Advance to the next page if the caller told us to.
+ if (this._migrator && this._skipImportSourcePage) {
+ this._wiz.advance();
+ this._wiz.canRewind = false;
+ }
+ },
+
+ onImportSourcePageAdvanced: function ()
+ {
+ var newSource = document.getElementById("importSourceGroup").selectedItem.id;
+
+ if (newSource == "nothing") {
+ document.documentElement.cancel();
+ return false;
+ }
+
+ if (!this._migrator || (newSource != this._source)) {
+ // Create the migrator for the selected source.
+ this._migrator = MigrationUtils.getMigrator(newSource);
+
+ this._itemsFlags = kIMig.ALL;
+ this._selectedProfile = null;
+ }
+ this._source = newSource;
+
+ // check for more than one source profile
+ var sourceProfiles = this._migrator.sourceProfiles;
+ if (this._skipImportSourcePage) {
+ this._wiz.currentPage.next = "homePageImport";
+ }
+ else if (sourceProfiles && sourceProfiles.length > 1) {
+ this._wiz.currentPage.next = "selectProfile";
+ }
+ else {
+ if (this._autoMigrate)
+ this._wiz.currentPage.next = "homePageImport";
+ else
+ this._wiz.currentPage.next = "importItems";
+
+ if (sourceProfiles && sourceProfiles.length == 1)
+ this._selectedProfile = sourceProfiles[0];
+ else
+ this._selectedProfile = null;
+ }
+ },
+
+ // 2 - [Profile Selection]
+ onSelectProfilePageShow: function ()
+ {
+ // Disabling this for now, since we ask about import sources in automigration
+ // too and don't want to disable the back button
+ // if (this._autoMigrate)
+ // document.documentElement.getButton("back").disabled = true;
+
+ var profiles = document.getElementById("profiles");
+ while (profiles.hasChildNodes())
+ profiles.removeChild(profiles.firstChild);
+
+ // Note that this block is still reached even if the user chose 'From File'
+ // and we canceled the dialog. When that happens, _migrator will be null.
+ if (this._migrator) {
+ var sourceProfiles = this._migrator.sourceProfiles;
+
+ for (let profile of sourceProfiles) {
+ var item = document.createElement("radio");
+ item.id = profile.id;
+ item.setAttribute("label", profile.name);
+ profiles.appendChild(item);
+ }
+ }
+
+ profiles.selectedItem = this._selectedProfile ? document.getElementById(this._selectedProfile.id) : profiles.firstChild;
+ },
+
+ onSelectProfilePageRewound: function ()
+ {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = this._migrator.sourceProfiles.find(
+ profile => profile.id == profiles.selectedItem.id
+ ) || null;
+ },
+
+ onSelectProfilePageAdvanced: function ()
+ {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = this._migrator.sourceProfiles.find(
+ profile => profile.id == profiles.selectedItem.id
+ ) || null;
+
+ // If we're automigrating or just doing bookmarks don't show the item selection page
+ if (this._autoMigrate)
+ this._wiz.currentPage.next = "homePageImport";
+ },
+
+ // 3 - ImportItems
+ onImportItemsPageShow: function ()
+ {
+ var dataSources = document.getElementById("dataSources");
+ while (dataSources.hasChildNodes())
+ dataSources.removeChild(dataSources.firstChild);
+
+ 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",
+ MigrationUtils.getLocalizedString(itemID + "_" + this._source));
+ dataSources.appendChild(checkbox);
+ if (!this._itemsFlags || this._itemsFlags & itemID)
+ checkbox.checked = true;
+ }
+ }
+ },
+
+ onImportItemsPageRewound: function ()
+ {
+ this._wiz.canAdvance = true;
+ this.onImportItemsPageAdvanced();
+ },
+
+ onImportItemsPageAdvanced: function ()
+ {
+ var dataSources = document.getElementById("dataSources");
+ this._itemsFlags = 0;
+ for (var i = 0; i < dataSources.childNodes.length; ++i) {
+ var checkbox = dataSources.childNodes[i];
+ if (checkbox.localName == "checkbox" && checkbox.checked)
+ this._itemsFlags |= parseInt(checkbox.id);
+ }
+ },
+
+ onImportItemCommand: function (aEvent)
+ {
+ var items = document.getElementById("dataSources");
+ var checkboxes = items.getElementsByTagName("checkbox");
+
+ var oneChecked = false;
+ for (var i = 0; i < checkboxes.length; ++i) {
+ if (checkboxes[i].checked) {
+ oneChecked = true;
+ break;
+ }
+ }
+
+ this._wiz.canAdvance = oneChecked;
+ },
+
+ // 4 - Home Page Selection
+ onHomePageMigrationPageShow: function ()
+ {
+ // only want this on the first run
+ if (!this._autoMigrate) {
+ this._wiz.advance();
+ return;
+ }
+
+ 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 = brandBundle.getString("homePageMigrationPageTitle");
+ var pageDesc = brandBundle.getString("homePageMigrationDescription");
+ var mainStr = brandBundle.getString("homePageSingleStartMain");
+ }
+ catch (e) {
+ this._wiz.advance();
+ return;
+ }
+
+ document.getElementById("homePageImport").setAttribute("label", pageTitle);
+ document.getElementById("homePageImportDesc").setAttribute("value", pageDesc);
+
+ this._wiz._adjustWizardHeader();
+
+ var singleStart = document.getElementById("homePageSingleStart");
+ singleStart.setAttribute("label", mainStr);
+ singleStart.setAttribute("value", "DEFAULT");
+
+ var source = null;
+ switch (this._source) {
+ case "ie":
+ source = "sourceNameIE";
+ break;
+ case "safari":
+ source = "sourceNameSafari";
+ break;
+ case "chrome":
+ source = "sourceNameChrome";
+ break;
+ case "firefox":
+ source = "sourceNameFirefox";
+ break;
+ }
+
+ // semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
+ this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
+
+ var oldHomePageURL = this._migrator.sourceHomePageURL;
+
+ if (oldHomePageURL && source) {
+ var appName = MigrationUtils.getLocalizedString(source);
+ var oldHomePageLabel =
+ brandBundle.getFormattedString("homePageImport", [appName]);
+ var oldHomePage = document.getElementById("oldHomePage");
+ oldHomePage.setAttribute("label", oldHomePageLabel);
+ oldHomePage.setAttribute("value", oldHomePageURL);
+ oldHomePage.removeAttribute("hidden");
+ }
+ else {
+ // if we don't have at least two options, just advance
+ this._wiz.advance();
+ }
+ },
+
+ onHomePageMigrationPageAdvanced: function ()
+ {
+ // we might not have a selectedItem if we're in fallback mode
+ try {
+ var radioGroup = document.getElementById("homePageRadiogroup");
+
+ this._newHomePage = radioGroup.selectedItem.value;
+ } catch(ex) {}
+ },
+
+ // 5 - Migrating
+ onMigratingPageShow: function ()
+ {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._wiz.canAdvance = false;
+
+ // When automigrating, show all of the data that can be received from this source.
+ if (this._autoMigrate)
+ this._itemsFlags = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
+
+ this._listItems("migratingItems");
+ setTimeout(this.onMigratingMigrate, 0, this);
+ },
+
+ onMigratingMigrate: function (aOuter)
+ {
+ aOuter._migrator.migrate(aOuter._itemsFlags, aOuter._autoMigrate, aOuter._selectedProfile);
+ },
+
+ _listItems: function (aID)
+ {
+ var items = document.getElementById(aID);
+ while (items.hasChildNodes())
+ items.removeChild(items.firstChild);
+
+ var brandBundle = document.getElementById("brandBundle");
+ var itemID;
+ for (var i = 0; i < 16; ++i) {
+ var itemID = (this._itemsFlags >> i) & 0x1 ? Math.pow(2, i) : 0;
+ if (itemID > 0) {
+ var label = document.createElement("label");
+ label.id = itemID + "_migrated";
+ try {
+ label.setAttribute("value",
+ MigrationUtils.getLocalizedString(itemID + "_" + this._source));
+ items.appendChild(label);
+ }
+ catch (e) {
+ // if the block above throws, we've enumerated all the import data types we
+ // currently support and are now just wasting time, break.
+ break;
+ }
+ }
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ switch (aTopic) {
+ case "Migration:Started":
+ break;
+ case "Migration:ItemBeforeMigrate":
+ var label = document.getElementById(aData + "_migrated");
+ if (label)
+ label.setAttribute("style", "font-weight: bold");
+ break;
+ case "Migration:ItemAfterMigrate":
+ var label = document.getElementById(aData + "_migrated");
+ if (label)
+ label.removeAttribute("style");
+ break;
+ case "Migration:Ended":
+ if (this._autoMigrate) {
+ if (this._newHomePage) {
+ try {
+ // set homepage properly
+ var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ var prefBranch = prefSvc.getBranch(null);
+
+ if (this._newHomePage == "DEFAULT") {
+ prefBranch.clearUserPref("browser.startup.homepage");
+ }
+ else {
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = this._newHomePage;
+ prefBranch.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString,
+ str);
+ }
+
+ var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ var prefFile = dirSvc.get("ProfDS", Components.interfaces.nsIFile);
+ prefFile.append("prefs.js");
+ prefSvc.savePrefFile(prefFile);
+ } catch(ex) {
+ dump(ex);
+ }
+ }
+
+ // We're done now.
+ this._wiz.canAdvance = true;
+ this._wiz.advance();
+
+ setTimeout(close, 5000);
+ }
+ else {
+ this._wiz.canAdvance = true;
+ var nextButton = this._wiz.getButton("next");
+ nextButton.click();
+ }
+ break;
+ case "Migration:ItemError":
+ var type = "undefined";
+ switch (parseInt(aData)) {
+ case Ci.nsIBrowserProfileMigrator.SETTINGS:
+ type = "settings";
+ break;
+ case Ci.nsIBrowserProfileMigrator.COOKIES:
+ type = "cookies";
+ break;
+ case Ci.nsIBrowserProfileMigrator.HISTORY:
+ type = "history";
+ break;
+ case Ci.nsIBrowserProfileMigrator.FORMDATA:
+ type = "form data";
+ break;
+ case Ci.nsIBrowserProfileMigrator.PASSWORDS:
+ type = "passwords";
+ break;
+ case Ci.nsIBrowserProfileMigrator.BOOKMARKS:
+ type = "bookmarks";
+ break;
+ case Ci.nsIBrowserProfileMigrator.OTHERDATA:
+ type = "misc. data";
+ break;
+ }
+ Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService)
+ .logStringMessage("some " + type + " did not successfully migrate.");
+ break;
+ }
+ },
+
+ onDonePageShow: function ()
+ {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._listItems("doneItems");
+ }
+};
diff --git a/application/palemoon/components/migration/content/migration.xul b/application/palemoon/components/migration/content/migration.xul
new file mode 100644
index 000000000..2c8df7b3e
--- /dev/null
+++ b/application/palemoon/components/migration/content/migration.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+# 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/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/migration/migration.dtd" >
+
+<wizard id="migrationWizard"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:MigrationWizard"
+ title="&migrationWizard.title;"
+ onload="MigrationWizard.init()"
+ onunload="MigrationWizard.uninit()"
+ style="width: 40em;"
+ buttons="accept,cancel"
+ branded="true">
+
+ <script type="application/javascript" src="chrome://browser/content/migration/migration.js"/>
+
+ <stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
+
+ <wizardpage id="importSource" pageid="importSource" next="selectProfile"
+ label="&importSource.title;"
+ onpageadvanced="return MigrationWizard.onImportSourcePageAdvanced();">
+ <description id="importAll" control="importSourceGroup">&importFrom.label;</description>
+ <description id="importBookmarks" control="importSourceGroup" hidden="true">&importFromBookmarks.label;</description>
+
+ <radiogroup id="importSourceGroup" align="start">
+ <radio id="firefox" label="&importFromFirefox.label;" accesskey="&importFromFirefox.accesskey;"/>
+#ifdef XP_WIN
+ <radio id="ie" label="&importFromIE.label;" accesskey="&importFromIE.accesskey;"/>
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+ <radio id="safari" label="&importFromSafari.label;" accesskey="&importFromSafari.accesskey;"/>
+#elifdef XP_MACOSX
+ <radio id="safari" label="&importFromSafari.label;" accesskey="&importFromSafari.accesskey;"/>
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+#elifdef XP_UNIX
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+#endif
+ <radio id="nothing" label="&importFromNothing.label;" accesskey="&importFromNothing.accesskey;" hidden="true"/>
+ </radiogroup>
+ <label id="noSources" hidden="true">&noMigrationSources.label;</label>
+ </wizardpage>
+
+ <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;"
+ next="importItems"
+ onpageshow="return MigrationWizard.onSelectProfilePageShow();"
+ onpagerewound="return MigrationWizard.onSelectProfilePageRewound();"
+ onpageadvanced="return MigrationWizard.onSelectProfilePageAdvanced();">
+ <description control="profiles">&selectProfile.label;</description>
+
+ <radiogroup id="profiles" align="left"/>
+ </wizardpage>
+
+ <wizardpage id="importItems" pageid="importItems" label="&importItems.title;"
+ next="homePageImport"
+ onpageshow="return MigrationWizard.onImportItemsPageShow();"
+ onpagerewound="return MigrationWizard.onImportItemsPageRewound();"
+ onpageadvanced="return MigrationWizard.onImportItemsPageAdvanced();"
+ oncommand="MigrationWizard.onImportItemCommand();">
+ <description control="dataSources">&importItems.label;</description>
+
+ <vbox id="dataSources" style="overflow: auto; -moz-appearance: listbox" align="left" flex="1" role="group"/>
+ </wizardpage>
+
+ <wizardpage id="homePageImport" pageid="homePageImport"
+ next="migrating"
+ onpageshow="return MigrationWizard.onHomePageMigrationPageShow();"
+ onpageadvanced="return MigrationWizard.onHomePageMigrationPageAdvanced();">
+
+ <description id="homePageImportDesc" control="homePageRadioGroup"/>
+ <radiogroup id="homePageRadiogroup">
+ <radio id="homePageSingleStart" selected="true" />
+ <radio id="oldHomePage" hidden="true" />
+ </radiogroup>
+ </wizardpage>
+
+ <wizardpage id="migrating" pageid="migrating" label="&migrating.title;"
+ next="done"
+ onpageshow="MigrationWizard.onMigratingPageShow();">
+ <description control="migratingItems">&migrating.label;</description>
+
+ <vbox id="migratingItems" style="overflow: auto;" align="left" role="group"/>
+ </wizardpage>
+
+ <wizardpage id="done" pageid="done" label="&done.title;"
+ onpageshow="MigrationWizard.onDonePageShow();">
+ <description control="doneItems">&done.label;</description>
+
+ <vbox id="doneItems" style="overflow: auto;" align="left" role="group"/>
+ </wizardpage>
+
+</wizard>
+
diff --git a/application/palemoon/components/migration/jar.mn b/application/palemoon/components/migration/jar.mn
new file mode 100644
index 000000000..b369ee1dc
--- /dev/null
+++ b/application/palemoon/components/migration/jar.mn
@@ -0,0 +1,7 @@
+# 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/.
+
+browser.jar:
+* content/browser/migration/migration.xul (content/migration.xul)
+ content/browser/migration/migration.js (content/migration.js)
diff --git a/application/palemoon/components/migration/moz.build b/application/palemoon/components/migration/moz.build
new file mode 100644
index 000000000..4cbf7b8ea
--- /dev/null
+++ b/application/palemoon/components/migration/moz.build
@@ -0,0 +1,53 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsIBrowserProfileMigrator.idl',
+]
+
+XPIDL_MODULE = 'migration'
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += [
+ 'nsIEHistoryEnumerator.cpp',
+ ]
+
+EXTRA_COMPONENTS += [
+ 'FirefoxProfileMigrator.js',
+ 'ProfileMigrator.js',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ EXTRA_COMPONENTS += [
+ 'IEProfileMigrator.js',
+ ]
+ DEFINES['HAS_IE_MIGRATOR'] = True
+
+EXTRA_PP_COMPONENTS += [
+ 'BrowserProfileMigrators.manifest',
+ 'ChromeProfileMigrator.js',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ EXTRA_PP_COMPONENTS += [
+ 'SafariProfileMigrator.js',
+ ]
+ DEFINES['HAS_SAFARI_MIGRATOR'] = True
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXTRA_PP_COMPONENTS += [
+ 'SafariProfileMigrator.js',
+ ]
+ DEFINES['HAS_SAFARI_MIGRATOR'] = True
+
+EXTRA_PP_JS_MODULES += [
+ 'MigrationUtils.jsm',
+]
+
+FINAL_LIBRARY = 'browsercomps'
+
diff --git a/application/palemoon/components/migration/nsIBrowserProfileMigrator.idl b/application/palemoon/components/migration/nsIBrowserProfileMigrator.idl
new file mode 100644
index 000000000..ebff4628c
--- /dev/null
+++ b/application/palemoon/components/migration/nsIBrowserProfileMigrator.idl
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIProfileStartup;
+
+[scriptable, uuid(30e5a7ec-f71e-4f41-9dbd-7429c02132ec)]
+interface nsIBrowserProfileMigrator : nsISupports
+{
+ /**
+ * profile items to migrate. use with migrate().
+ */
+ const unsigned short ALL = 0x0000;
+ const unsigned short SETTINGS = 0x0001;
+ const unsigned short COOKIES = 0x0002;
+ const unsigned short HISTORY = 0x0004;
+ const unsigned short FORMDATA = 0x0008;
+ const unsigned short PASSWORDS = 0x0010;
+ const unsigned short BOOKMARKS = 0x0020;
+ const unsigned short OTHERDATA = 0x0040;
+
+ /**
+ * Copy user profile information to the current active profile.
+ * @param aItems list of data items to migrate. see above for values.
+ * @param aStartup helper interface which is non-null if called during startup.
+ * @param aProfile profile to migrate from, if there is more than one.
+ */
+ void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in jsval aProfile);
+
+ /**
+ * A bit field containing profile items that this migrator
+ * offers for import.
+ * @param aProfile the profile that we are looking for available data
+ * to import
+ * @param aDoingStartup "true" if the profile is not currently being used.
+ * @return bit field containing profile items (see above)
+ * @note a return value of 0 represents no items rather than ALL.
+ */
+ unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
+
+ /**
+ * Whether or not there is any data that can be imported from this
+ * browser (i.e. whether or not it is installed, and there exists
+ * a user profile)
+ */
+ readonly attribute boolean sourceExists;
+
+
+ /**
+ * An enumeration of available profiles. If the import source does
+ * not support profiles, this attribute is null.
+ */
+ readonly attribute jsval sourceProfiles;
+
+ /**
+ * The import source homepage. Returns null if not present/available
+ */
+ readonly attribute AUTF8String sourceHomePageURL;
+};
diff --git a/application/palemoon/components/migration/nsIEHistoryEnumerator.cpp b/application/palemoon/components/migration/nsIEHistoryEnumerator.cpp
new file mode 100644
index 000000000..7fe31a666
--- /dev/null
+++ b/application/palemoon/components/migration/nsIEHistoryEnumerator.cpp
@@ -0,0 +1,139 @@
+/* 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/. */
+
+#include "nsIEHistoryEnumerator.h"
+
+#include <urlhist.h>
+#include <shlguid.h>
+
+#include "nsStringAPI.h"
+#include "nsNetUtil.h"
+#include "nsIVariant.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+
+namespace {
+
+ PRTime FileTimeToPRTime(FILETIME* filetime)
+ {
+ SYSTEMTIME st;
+ ::FileTimeToSystemTime(filetime, &st);
+ PRExplodedTime prt;
+ prt.tm_year = st.wYear;
+ // SYSTEMTIME's day-of-month parameter is 1-based,
+ // PRExplodedTime's is 0-based.
+ prt.tm_month = st.wMonth - 1;
+ prt.tm_mday = st.wDay;
+ prt.tm_hour = st.wHour;
+ prt.tm_min = st.wMinute;
+ prt.tm_sec = st.wSecond;
+ prt.tm_usec = st.wMilliseconds * 1000;
+ prt.tm_wday = 0;
+ prt.tm_yday = 0;
+ prt.tm_params.tp_gmt_offset = 0;
+ prt.tm_params.tp_dst_offset = 0;
+ return PR_ImplodeTime(&prt);
+ }
+
+} // Anonymous namespace.
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIEHistoryEnumerator
+
+NS_IMPL_ISUPPORTS(nsIEHistoryEnumerator, nsISimpleEnumerator)
+
+nsIEHistoryEnumerator::nsIEHistoryEnumerator()
+{
+ ::CoInitialize(nullptr);
+}
+
+nsIEHistoryEnumerator::~nsIEHistoryEnumerator()
+{
+ ::CoUninitialize();
+}
+
+void
+nsIEHistoryEnumerator::EnsureInitialized()
+{
+ if (mURLEnumerator)
+ return;
+
+ HRESULT hr = ::CoCreateInstance(CLSID_CUrlHistory,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IUrlHistoryStg2,
+ getter_AddRefs(mIEHistory));
+ if (FAILED(hr))
+ return;
+
+ hr = mIEHistory->EnumUrls(getter_AddRefs(mURLEnumerator));
+ if (FAILED(hr))
+ return;
+}
+
+NS_IMETHODIMP
+nsIEHistoryEnumerator::HasMoreElements(bool* _retval)
+{
+ *_retval = false;
+
+ EnsureInitialized();
+ MOZ_ASSERT(mURLEnumerator, "Should have instanced an IE History URLEnumerator");
+ if (!mURLEnumerator)
+ return NS_OK;
+
+ STATURL statURL;
+ ULONG fetched;
+
+ // First argument is not implemented, so doesn't matter what we pass.
+ HRESULT hr = mURLEnumerator->Next(1, &statURL, &fetched);
+ if (FAILED(hr) || fetched != 1UL) {
+ // Reached the last entry.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (statURL.pwcsUrl) {
+ nsDependentString url(statURL.pwcsUrl);
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ ::CoTaskMemFree(statURL.pwcsUrl);
+ if (NS_FAILED(rv)) {
+ // Got a corrupt or invalid URI, continue to the next entry.
+ return HasMoreElements(_retval);
+ }
+ }
+
+ nsDependentString title(statURL.pwcsTitle);
+
+ PRTime lastVisited = FileTimeToPRTime(&(statURL.ftLastVisited));
+
+ mCachedNextEntry = do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ MOZ_ASSERT(mCachedNextEntry, "Should have instanced a new property bag");
+ if (mCachedNextEntry) {
+ mCachedNextEntry->SetPropertyAsInterface(NS_LITERAL_STRING("uri"), uri);
+ mCachedNextEntry->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
+ mCachedNextEntry->SetPropertyAsInt64(NS_LITERAL_STRING("time"), lastVisited);
+
+ *_retval = true;
+ }
+
+ if (statURL.pwcsTitle)
+ ::CoTaskMemFree(statURL.pwcsTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIEHistoryEnumerator::GetNext(nsISupports** _retval)
+{
+ *_retval = nullptr;
+
+ if (!mCachedNextEntry)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*_retval = mCachedNextEntry);
+ // Release the cached entry, so it can't be returned twice.
+ mCachedNextEntry = nullptr;
+
+ return NS_OK;
+}
diff --git a/application/palemoon/components/migration/nsIEHistoryEnumerator.h b/application/palemoon/components/migration/nsIEHistoryEnumerator.h
new file mode 100644
index 000000000..fc1419859
--- /dev/null
+++ b/application/palemoon/components/migration/nsIEHistoryEnumerator.h
@@ -0,0 +1,37 @@
+/* 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/. */
+
+#ifndef iehistoryenumerator___h___
+#define iehistoryenumerator___h___
+
+#include <urlhist.h>
+
+#include "mozilla/Attributes.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsAutoPtr.h"
+
+class nsIEHistoryEnumerator final : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ nsIEHistoryEnumerator();
+
+private:
+ ~nsIEHistoryEnumerator();
+
+ /**
+ * Initializes the history reader, if needed.
+ */
+ void EnsureInitialized();
+
+ nsRefPtr<IUrlHistoryStg2> mIEHistory;
+ nsRefPtr<IEnumSTATURL> mURLEnumerator;
+
+ nsCOMPtr<nsIWritablePropertyBag2> mCachedNextEntry;
+};
+
+#endif