diff options
Diffstat (limited to 'application/palemoon/components/migration/SafariProfileMigrator.js')
-rw-r--r-- | application/palemoon/components/migration/SafariProfileMigrator.js | 416 |
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]); |