summaryrefslogtreecommitdiffstats
path: root/application/palemoon/components/migration/SafariProfileMigrator.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/components/migration/SafariProfileMigrator.js')
-rw-r--r--application/palemoon/components/migration/SafariProfileMigrator.js416
1 files changed, 416 insertions, 0 deletions
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]);