diff --git a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp index eb80fe26978..869970c347f 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp @@ -163,3 +163,20 @@ GetProfilePath(nsIProfileStartup* aStartup, nsCOMPtr& aProfileDir) } } +nsresult +ImportDefaultBookmarks() +{ + nsCOMPtr importer = + do_GetService(NS_PLACESIMPORTEXPORTSERVICE_CONTRACTID); + NS_ENSURE_STATE(importer); + + nsCOMPtr ioService = mozilla::services::GetIOService(); + NS_ENSURE_STATE(ioService); + nsCOMPtr bookmarksURI; + nsresult rv = ioService->NewURI(DEFAULT_BOOKMARKS, nsnull, nsnull, + getter_AddRefs(bookmarksURI)); + if (NS_FAILED(rv)) + return rv; + + return importer->ImportHTMLFromURI(bookmarksURI, true); +} diff --git a/browser/components/migration/src/nsBrowserProfileMigratorUtils.h b/browser/components/migration/src/nsBrowserProfileMigratorUtils.h index a1e00a797a5..26c9fad4554 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.h +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.h @@ -99,5 +99,10 @@ void GetMigrateDataFromArray(MigrationData* aDataArray, // this is already cloned, modify it to your heart's content void GetProfilePath(nsIProfileStartup* aStartup, nsCOMPtr& aProfileDir); +/** + * Imports default bookmarks to the profile. + */ +nsresult ImportDefaultBookmarks(); + #endif diff --git a/browser/components/migration/src/nsIEProfileMigrator.cpp b/browser/components/migration/src/nsIEProfileMigrator.cpp index 8ce5d0ef7a8..e153e65f82c 100644 --- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -1398,6 +1398,11 @@ nsIEProfileMigrator::CopyFavoritesBatched(bool aReplace) NS_ENSURE_SUCCESS(rv, rv); } else { + // If importing defaults fails for whatever reason, let the import process + // continue. + DebugOnly rv = ImportDefaultBookmarks(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to import default bookmarks"); + // Locate the Links toolbar folder, we want to replace the Personal Toolbar // content with Favorites in this folder. // On versions minor or equal to IE6 the folder name is stored in the diff --git a/browser/components/migration/src/nsSafariProfileMigrator.cpp b/browser/components/migration/src/nsSafariProfileMigrator.cpp index 05feb249df3..dcfe9e0c8c2 100644 --- a/browser/components/migration/src/nsSafariProfileMigrator.cpp +++ b/browser/components/migration/src/nsSafariProfileMigrator.cpp @@ -974,6 +974,11 @@ nsSafariProfileMigrator::CopyBookmarksBatched(bool aReplace) NS_ENSURE_SUCCESS(rv, rv); } else { + // If importing defaults fails for whatever reason, let the import process + // continue. + DebugOnly rv = ImportDefaultBookmarks(); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to import default bookmarks"); + // In replace mode we are merging at the top level. folder = bookmarksMenuFolderId; } diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 9f99c6556af..8e3e72c6364 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -62,9 +62,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils", - "resource://gre/modules/BookmarkHTMLUtils.jsm"); - XPCOMUtils.defineLazyModuleGetter(this, "KeywordURLResetPrompter", "resource:///modules/KeywordURLResetPrompter.jsm"); @@ -231,6 +228,10 @@ BrowserGlue.prototype = { // no longer needed, since history was initialized completely. Services.obs.removeObserver(this, "places-database-locked"); this._isPlacesLockedObserver = false; + + // Now apply distribution customized bookmarks. + // This should always run after Places initialization. + this._distributionCustomizer.applyBookmarks(); break; case "places-database-locked": this._isPlacesDatabaseLocked = true; @@ -256,6 +257,13 @@ BrowserGlue.prototype = { // Customization has finished, we don't need the customizer anymore. delete this._distributionCustomizer; break; + case "bookmarks-restore-success": + case "bookmarks-restore-failed": + Services.obs.removeObserver(this, "bookmarks-restore-success"); + Services.obs.removeObserver(this, "bookmarks-restore-failed"); + if (topic == "bookmarks-restore-success" && data == "html-initial") + this.ensurePlacesDefaultQueriesInitialized(); + break; case "browser-glue-test": // used by tests if (data == "post-update-notification") { if (Services.prefs.prefHasUserValue("app.update.postupdate")) @@ -280,9 +288,6 @@ BrowserGlue.prototype = { keywordURI); } break; - case "initial-migration": - this._initialMigrationPerformed = true; - break; } }, @@ -985,9 +990,18 @@ BrowserGlue.prototype = { // If the database is corrupt or has been newly created we should // import bookmarks. var dbStatus = PlacesUtils.history.databaseStatus; - var importBookmarks = !this._initialMigrationPerformed && - (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE || - dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT); + var importBookmarks = dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE || + dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT; + + if (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE) { + // If the database has just been created, but we already have any + // bookmark, this is not the initial import. This can happen after a + // migration from a different browser since migrators run before us. + // In such a case we should not import, unless some pref has been set. + if (PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarksMenuFolderId, 0) != -1 || + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0) != -1) + importBookmarks = false; + } // Check if user or an extension has required to import bookmarks.html var importBookmarksHTML = false; @@ -1044,9 +1058,6 @@ BrowserGlue.prototype = { // delayed till the import operations has finished. Not doing so would // cause them to be overwritten by the newly imported bookmarks. if (!importBookmarks) { - // Now apply distribution customized bookmarks. - // This should always run after Places initialization. - this._distributionCustomizer.applyBookmarks(); this.ensurePlacesDefaultQueriesInitialized(); } else { @@ -1080,28 +1091,25 @@ BrowserGlue.prototype = { } if (bookmarksURI) { + // Add an import observer. It will ensure that smart bookmarks are + // created once the operation is complete. + Services.obs.addObserver(this, "bookmarks-restore-success", false); + Services.obs.addObserver(this, "bookmarks-restore-failed", false); + // Import from bookmarks.html file. try { - BookmarkHTMLUtils.importFromURL(bookmarksURI.spec, true, (function (success) { - if (success) { - // Now apply distribution customized bookmarks. - // This should always run after Places initialization. - this._distributionCustomizer.applyBookmarks(); - // Ensure that smart bookmarks are created once the operation is - // complete. - this.ensurePlacesDefaultQueriesInitialized(); - } - else { - Cu.reportError("Bookmarks.html file could be corrupt."); - } - }).bind(this)); + var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. + getService(Ci.nsIPlacesImportExportService); + importer.importHTMLFromURI(bookmarksURI, true /* overwrite existing */); } catch (err) { + // Report the error, but ignore it. Cu.reportError("Bookmarks.html file could be corrupt. " + err); + Services.obs.removeObserver(this, "bookmarks-restore-success"); + Services.obs.removeObserver(this, "bookmarks-restore-failed"); } } - else { + else Cu.reportError("Unable to find bookmarks.html file."); - } // Reset preferences, so we won't try to import again at next run if (importBookmarksHTML) diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js index 40e7cfa1d26..ce30f248aa0 100644 --- a/browser/components/places/content/places.js +++ b/browser/components/places/content/places.js @@ -400,9 +400,10 @@ var PlacesOrganizer = { Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterHTML); if (fp.show() != Ci.nsIFilePicker.returnCancel) { - if (fp.fileURL) { - Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false); + if (fp.file) { + var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. + getService(Ci.nsIPlacesImportExportService); + importer.importHTMLFromFile(fp.file, false); } } }, diff --git a/browser/components/places/tests/unit/test_384370.js b/browser/components/places/tests/unit/test_384370.js index a7a68708c51..adb7c4ae34c 100644 --- a/browser/components/places/tests/unit/test_384370.js +++ b/browser/components/places/tests/unit/test_384370.js @@ -57,8 +57,8 @@ function run_test() { - export as json, import, test */ - // import the importer - Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); + // get places import/export service + var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].getService(Ci.nsIPlacesImportExportService); // avoid creating the places smart folder during tests Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch). @@ -83,14 +83,8 @@ function run_test() { // 2. run the test-suite // Note: we do not empty the db before this import to catch bugs like 380999 try { - BookmarkHTMLUtils.importFromFile(bookmarksFileOld, true, after_import); + importer.importHTMLFromFile(bookmarksFileOld, true); } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } -} - -function after_import(success) { - if (!success) { - do_throw("Couldn't import legacy bookmarks file."); - } populate(); validate(); @@ -101,8 +95,6 @@ function after_import(success) { // 3. import bookmarks.exported.json // 4. run the test-suite try { - var jsonFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); - jsonFile.append("bookmarks.exported.json"); PlacesUtils.backups.saveBookmarksToJSONFile(jsonFile); } catch(ex) { do_throw("couldn't export to file: " + ex); } LOG("exported json"); diff --git a/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js index 6e5c82d7100..f53451a2cdd 100644 --- a/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js +++ b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js @@ -55,7 +55,6 @@ var ps = Cc["@mozilla.org/preferences-service;1"]. getService(Ci.nsIPrefBranch); var ies = Cc["@mozilla.org/browser/places/import-export-service;1"]. getService(Ci.nsIPlacesImportExportService); -Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); const DESCRIPTION_ANNO = "bookmarkProperties/description"; const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; @@ -73,14 +72,8 @@ function run_test() { // import bookmarks from corrupt file var corruptBookmarksFile = do_get_file("bookmarks.corrupt.html"); try { - BookmarkHTMLUtils.importFromFile(corruptBookmarksFile, true, after_import); + ies.importHTMLFromFile(corruptBookmarksFile, true); } catch(ex) { do_throw("couldn't import corrupt bookmarks file: " + ex); } -} - -function after_import(success) { - if (!success) { - do_throw("Couldn't import corrupt bookmarks file."); - } // Check that every bookmark is correct // Corrupt bookmarks should not have been imported @@ -112,16 +105,14 @@ function after_import(success) { // Import bookmarks try { - BookmarkHTMLUtils.importFromFile(bookmarksFile, true, before_database_check); + ies.importHTMLFromFile(bookmarksFile, true); } catch(ex) { do_throw("couldn't import the exported file: " + ex); } - }); -} -function before_database_check(success) { // Check that every bookmark is correct database_check(); waitForAsyncUpdates(do_test_finished); + }); } /* diff --git a/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js b/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js index c463272d092..4b2a55fb4c2 100644 --- a/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js +++ b/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js @@ -36,8 +36,6 @@ * * ***** END LICENSE BLOCK ***** */ -Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - /** * Tests the bookmarks-restore-* nsIObserver notifications after restoring * bookmarks from JSON and HTML. See bug 470314. @@ -136,14 +134,10 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.html"); addBookmarks(); - exporter.exportHTMLToFile(this.file); + importer.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { - if (!success) { - do_throw(" Restore should not have failed"); - } - }); + importer.importHTMLFromFile(this.file, false); } catch (e) { do_throw(" Restore should not have failed"); @@ -160,11 +154,7 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { - if (!success) { - do_throw(" Restore should not have failed"); - } - }); + importer.importHTMLFromFile(this.file, false); } catch (e) { do_throw(" Restore should not have failed"); @@ -182,12 +172,8 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { - print("callback"); - if (success) { - do_throw(" Restore should have failed"); - } - }); + importer.importHTMLFromFile(this.file, false); + do_throw(" Restore should have failed"); } catch (e) {} } @@ -202,14 +188,10 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); addBookmarks(); - exporter.exportHTMLToFile(this.file); + importer.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { - if (!success) { - do_throw(" Restore should not have failed"); - } - }); + importer.importHTMLFromFile(this.file, true); } catch (e) { do_throw(" Restore should not have failed"); @@ -226,11 +208,7 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { - if (!success) { - do_throw(" Restore should not have failed"); - } - }); + importer.importHTMLFromFile(this.file, true); } catch (e) { do_throw(" Restore should not have failed"); @@ -248,15 +226,13 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { - if (success) { - do_throw(" Restore should have failed"); - } - }); + importer.importHTMLFromFile(this.file, true); + do_throw(" Restore should have failed"); } catch (e) {} } } + ]; // nsIObserver that observes bookmarks-restore-begin. @@ -305,7 +281,7 @@ var successAndFailedObserver = { do_check_eq(test.folderId, null); remove_all_bookmarks(); - do_execute_soon(doNextTest); + doNextTest(); } }; @@ -318,7 +294,7 @@ var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. var obssvc = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); -var exporter = Cc["@mozilla.org/browser/places/import-export-service;1"]. +var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. getService(Ci.nsIPlacesImportExportService); /////////////////////////////////////////////////////////////////////////////// diff --git a/browser/components/places/tests/unit/test_bookmarks_html.js b/browser/components/places/tests/unit/test_bookmarks_html.js index aa4772c7b73..4423de00051 100644 --- a/browser/components/places/tests/unit/test_bookmarks_html.js +++ b/browser/components/places/tests/unit/test_bookmarks_html.js @@ -99,11 +99,9 @@ let gBookmarksFileOld; // Places bookmarks.html file pointer. let gBookmarksFileNew; -let exporter = Cc["@mozilla.org/browser/places/import-export-service;1"]. +let importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. getService(Ci.nsIPlacesImportExportService); -Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - function run_test() { run_next_test(); @@ -133,26 +131,22 @@ add_test(function setup() { // 2. run the test-suite // Note: we do not empty the db before this import to catch bugs like 380999 try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileOld, true, function(success) { - if (success) { - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - // Prepare for next tests. - try { - exporter.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); - }); - }); - } else { - do_throw("couldn't import legacy bookmarks file."); - } - }); + importer.importHTMLFromFile(gBookmarksFileOld, true); } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } + + waitForAsyncUpdates(function () { + testImportedBookmarks(); + + // Prepare for next tests. + try { + importer.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); }); add_test(function test_import_new() @@ -162,21 +156,17 @@ add_test(function test_import_new() // 2. run the test-suite try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { - if (success) { - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); - }); - }); - } else { - do_throw("couldn't import the exported file."); - } - }); + importer.importHTMLFromFile(gBookmarksFileNew, true); } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + waitForAsyncUpdates(function () { + testImportedBookmarks(); + + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); }); add_test(function test_emptytitle_export() @@ -190,50 +180,42 @@ add_test(function test_emptytitle_export() // 5. run the test-suite try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { - if (success) { - const NOTITLE_URL = "http://notitle.mozilla.org/"; - let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI(NOTITLE_URL), - PlacesUtils.bookmarks.DEFAULT_INDEX, - ""); - test_bookmarks.unfiled.push({ title: "", url: NOTITLE_URL }); - - try { - exporter.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } - - remove_all_bookmarks(); - - try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { - if (success) { - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - // Cleanup. - test_bookmarks.unfiled.pop(); - PlacesUtils.bookmarks.removeItem(id); - - try { - exporter.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); - }); - }); - } else { - do_throw("couldn't import the exported file."); - } - }); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } - } else { - do_throw("couldn't import the exported file."); - } - }); + importer.importHTMLFromFile(gBookmarksFileNew, true); } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + const NOTITLE_URL = "http://notitle.mozilla.org/"; + let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + NetUtil.newURI(NOTITLE_URL), + PlacesUtils.bookmarks.DEFAULT_INDEX, + ""); + test_bookmarks.unfiled.push({ title: "", url: NOTITLE_URL }); + + try { + importer.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + + remove_all_bookmarks(); + + try { + importer.importHTMLFromFile(gBookmarksFileNew, true); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + waitForAsyncUpdates(function () { + testImportedBookmarks(); + + // Cleanup. + test_bookmarks.unfiled.pop(); + PlacesUtils.bookmarks.removeItem(id); + + try { + importer.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); }); add_test(function test_import_ontop() @@ -247,33 +229,23 @@ add_test(function test_import_ontop() // 4. run the test-suite try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { - if (success) { - try { - exporter.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } - try { - BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { - if (success) { - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); - }); - }); - } else { - do_throw("couldn't import the exported file."); - } - }); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } - - } else { - do_throw("couldn't import the exported file."); - } - }); + importer.importHTMLFromFile(gBookmarksFileNew, true); } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + try { + importer.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } + try { + importer.importHTMLFromFile(gBookmarksFileNew, true); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + waitForAsyncUpdates(function () { + testImportedBookmarks(); + + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); }); function testImportedBookmarks() diff --git a/browser/components/places/tests/unit/test_browserGlue_migrate.js b/browser/components/places/tests/unit/test_browserGlue_migrate.js index 85cefef882b..7f7bf012226 100644 --- a/browser/components/places/tests/unit/test_browserGlue_migrate.js +++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js @@ -29,15 +29,14 @@ function run_test() { do_check_eq(PlacesUtils.history.databaseStatus, PlacesUtils.history.DATABASE_STATUS_CREATE); - //A migrator would run before nsBrowserGlue Places initialization, so mimic - //that behavior adding a bookmark and notifying the migration. + // A migrator would run before nsBrowserGlue, so we mimic that behavior + // adding a bookmark. PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder, uri("http://mozilla.org/"), PlacesUtils.bookmarks.DEFAULT_INDEX, "migrated"); // Initialize nsBrowserGlue. let bg = Cc["@mozilla.org/browser/browserglue;1"]. - getService(Ci.nsIObserver); - bg.observe(null, "initial-migration", null) + getService(Ci.nsIBrowserGlue); let bookmarksObserver = { onBeginUpdateBatch: function() {}, diff --git a/browser/components/places/tests/unit/test_browserGlue_prefs.js b/browser/components/places/tests/unit/test_browserGlue_prefs.js index 0194510c96e..74c2eb3fbb3 100644 --- a/browser/components/places/tests/unit/test_browserGlue_prefs.js +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -17,16 +17,6 @@ const TOPICDATA_FORCE_PLACES_INIT = "force-places-init"; let bg = Cc["@mozilla.org/browser/browserglue;1"]. getService(Ci.nsIBrowserGlue); -function waitForImportAndSmartBookmarks(aCallback) { - Services.obs.addObserver(function waitImport() { - Services.obs.removeObserver(waitImport, "bookmarks-restore-success"); - // Delay to test eventual smart bookmarks creation. - do_execute_soon(function () { - waitForAsyncUpdates(aCallback); - }); - }, "bookmarks-restore-success", false); -} - let gTests = [ // This test must be the first one. @@ -76,20 +66,19 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces(). print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been imported, and a smart bookmark has been - // created. - itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR); - do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); - // Check preferences have been reverted. - do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + + // Check bookmarks.html has been imported, and a smart bookmark has been + // created. + itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, + SMART_BOOKMARKS_ON_TOOLBAR); + do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); + // Check preferences have been reverted. + do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + + run_next_test(); }, function test_import_noSmartBookmarks() @@ -109,20 +98,19 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces(). print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been imported, but smart bookmarks have not - // been created. - itemId = - PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0); - do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); - // Check preferences have been reverted. - do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + itemId = + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0); + do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); + // Check preferences have been reverted. + do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + + run_next_test(); }, function test_import_autoExport_updatedSmartBookmarks() @@ -143,21 +131,20 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces() print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been imported, but smart bookmarks have not - // been created. - itemId = - PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0); - do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); - do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); - // Check preferences have been reverted. - Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + itemId = + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0); + do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); + do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + // Check preferences have been reverted. + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); + + run_next_test(); }, function test_import_autoExport_oldSmartBookmarks() @@ -178,22 +165,21 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces() print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been imported, but smart bookmarks have not - // been created. - itemId = - PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR); - do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); - do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); - // Check preferences have been reverted. - Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + + // Check bookmarks.html has been imported, but smart bookmarks have not + // been created. + itemId = + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, + SMART_BOOKMARKS_ON_TOOLBAR); + do_check_eq(PlacesUtils.bookmarks.getItemTitle(itemId), "example"); + do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + // Check preferences have been reverted. + Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false); + + run_next_test(); }, function test_restore() @@ -212,21 +198,19 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces() print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been restored. - itemId = - PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR); - do_check_true(itemId > 0); - // Check preferences have been reverted. - do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + // Check bookmarks.html has been restored. + itemId = + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, + SMART_BOOKMARKS_ON_TOOLBAR); + do_check_true(itemId > 0); + // Check preferences have been reverted. + do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); + + run_next_test(); }, function test_restore_import() @@ -246,21 +230,20 @@ let gTests = [ // Force nsBrowserGlue::_initPlaces() print("Simulate Places init"); - waitForImportAndSmartBookmarks(function () { - // Check bookmarks.html has been restored. - itemId = - PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, - SMART_BOOKMARKS_ON_TOOLBAR); - do_check_true(itemId > 0); - // Check preferences have been reverted. - do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); - do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); - - run_next_test(); - }); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); + + // Check bookmarks.html has been restored. + itemId = + PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, + SMART_BOOKMARKS_ON_TOOLBAR); + do_check_true(itemId > 0); + // Check preferences have been reverted. + do_check_false(Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS)); + do_check_false(Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML)); + + run_next_test(); } ]; diff --git a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js index d7028b07b02..f6532f0cf17 100644 --- a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js +++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js @@ -364,15 +364,6 @@ function run_test() { } catch(ex) {} - waitForImportAndSmartBookmarks(next_test); -} - -function waitForImportAndSmartBookmarks(aCallback) { - Services.obs.addObserver(function waitImport() { - Services.obs.removeObserver(waitImport, "bookmarks-restore-success"); - // Delay to test eventual smart bookmarks creation. - do_execute_soon(function () { - waitForAsyncUpdates(aCallback); - }); - }, "bookmarks-restore-success", false); + // Kick-off tests. + next_test(); } diff --git a/toolkit/components/places/BookmarkHTMLUtils.jsm b/toolkit/components/places/BookmarkHTMLUtils.jsm deleted file mode 100644 index e2cd4d0116d..00000000000 --- a/toolkit/components/places/BookmarkHTMLUtils.jsm +++ /dev/null @@ -1,775 +0,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/. */ - -const EXPORTED_SYMBOLS = [ "BookmarkHTMLUtils" ]; - -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); -Cu.import("resource://gre/modules/PlacesUtils.jsm"); - -const Container_Normal = 0; -const Container_Toolbar = 1; -const Container_Menu = 2; -const Container_Unfiled = 3; -const Container_Places = 4; - -const RESTORE_BEGIN_NSIOBSERVER_TOPIC = "bookmarks-restore-begin"; -const RESTORE_SUCCESS_NSIOBSERVER_TOPIC = "bookmarks-restore-success"; -const RESTORE_FAILED_NSIOBSERVER_TOPIC = "bookmarks-restore-failed"; -const RESTORE_NSIOBSERVER_DATA = "html"; -const RESTORE_INITIAL_NSIOBSERVER_DATA = "html-initial"; - -const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; -const DESCRIPTION_ANNO = "bookmarkProperties/description"; -const POST_DATA_ANNO = "bookmarkProperties/POSTData"; - -let serialNumber = 0; // for favicons - -let BookmarkHTMLUtils = Object.freeze({ - importFromURL: function importFromFile(aUrlString, - aInitialImport, - aCallback) { - let importer = new BookmarkImporter(aInitialImport); - importer.importFromURL(aUrlString, aCallback); - }, - - importFromFile: function importFromFile(aLocalFile, - aInitialImport, - aCallback) { - let importer = new BookmarkImporter(aInitialImport); - importer.importFromFile(aLocalFile, aCallback); - }, -}); - -function Frame(aFrameId) { - this.containerId = aFrameId; - - /** - * How many
s have been nested. Each frame/container should start - * with a heading, and is then followed by a
,
    , or . When - * that list is complete, then it is the end of this container and we need - * to pop back up one level for new items. If we never get an open tag for - * one of these things, we should assume that the container is empty and - * that things we find should be siblings of it. Normally, these
    s won't - * be nested so this will be 0 or 1. - */ - this.containerNesting = 0; - - /** - * when we find a heading tag, it actually affects the title of the NEXT - * container in the list. This stores that heading tag and whether it was - * special. 'consumeHeading' resets this._ - */ - this.lastContainerType = Container_Normal; - - /** - * this contains the text from the last begin tag until now. It is reset - * at every begin tag. We can check it when we see a , or - * to see what the text content of that node should be. - */ - this.previousText = ""; - - /** - * true when we hit a
    , which contains the description for the preceding - * tag. We can't just check for
    like we can for or - * because if there is a sub-folder, it is actually a child of the
    - * because the tag is never explicitly closed. If this is true and we see a - * new open tag, that means to commit the description to the previous - * bookmark. - * - * Additional weirdness happens when the previous
    tag contains a

    : - * this means there is a new folder with the given description, and whose - * children are contained in the following
    list. - * - * This is handled in openContainer(), which commits previous text if - * necessary. - */ - this.inDescription = false; - - /** - * contains the URL of the previous bookmark created. This is used so that - * when we encounter a
    , we know what bookmark to associate the text with. - * This is cleared whenever we hit a

    , so that we know NOT to save this - * with a bookmark, but to keep it until - */ - this.previousLink = null; // nsIURI - - /** - * contains the URL of the previous livemark, so that when the link ends, - * and the livemark title is known, we can create it. - */ - this.previousFeed = null; // nsIURI - - /** - * Contains the id of an imported, or newly created bookmark. - */ - this.previousId = 0; - - /** - * Contains the date-added and last-modified-date of an imported item. - * Used to override the values set by insertBookmark, createFolder, etc. - */ - this.previousDateAdded = 0; - this.previousLastModifiedDate = 0; -} - -function BookmarkImporter(aInitialImport) { - this._isImportDefaults = aInitialImport; - this._frames = new Array(); - this._frames.push(new Frame(PlacesUtils.bookmarksMenuFolderId)); -} - -BookmarkImporter.prototype = { - - _safeTrim: function safeTrim(aStr) { - return aStr ? aStr.trim() : aStr; - }, - - get _curFrame() { - return this._frames[this._frames.length - 1]; - }, - - get _previousFrame() { - return this._frames[this._frames.length - 2]; - }, - - /** - * This is called when there is a new folder found. The folder takes the - * name from the previous frame's heading. - */ - _newFrame: function newFrame() { - let containerId = -1; - let frame = this._curFrame; - let containerTitle = frame.previousText; - frame.previousText = ""; - let containerType = frame.lastContainerType; - - switch (containerType) { - case Container_Normal: - // append a new folder - containerId = - PlacesUtils.bookmarks.createFolder(frame.containerId, - containerTitle, - PlacesUtils.bookmarks.DEFAULT_INDEX); - break; - case Container_Places: - containerId = PlacesUtils.placesRootId; - break; - case Container_Menu: - containerId = PlacesUtils.bookmarksMenuFolderId; - break; - case Container_Unfiled: - containerId = PlacesUtils.unfiledBookmarksFolderId; - break; - case Container_Toolbar: - containerId = PlacesUtils.toolbarFolderId; - break; - default: - // NOT REACHED - throw new Error("Unreached"); - } - - if (frame.previousDateAdded > 0) { - try { - PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded); - } catch(e) { - } - frame.previousDateAdded = 0; - } - if (frame.previousLastModifiedDate > 0) { - try { - PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate); - } catch(e) { - } - // don't clear last-modified, in case there's a description - } - - frame.previousId = containerId; - - this._frames.push(new Frame(containerId)); - }, - - /** - * Handles

    . We check for the attribute PLACES_ROOT and reset the - * container id if it's found. Otherwise, the default bookmark menu - * root is assumed and imported things will go into the bookmarks menu. - */ - _handleHead1Begin: function handleHead1Begin(aElt) { - if (this._frames.length > 1) { - return; - } - if (aElt.hasAttribute("places_root")) { - this._curFrame.containerId = PlacesUtils.placesRootId; - } - }, - - /** - * Called for h2,h3,h4,h5,h6. This just stores the correct information in - * the current frame; the actual new frame corresponding to the container - * associated with the heading will be created when the tag has been closed - * and we know the title (we don't know to create a new folder or to merge - * with an existing one until we have the title). - */ - _handleHeadBegin: function handleHeadBegin(aElt) { - let frame = this._curFrame; - - // after a heading, a previous bookmark is not applicable (for example, for - // the descriptions contained in a
    ). Neither is any previous head type - frame.previousLink = null; - frame.lastContainerType = Container_Normal; - - // It is syntactically possible for a heading to appear after another heading - // but before the
    that encloses that folder's contents. This should not - // happen in practice, as the file will contain "
    " sequence for - // empty containers. - // - // Just to be on the safe side, if we encounter - //

    FOO

    - //

    BAR

    - //
    ...content 1...
    - //
    ...content 2...
    - // we'll pop the stack when we find the h3 for BAR, treating that as an - // implicit ending of the FOO container. The output will be FOO and BAR as - // siblings. If there's another
    following (as in "content 2"), those - // items will be treated as further siblings of FOO and BAR - if (frame.containerNesting == 0) { - this._frames.pop(); - } - - // We have to check for some attributes to see if this is a "special" - // folder, which will have different creation rules when the end tag is - // processed. - if (aElt.hasAttribute("personal_toolbar_folder")) { - if (this._isImportDefaults) { - frame.lastContainerType = Container_Toolbar; - } - } else if (aElt.hasAttribute("bookmarks_menu")) { - if (this._isImportDefaults) { - frame.lastContainerType = Container_Menu; - } - } else if (aElt.hasAttribute("unfiled_bookmarks_folder")) { - if (this._isImportDefaults) { - frame.lastContainerType = Container_Unfiled; - } - } else if (aElt.hasAttribute("places_root")) { - if (this._isImportDefaults) { - frame.lastContainerType = Container_Places; - } - } else { - let addDate = aElt.getAttribute("add_date"); - if (addDate) { - frame.previousDateAdded = - this._convertImportedDateToInternalDate(addDate); - } - let modDate = aElt.getAttribute("last_modified"); - if (modDate) { - frame.previousLastModifiedDate = - this._convertImportedDateToInternalDate(modDate); - } - } - this._curFrame.previousText = ""; - }, - - /* - * Handles " tags that have no href. - if (href) { - // Save the address if it's valid. Note that we ignore errors if this is a - // feed since href is optional for them. - try { - frame.previousLink = NetUtil.newURI(href); - } catch(e) { - if (!frame.previousFeed) { - frame.previousLink = null; - return; - } - } - } else { - frame.previousLink = null; - // The exception is for feeds, where the href is an optional component - // indicating the source web site. - if (!frame.previousFeed) { - return; - } - } - - // Save bookmark's last modified date. - if (lastModified) { - frame.previousLastModifiedDate = - this._convertImportedDateToInternalDate(lastModified); - } - - // If this is a live bookmark, we will handle it in HandleLinkEnd(), so we - // can skip bookmark creation. - if (frame.previousFeed) { - return; - } - - // Create the bookmark. The title is unknown for now, we will set it later. - try { - frame.previousId = - PlacesUtils.bookmarks.insertBookmark(frame.containerId, - frame.previousLink, - PlacesUtils.bookmarks.DEFAULT_INDEX, - ""); - } catch(e) { - return; - } - - // Set the date added value, if we have it. - if (dateAdded) { - try { - PlacesUtils.bookmarks.setItemDateAdded(frame.previousId, - this._convertImportedDateToInternalDate(dateAdded)); - } catch(e) { - } - } - - // Save the favicon. - if (icon || iconUri) { - let iconUriObject; - try { - iconUriObject = NetUtil.newURI(iconUri); - } catch(e) { - } - if (icon || iconUriObject) { - try { - this._setFaviconForURI(frame.previousLink, iconUriObject, icon); - } catch(e) { - } - } - } - - // Save the keyword. - if (keyword) { - try { - PlacesUtils.bookmarks.setKeywordForBookmark(frame.previousId, keyword); - if (postData) { - PlacesUtils.annotations.setItemAnnotation(frame.previousId, - POST_DATA_ANNO, - postData, - 0, - PlacesUtils.annotations.EXPIRE_NEVER); - } - } catch(e) { - } - } - - // Set load-in-sidebar annotation for the bookmark. - if (webPanel && webPanel.toLowerCase() == "true") { - try { - PlacesUtils.annotations.setItemAnnotation(frame.previousId, - LOAD_IN_SIDEBAR_ANNO, - 1, - 0, - PlacesUtils.annotations.EXPIRE_NEVER); - } catch(e) { - } - } - - // Import last charset. - if (lastCharset) { - try { - PlacesUtils.history.setCharsetForURI(frame.previousLink, lastCharset); - } catch(e) { - } - } - - }, - - _handleContainerBegin: function handleContainerBegin() { - this._curFrame.containerNesting++; - }, - - /** - * Our "indent" count has decreased, and when we hit 0 that means that this - * container is complete and we need to pop back to the outer frame. Never - * pop the toplevel frame - */ - _handleContainerEnd: function handleContainerEnd() { - let frame = this._curFrame; - if (frame.containerNesting > 0) - frame.containerNesting --; - if (this._frames.length > 1 && frame.containerNesting == 0) { - // we also need to re-set the imported last-modified date here. Otherwise - // the addition of items will override the imported field. - let prevFrame = this._previousFrame; - if (prevFrame.previousLastModifiedDate > 0) { - PlacesUtils.bookmarks.setItemLastModified(frame.containerId, - prevFrame.previousLastModifiedDate); - } - this._frames.pop(); - } - }, - - /** - * Creates the new frame for this heading now that we know the name of the - * container (tokens since the heading open tag will have been placed in - * previousText). - */ - _handleHeadEnd: function handleHeadEnd() { - this._newFrame(); - }, - - /** - * Saves the title for the given bookmark. - */ - _handleLinkEnd: function handleLinkEnd() { - let frame = this._curFrame; - frame.previousText = frame.previousText.trim(); - - try { - if (frame.previousFeed) { - // The is a live bookmark. We create it here since in HandleLinkBegin we - // don't know the title. - PlacesUtils.livemarks.addLivemark({ - "title": frame.previousText, - "parentId": frame.containerId, - "index": PlacesUtils.bookmarks.DEFAULT_INDEX, - "feedURI": frame.previousFeed, - "siteURI": frame.previousLink, - }); - } else if (frame.previousLink) { - // This is a common bookmark. - PlacesUtils.bookmarks.setItemTitle(frame.previousId, - frame.previousText); - } - } catch(e) { - } - - - // Set last modified date as the last change. - if (frame.previousId > 0 && frame.previousLastModifiedDate > 0) { - try { - PlacesUtils.bookmarks.setItemLastModified(frame.previousId, - frame.previousLastModifiedDate); - } catch(e) { - } - // Note: don't clear previousLastModifiedDate, because if this item has a - // description, we'll need to set it again. - } - - frame.previousText = ""; - - }, - - _openContainer: function openContainer(aElt) { - if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") { - return; - } - switch(aElt.localName) { - case "h1": - this._handleHead1Begin(aElt); - break; - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - this._handleHeadBegin(aElt); - break; - case "a": - this._handleLinkBegin(aElt); - break; - case "dl": - case "ul": - case "menu": - this._handleContainerBegin(); - break; - case "dd": - this._curFrame.inDescription = true; - break; - } - }, - - _closeContainer: function closeContainer(aElt) { - let frame = this._curFrame; - - // see the comment for the definition of inDescription. Basically, we commit - // any text in previousText to the description of the node/folder if there - // is any. - if (frame.inDescription) { - // NOTE ES5 trim trims more than the previous C++ trim. - frame.previousText = frame.previousText.trim(); // important - if (frame.previousText) { - - let itemId = !frame.previousLink ? frame.containerId - : frame.previousId; - - try { - if (!PlacesUtils.annotations.itemHasAnnotation(itemId, DESCRIPTION_ANNO)) { - PlacesUtils.annotations.setItemAnnotation(itemId, - DESCRIPTION_ANNO, - frame.previousText, - 0, - PlacesUtils.annotations.EXPIRE_NEVER); - } - } catch(e) { - } - frame.previousText = ""; - - // Set last-modified a 2nd time for all items with descriptions - // we need to set last-modified as the *last* step in processing - // any item type in the bookmarks.html file, so that we do - // not overwrite the imported value. for items without descriptions, - // setting this value after setting the item title is that - // last point at which we can save this value before it gets reset. - // for items with descriptions, it must set after that point. - // however, at the point at which we set the title, there's no way - // to determine if there will be a description following, - // so we need to set the last-modified-date at both places. - - let lastModified; - if (!frame.previousLink) { - lastModified = this._previousFrame.previousLastModifiedDate; - } else { - lastModified = frame.previousLastModifiedDate; - } - - if (itemId > 0 && lastModified > 0) { - PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified); - } - } - frame.inDescription = false; - } - - if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") { - return; - } - switch(aElt.localName) { - case "dl": - case "ul": - case "menu": - this._handleContainerEnd(); - break; - case "dt": - break; - case "h1": - // ignore - break; - case "h2": - case "h3": - case "h4": - case "h5": - case "h6": - this._handleHeadEnd(); - break; - case "a": - this._handleLinkEnd(); - break; - default: - break; - } - }, - - _appendText: function appendText(str) { - this._curFrame.previousText += str; - }, - - /** - * data is a string that is a data URI for the favicon. Our job is to - * decode it and store it in the favicon service. - * - * When aIconURI is non-null, we will use that as the URI of the favicon - * when storing in the favicon service. - * - * When aIconURI is null, we have to make up a URI for this favicon so that - * it can be stored in the service. The real one will be set the next time - * the user visits the page. Our made up one should get expired when the - * page no longer references it. - */ - _setFaviconForURI: function setFaviconForURI(aPageURI, aIconURI, aData) { - // if the input favicon URI is a chrome: URI, then we just save it and don't - // worry about data - if (aIconURI) { - if (aIconURI.scheme == "chrome") { - PlacesUtils.favicons.setFaviconUrlForPage(aPageURI, aIconURI); - return; - } - } - - // some bookmarks have placeholder URIs that contain just "data:" - // ignore these - if (aData.length <= 5) { - return; - } - - let faviconURI; - if (aIconURI) { - faviconURI = aIconURI; - } else { - // make up favicon URL - let faviconSpec = "http://www.mozilla.org/2005/made-up-favicon/" - + serialNumber - + "-" - + new Date().getTime(); - faviconURI = NetUtil.newURI(faviconSpec); - serialNumber++; - } - - // save the favicon data - // This could fail if the favicon is bigger than defined limit, in such a - // case data will not be saved to the db but we will still continue. - PlacesUtils.favicons.setFaviconDataFromDataURL(faviconURI, aData, 0); - - PlacesUtils.favicons.setFaviconUrlForPage(aPageURI, faviconURI); - }, - - /** - * Converts a string date in seconds to an int date in microseconds - */ - _convertImportedDateToInternalDate: function convertImportedDateToInternalDate(aDate) { - if (aDate && !isNaN(aDate)) { - return parseInt(aDate) * 1000000; // in bookmarks.html this value is in seconds, not microseconds - } else { - return Date.now(); - } - }, - - runBatched: function runBatched(aDoc) { - if (!aDoc) { - return; - } - - if (this._isImportDefaults) { - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId); - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId); - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); - } - - let current = aDoc; - let next; - for (;;) { - switch (current.nodeType) { - case Ci.nsIDOMNode.ELEMENT_NODE: - this._openContainer(current); - break; - case Ci.nsIDOMNode.TEXT_NODE: - this._appendText(current.data); - break; - } - if ((next = current.firstChild)) { - current = next; - continue; - } - for (;;) { - if (current.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) { - this._closeContainer(current); - } - if (current == aDoc) { - return; - } - if ((next = current.nextSibling)) { - current = next; - break; - } - current = current.parentNode; - } - } - }, - - _walkTreeForImport: function walkTreeForImport(aDoc) { - PlacesUtils.bookmarks.runInBatchMode(this, aDoc); - }, - - _notifyObservers: function notifyObservers(topic) { - Services.obs.notifyObservers(null, - topic, - this._isImportDefaults ? - RESTORE_INITIAL_NSIOBSERVER_DATA : - RESTORE_NSIOBSERVER_DATA); - }, - - importFromURL: function importFromURL(aUrlString, aCallback) { - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); - xhr.onload = (function onload() { - try { - this._walkTreeForImport(xhr.responseXML); - this._notifyObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC); - if (aCallback) { - try { - aCallback(true); - } catch(ex) { - } - } - } catch(e) { - this._notifyObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC); - if (aCallback) { - try { - aCallback(false); - } catch(ex) { - } - } - throw e; - } - }).bind(this); - xhr.onabort = xhr.onerror = xhr.ontimeout = (function handleFail() { - this._notifyObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC); - if (aCallback) { - try { - aCallback(false); - } catch(ex) { - } - } - }).bind(this); - this._notifyObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC); - try { - xhr.open("GET", aUrlString); - xhr.responseType = "document"; - xhr.overrideMimeType("text/html"); - xhr.send(); - } catch (e) { - this._notifyObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC); - if (aCallback) { - try { - aCallback(false); - } catch(ex) { - } - } - } - }, - - importFromFile: function importFromFile(aLocalFile, aCallback) { - let url = NetUtil.newURI(aLocalFile); - this.importFromURL(url.spec, aCallback); - }, - -}; diff --git a/toolkit/components/places/Helpers.cpp b/toolkit/components/places/Helpers.cpp index 76bb3ee340d..1a5806ee791 100644 --- a/toolkit/components/places/Helpers.cpp +++ b/toolkit/components/places/Helpers.cpp @@ -445,5 +445,64 @@ AsyncStatementTelemetryTimer::HandleCompletion(PRUint16 aReason) return NS_OK; } +// This is a temporary converter used by nsPlacesImportExportService until +// bug 482911 completes its js rewrite. +jsval +livemarkInfoToJSVal(PRInt64 aId, + const nsACString& aGUID, + const nsAString& aTitle, + PRInt64 aParentId, + PRInt32 aIndex, + nsCOMPtr& aFeedURI, + nsCOMPtr& aSiteURI) +{ + nsCOMPtr xpc = mozilla::services::GetXPConnect(); + NS_ENSURE_TRUE(xpc, JSVAL_NULL); + + nsAXPCNativeCallContext *ncc; + nsresult rv = xpc->GetCurrentNativeCallContext(&ncc); + NS_ENSURE_SUCCESS(rv, JSVAL_NULL); + JSContext *cx = nsnull; + rv = ncc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, JSVAL_NULL); + JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL); + NS_ENSURE_TRUE(obj, JSVAL_NULL); + + jsval id; + NS_ENSURE_TRUE(JS_NewNumberValue(cx, double(aId), &id), JSVAL_NULL); + + JSString* guid = JS_NewStringCopyN(cx, PromiseFlatCString(aGUID).get(), + aGUID.Length()); + NS_ENSURE_TRUE(guid, JSVAL_NULL); + + JSString* title = JS_NewUCStringCopyN(cx, PromiseFlatString(aTitle).get(), + aTitle.Length()); + NS_ENSURE_TRUE(title, JSVAL_NULL); + + jsval parentId; + NS_ENSURE_TRUE(JS_NewNumberValue(cx, double(aParentId), &parentId), JSVAL_NULL); + + jsval feedURI; + rv = nsContentUtils::WrapNative(cx, JS_GetGlobalForScopeChain(cx), + NS_ISUPPORTS_CAST(nsIURI*, aFeedURI), &feedURI); + NS_ENSURE_SUCCESS(rv, JSVAL_NULL); + + jsval siteURI; + rv = nsContentUtils::WrapNative(cx, JS_GetGlobalForScopeChain(cx), + NS_ISUPPORTS_CAST(nsIURI*, aSiteURI), &siteURI); + NS_ENSURE_SUCCESS(rv, JSVAL_NULL); + + if (!JS_DefineProperty(cx, obj, "id", id, NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "guid", STRING_TO_JSVAL(guid), NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "title", STRING_TO_JSVAL(title), NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "parentId", parentId, NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "index", INT_TO_JSVAL(aIndex), NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "feedURI", feedURI, NULL, NULL, JSPROP_ENUMERATE) || + !JS_DefineProperty(cx, obj, "siteURI", siteURI, NULL, NULL, JSPROP_ENUMERATE)) { + return JSVAL_NULL; + } + return OBJECT_TO_JSVAL(obj); +} + } // namespace places } // namespace mozilla diff --git a/toolkit/components/places/Helpers.h b/toolkit/components/places/Helpers.h index e77a214edd1..600fa67df6b 100644 --- a/toolkit/components/places/Helpers.h +++ b/toolkit/components/places/Helpers.h @@ -297,6 +297,15 @@ private: const TimeStamp mStart; }; +jsval +livemarkInfoToJSVal(PRInt64 aId, + const nsACString& aGUID, + const nsAString& aTitle, + PRInt64 aParentId, + PRInt32 aIndex, + nsCOMPtr& aFeedURI, + nsCOMPtr& aSiteURI); + } // namespace places } // namespace mozilla diff --git a/toolkit/components/places/Makefile.in b/toolkit/components/places/Makefile.in index e1d43e8f5b7..59120d76e38 100644 --- a/toolkit/components/places/Makefile.in +++ b/toolkit/components/places/Makefile.in @@ -92,7 +92,7 @@ CPPSRCS = \ SQLFunctions.cpp \ Helpers.cpp \ History.cpp \ - nsPlacesExportService.cpp \ + nsPlacesImportExportService.cpp \ AsyncFaviconHelpers.cpp \ PlaceInfo.cpp \ VisitInfo.cpp \ @@ -123,7 +123,6 @@ endif EXTRA_JS_MODULES = \ PlacesDBUtils.jsm \ - BookmarkHTMLUtils.jsm \ $(NULL) EXTRA_PP_JS_MODULES = \ diff --git a/toolkit/components/places/PlacesUtils.jsm b/toolkit/components/places/PlacesUtils.jsm index 20e914f2dcb..4499df2800a 100644 --- a/toolkit/components/places/PlacesUtils.jsm +++ b/toolkit/components/places/PlacesUtils.jsm @@ -85,7 +85,7 @@ XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { const MIN_TRANSACTIONS_FOR_BATCH = 5; // The RESTORE_*_NSIOBSERVER_TOPIC constants should match the #defines of the -// same names in browser/components/places/src/nsPlacesExportService.cpp +// same names in browser/components/places/src/nsPlacesImportExportService.cpp const RESTORE_BEGIN_NSIOBSERVER_TOPIC = "bookmarks-restore-begin"; const RESTORE_SUCCESS_NSIOBSERVER_TOPIC = "bookmarks-restore-success"; const RESTORE_FAILED_NSIOBSERVER_TOPIC = "bookmarks-restore-failed"; diff --git a/toolkit/components/places/nsIPlacesImportExportService.idl b/toolkit/components/places/nsIPlacesImportExportService.idl index af9835dae6c..b6e60f4ba4b 100644 --- a/toolkit/components/places/nsIPlacesImportExportService.idl +++ b/toolkit/components/places/nsIPlacesImportExportService.idl @@ -41,15 +41,34 @@ interface nsILocalFile; interface nsIURI; /** - * The PlacesImportExport interface provides methods for exporting Places data. - * The word "Import" is in the name for legacy reasons and was kept to avoid - * disrupting potential extension code using the export part. The new importer - * lives in BookmarkHTMLUtils.jsm. + * The PlacesImportExport interface provides methods for importing + * and exporting Places data. */ -[scriptable, uuid(47a4a09e-c708-4e68-b2f2-664d982ce026)] +[scriptable, uuid(08f4626e-af3f-4e84-bd01-cf09175c4f94)] interface nsIPlacesImportExportService: nsISupports { + /** + * Loads the given bookmarks.html file and replaces it with the current + * bookmarks hierarchy (if aIsInitialImport is true) or appends it + * (if aIsInitialImport is false). + * + * Three nsIObserverService notifications are fired as a result of the + * import. "bookmarks-restore-begin" is fired just before the import is + * started. "bookmarks-restore-success" is fired right after the + * bookmarks are successfully imported. "bookmarks-restore-failed" is + * fired right after a failure occurs when importing the bookmarks. + * Observers will be passed through their data parameters either "html" + * if aIsInitialImport is false or "html-initial" if aIsInitialImport is + * true. The observer subject will be null. + */ + void importHTMLFromFile(in nsILocalFile aFile, in boolean aIsInitialImport); + + /** + * Same thing as importHTMLFromFile, but takes a URI instead + */ + void importHTMLFromURI(in nsIURI aURI, in boolean aIsInitialImport); + /** * Saves the current bookmarks hierarchy to a bookmarks.html file. */ diff --git a/toolkit/components/places/nsPlacesExportService.cpp b/toolkit/components/places/nsPlacesExportService.cpp deleted file mode 100644 index ec70ec2769a..00000000000 --- a/toolkit/components/places/nsPlacesExportService.cpp +++ /dev/null @@ -1,1163 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla History System - * - * The Initial Developer of the Original Code is - * Google Inc. - * Portions created by the Initial Developer are Copyright (C) 2005 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Brett Wilson - * Dietrich Ayala - * Drew Willcoxon - * Marco Bonardo - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/** - * Importer/exporter between the mozStorage-based bookmarks and the old-style - * "bookmarks.html" - * - * Format: - * - * Primary heading := h1 - * Old version used this to set attributes on the bookmarks RDF root, such - * as the last modified date. We only use H1 to check for the attribute - * PLACES_ROOT, which tells us that this hierarchy root is the places root. - * For backwards compatibility, if we don't find this, we assume that the - * hierarchy is rooted at the bookmarks menu. - * Heading := any heading other than h1 - * Old version used this to set attributes on the current container. We only - * care about the content of the heading container, which contains the title - * of the bookmark container. - * Bookmark := a - * HREF is the destination of the bookmark - * FEEDURL is the URI of the RSS feed if this is a livemark. - * LAST_CHARSET is stored as an annotation so that the next time we go to - * that page we remember the user's preference. - * WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar. - * ICON will be stored in the favicon service - * ICON_URI is new for places bookmarks.html, it refers to the original - * URI of the favicon so we don't have to make up favicon URLs. - * Text of the container is the name of the bookmark - * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2) - * Bookmark comment := dd - * This affects the previosly added bookmark - * Separator := hr - * Insert a separator into the current container - * The folder hierarchy is defined by
    /
      / (the old importing code - * handles all these cases, when we write, use
      ). - * - * Overall design - * -------------- - * - * We need to emulate a recursive parser. A "Bookmark import frame" is created - * corresponding to each folder we encounter. These are arranged in a stack, - * and contain all the state we need to keep track of. - * - * A frame is created when we find a heading, which defines a new container. - * The frame also keeps track of the nesting of
      s, (in well-formed - * bookmarks files, these will have a 1-1 correspondence with frames, but we - * try to be a little more flexible here). When the nesting count decreases - * to 0, then we know a frame is complete and to pop back to the previous - * frame. - * - * Note that a lot of things happen when tags are CLOSED because we need to - * get the text from the content of the tag. For example, link and heading tags - * both require the content (= title) before actually creating it. - */ - -#include "nsPlacesExportService.h" -#include "nsNetUtil.h" -#include "nsParserCIID.h" -#include "nsUnicharUtils.h" -#include "nsAppDirectoryServiceDefs.h" -#include "nsDirectoryServiceUtils.h" -#include "nsToolkitCompsCID.h" -#include "nsIParser.h" -#include "prprf.h" -#include "nsIObserverService.h" -#include "nsISupportsPrimitives.h" -#include "nsPlacesMacros.h" -#include "mozilla/Util.h" - -using namespace mozilla; - -#define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar") -#define DESCRIPTION_ANNO NS_LITERAL_CSTRING("bookmarkProperties/description") -#define POST_DATA_ANNO NS_LITERAL_CSTRING("bookmarkProperties/POSTData") - -#define LMANNO_FEEDURI "livemark/feedURI" -#define LMANNO_SITEURI "livemark/siteURI" - -// define to get debugging messages on console about import/export -//#define DEBUG_EXPORT - -#if defined(XP_WIN) || defined(XP_OS2) -#define NS_LINEBREAK "\015\012" -#else -#define NS_LINEBREAK "\012" -#endif - -class nsIOutputStream; -static const char kWhitespace[] = " \r\n\t\b"; -static nsresult WriteEscapedUrl(const nsCString &aString, nsIOutputStream* aOutput); - -/** - * Copied from nsEscape.cpp, which requires internal string API. - */ -char* -nsEscapeHTML(const char* string) -{ - /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ - char* escaped = nsnull; - PRUint32 len = strlen(string); - if (len >= (PR_UINT32_MAX / 6)) - return nsnull; - - escaped = (char*)NS_Alloc((len * 6) + 1); - if (escaped) { - char* ptr = escaped; - for (; *string != '\0'; string++) { - switch(*string) { - case '<': - *ptr++ = '&'; - *ptr++ = 'l'; - *ptr++ = 't'; - *ptr++ = ';'; - break; - case '>': - *ptr++ = '&'; - *ptr++ = 'g'; - *ptr++ = 't'; - *ptr++ = ';'; - break; - case '&': - *ptr++ = '&'; - *ptr++ = 'a'; - *ptr++ = 'm'; - *ptr++ = 'p'; - *ptr++ = ';'; - break; - case '"': - *ptr++ = '&'; - *ptr++ = 'q'; - *ptr++ = 'u'; - *ptr++ = 'o'; - *ptr++ = 't'; - *ptr++ = ';'; - break; - case '\'': - *ptr++ = '&'; - *ptr++ = '#'; - *ptr++ = '3'; - *ptr++ = '9'; - *ptr++ = ';'; - break; - default: - *ptr++ = *string; - } - } - *ptr = '\0'; - } - return escaped; -} - -PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsPlacesExportService, gExportService) - -NS_IMPL_ISUPPORTS1(nsPlacesExportService, nsIPlacesImportExportService) - -nsPlacesExportService::nsPlacesExportService() -{ - NS_ASSERTION(!gExportService, - "Attempting to create two instances of the service!"); - gExportService = this; -} - -nsPlacesExportService::~nsPlacesExportService() -{ - NS_ASSERTION(gExportService == this, - "Deleting a non-singleton instance of the service"); - if (gExportService == this) - gExportService = nsnull; -} - -nsresult -nsPlacesExportService::Init() -{ - // Be sure to call EnsureServiceState() before using services. - mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); - NS_ENSURE_TRUE(mHistoryService, NS_ERROR_OUT_OF_MEMORY); - mFaviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID); - NS_ENSURE_TRUE(mFaviconService, NS_ERROR_OUT_OF_MEMORY); - mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID); - NS_ENSURE_TRUE(mAnnotationService, NS_ERROR_OUT_OF_MEMORY); - mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID); - NS_ENSURE_TRUE(mBookmarksService, NS_ERROR_OUT_OF_MEMORY); - mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID); - NS_ENSURE_TRUE(mLivemarkService, NS_ERROR_OUT_OF_MEMORY); - return NS_OK; -} - -// SyncChannelStatus -// -// If a function returns an error, we need to set the channel status to be -// the same, but only if the channel doesn't have its own error. This returns -// the error code that should be sent to OnStopRequest. -static nsresult -SyncChannelStatus(nsIChannel* channel, nsresult status) -{ - nsresult channelStatus; - channel->GetStatus(&channelStatus); - if (NS_FAILED(channelStatus)) - return channelStatus; - - if (NS_SUCCEEDED(status)) - return NS_OK; // caller and the channel are happy - - // channel was OK, but caller wasn't: set the channel state - channel->Cancel(status); - return status; -} - - -static char kFileIntro[] = - "" NS_LINEBREAK - // Note: we write bookmarks in UTF-8 - "" NS_LINEBREAK - "" NS_LINEBREAK - "Bookmarks" NS_LINEBREAK; -static const char kRootIntro[] = "

      -// -// Goes after the container header (Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kBookmarkIntro, sizeof(kBookmarkIntro)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - - -// WriteContainerEpilogue -// -//

      -// -// Goes after the container contents to close the container -static nsresult -WriteContainerEpilogue(const nsACString& aIndent, nsIOutputStream* aOutput) -{ - PRUint32 dummy; - nsresult rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kBookmarkClose, sizeof(kBookmarkClose)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - - -// WriteFaviconAttribute -// -// This writes the 'ICON="data:asdlfkjas;ldkfja;skdljfasdf"' attribute for -// an item. We special-case chrome favicon URIs by just writing the chrome: -// URI. -static nsresult -WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput) -{ - PRUint32 dummy; - - // if favicon uri is invalid we skip the attribute silently, to avoid - // creating a corrupt file. - nsCOMPtr uri; - nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI); - if (NS_FAILED(rv)) { - nsCAutoString warnMsg; - warnMsg.Append("Bookmarks Export: Found invalid favicon '"); - warnMsg.Append(aURI); - warnMsg.Append("'"); - NS_WARNING(warnMsg.get()); - return NS_OK; - } - - // get favicon - nsCOMPtr faviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr faviconURI; - rv = faviconService->GetFaviconForPage(uri, getter_AddRefs(faviconURI)); - if (rv == NS_ERROR_NOT_AVAILABLE) - return NS_OK; // no favicon - NS_ENSURE_SUCCESS(rv, rv); // anything else is error - - nsCAutoString faviconScheme; - nsCAutoString faviconSpec; - rv = faviconURI->GetSpec(faviconSpec); - NS_ENSURE_SUCCESS(rv, rv); - rv = faviconURI->GetScheme(faviconScheme); - NS_ENSURE_SUCCESS(rv, rv); - - // write favicon URI: 'ICON_URI="..."' - rv = aOutput->Write(kIconURIAttribute, sizeof(kIconURIAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteEscapedUrl(faviconSpec, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - if (!faviconScheme.EqualsLiteral("chrome")) { - // only store data for non-chrome URIs - - nsAutoString faviconContents; - rv = faviconService->GetFaviconDataAsDataURL(faviconURI, faviconContents); - NS_ENSURE_SUCCESS(rv, rv); - if (faviconContents.Length() > 0) { - rv = aOutput->Write(kIconAttribute, sizeof(kIconAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - NS_ConvertUTF16toUTF8 utf8Favicon(faviconContents); - rv = aOutput->Write(utf8Favicon.get(), utf8Favicon.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - } - return NS_OK; -} - - -// WriteDateAttribute -// -// This writes the '{attr value=}"{time in seconds}"' attribute for -// an item. -static nsresult -WriteDateAttribute(const char aAttributeStart[], PRInt32 aLength, PRTime aAttributeValue, nsIOutputStream* aOutput) -{ - // write attribute start - PRUint32 dummy; - nsresult rv = aOutput->Write(aAttributeStart, aLength, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // in bookmarks.html this value is in seconds, not microseconds - aAttributeValue /= 1000000; - - // write attribute value - char dateInSeconds[32]; - PR_snprintf(dateInSeconds, sizeof(dateInSeconds), "%lld", aAttributeValue); - rv = aOutput->Write(dateInSeconds, strlen(dateInSeconds), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - - -// nsPlacesExportService::WriteContainer -// -// Writes out all the necessary parts of a bookmarks folder. -nsresult -nsPlacesExportService::WriteContainer(nsINavHistoryResultNode* aFolder, - const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - nsresult rv = WriteContainerHeader(aFolder, aIndent, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteContainerPrologue(aIndent, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteContainerContents(aFolder, aIndent, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteContainerEpilogue(aIndent, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - - -// nsPlacesExportService::WriteContainerHeader -// -// This writes '

      Title

      ' -// Remember folders can also have favicons, which we put in the H3 tag -nsresult -nsPlacesExportService::WriteContainerHeader(nsINavHistoryResultNode* aFolder, - const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - PRUint32 dummy; - nsresult rv; - - // indent - if (!aIndent.IsEmpty()) { - rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // "
      Write(kContainerIntro, sizeof(kContainerIntro)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // get folder id - PRInt64 folderId; - rv = aFolder->GetItemId(&folderId); - NS_ENSURE_SUCCESS(rv, rv); - - // write ADD_DATE - PRTime dateAdded = 0; - rv = aFolder->GetDateAdded(&dateAdded); - NS_ENSURE_SUCCESS(rv, rv); - - if (dateAdded) { - rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - } - - // write LAST_MODIFIED - PRTime lastModified = 0; - rv = aFolder->GetLastModified(&lastModified); - NS_ENSURE_SUCCESS(rv, rv); - - if (lastModified) { - rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - } - - PRInt64 placesRoot; - rv = mBookmarksService->GetPlacesRoot(&placesRoot); - NS_ENSURE_SUCCESS(rv, rv); - - PRInt64 bookmarksMenuFolder; - rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); - NS_ENSURE_SUCCESS(rv, rv); - - PRInt64 toolbarFolder; - rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); - NS_ENSURE_SUCCESS(rv, rv); - - PRInt64 unfiledBookmarksFolder; - rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder); - NS_ENSURE_SUCCESS(rv, rv); - - // " PERSONAL_TOOLBAR_FOLDER="true"", etc. - if (folderId == placesRoot) { - rv = aOutput->Write(kPlacesRootAttribute, sizeof(kPlacesRootAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - else if (folderId == bookmarksMenuFolder) { - rv = aOutput->Write(kBookmarksRootAttribute, sizeof(kBookmarksRootAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - else if (folderId == unfiledBookmarksFolder) { - rv = aOutput->Write(kUnfiledBookmarksFolderAttribute, sizeof(kUnfiledBookmarksFolderAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - else if (folderId == toolbarFolder) { - rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // ">" - rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // title - rv = WriteTitle(aFolder, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - // "

    \n" - rv = aOutput->Write(kContainerClose, sizeof(kContainerClose)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // description - rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_FOLDER, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - return rv; -} - - -// nsPlacesExportService::WriteTitle -// -// Retrieves, escapes and writes the title to the stream. -nsresult -nsPlacesExportService::WriteTitle(nsINavHistoryResultNode* aItem, - nsIOutputStream* aOutput) -{ - // XXX Bug 381767 - support titles for separators - PRUint32 type = 0; - nsresult rv = aItem->GetType(&type); - NS_ENSURE_SUCCESS(rv, rv); - if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) - return NS_ERROR_INVALID_ARG; - - nsCAutoString title; - rv = aItem->GetTitle(title); - NS_ENSURE_SUCCESS(rv, rv); - - char* escapedTitle = nsEscapeHTML(title.get()); - if (escapedTitle) { - PRUint32 dummy; - rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); - nsMemory::Free(escapedTitle); - NS_ENSURE_SUCCESS(rv, rv); - } - return NS_OK; -} - - -// nsPlacesExportService::WriteDescription -// -// Write description out for all item types. -nsresult -nsPlacesExportService::WriteDescription(PRInt64 aItemId, PRInt32 aType, - nsIOutputStream* aOutput) -{ - bool hasDescription = false; - nsresult rv = mAnnotationService->ItemHasAnnotation(aItemId, - DESCRIPTION_ANNO, - &hasDescription); - if (NS_FAILED(rv) || !hasDescription) - return rv; - - nsAutoString description; - rv = mAnnotationService->GetItemAnnotationString(aItemId, DESCRIPTION_ANNO, - description); - NS_ENSURE_SUCCESS(rv, rv); - - char* escapedDesc = nsEscapeHTML(NS_ConvertUTF16toUTF8(description).get()); - if (escapedDesc) { - PRUint32 dummy; - rv = aOutput->Write(kDescriptionIntro, sizeof(kDescriptionIntro)-1, &dummy); - if (NS_FAILED(rv)) { - nsMemory::Free(escapedDesc); - return rv; - } - rv = aOutput->Write(escapedDesc, strlen(escapedDesc), &dummy); - nsMemory::Free(escapedDesc); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kDescriptionClose, sizeof(kDescriptionClose)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - return NS_OK; -} - -// nsBookmarks::WriteItem -// -// "
    Name" -nsresult -nsPlacesExportService::WriteItem(nsINavHistoryResultNode* aItem, - const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - // before doing any attempt to write the item check that uri is valid, if the - // item has a bad uri we skip it silently, otherwise we could stop while - // exporting, generating a corrupt file. - nsCAutoString uri; - nsresult rv = aItem->GetUri(uri); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr pageURI; - rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull); - if (NS_FAILED(rv)) { - nsCAutoString warnMsg; - warnMsg.Append("Bookmarks Export: Found invalid item uri '"); - warnMsg.Append(uri); - warnMsg.Append("'"); - NS_WARNING(warnMsg.get()); - return NS_OK; - } - - // indent - PRUint32 dummy; - if (!aIndent.IsEmpty()) { - rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // '
    Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // ' HREF="http://..."' - note that we need to call GetURI on the result - // node because some nodes (eg queries) generate this lazily. - rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteEscapedUrl(uri, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // write ADD_DATE - PRTime dateAdded = 0; - rv = aItem->GetDateAdded(&dateAdded); - NS_ENSURE_SUCCESS(rv, rv); - - if (dateAdded) { - rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - } - - // write LAST_MODIFIED - PRTime lastModified = 0; - rv = aItem->GetLastModified(&lastModified); - NS_ENSURE_SUCCESS(rv, rv); - - if (lastModified) { - rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - } - - // ' ICON="..."' - rv = WriteFaviconAttribute(uri, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - // get item id - PRInt64 itemId; - rv = aItem->GetItemId(&itemId); - NS_ENSURE_SUCCESS(rv, rv); - - // keyword (shortcuturl) - nsAutoString keyword; - rv = mBookmarksService->GetKeywordForBookmark(itemId, keyword); - NS_ENSURE_SUCCESS(rv, rv); - if (!keyword.IsEmpty()) { - rv = aOutput->Write(kKeywordAttribute, sizeof(kKeywordAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - char* escapedKeyword = nsEscapeHTML(NS_ConvertUTF16toUTF8(keyword).get()); - rv = aOutput->Write(escapedKeyword, strlen(escapedKeyword), &dummy); - nsMemory::Free(escapedKeyword); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // post data - bool hasPostData; - rv = mAnnotationService->ItemHasAnnotation(itemId, POST_DATA_ANNO, - &hasPostData); - NS_ENSURE_SUCCESS(rv, rv); - if (hasPostData) { - nsAutoString postData; - rv = mAnnotationService->GetItemAnnotationString(itemId, POST_DATA_ANNO, - postData); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kPostDataAttribute, sizeof(kPostDataAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - char* escapedPostData = nsEscapeHTML(NS_ConvertUTF16toUTF8(postData).get()); - rv = aOutput->Write(escapedPostData, strlen(escapedPostData), &dummy); - nsMemory::Free(escapedPostData); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Write WEB_PANEL="true" if the load-in-sidebar annotation is set for the - // item - bool loadInSidebar = false; - rv = mAnnotationService->ItemHasAnnotation(itemId, LOAD_IN_SIDEBAR_ANNO, - &loadInSidebar); - NS_ENSURE_SUCCESS(rv, rv); - if (loadInSidebar) - aOutput->Write(kWebPanelAttribute, sizeof(kWebPanelAttribute)-1, &dummy); - - // last charset - nsAutoString lastCharset; - if (NS_SUCCEEDED(mHistoryService->GetCharsetForURI(pageURI, lastCharset)) && - !lastCharset.IsEmpty()) { - rv = aOutput->Write(kLastCharsetAttribute, sizeof(kLastCharsetAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - char* escapedLastCharset = nsEscapeHTML(NS_ConvertUTF16toUTF8(lastCharset).get()); - rv = aOutput->Write(escapedLastCharset, strlen(escapedLastCharset), &dummy); - nsMemory::Free(escapedLastCharset); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // '>' - rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // title - nsCAutoString title; - rv = aItem->GetTitle(title); - NS_ENSURE_SUCCESS(rv, rv); - char* escapedTitle = nsEscapeHTML(title.get()); - if (escapedTitle) { - rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); - nsMemory::Free(escapedTitle); - NS_ENSURE_SUCCESS(rv, rv); - } - - // '\n' - rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // description - rv = WriteDescription(itemId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - - -// WriteLivemark -// -// Similar to WriteItem, this has an additional FEEDURL attribute and -// the HREF is optional and points to the source page. -nsresult -nsPlacesExportService::WriteLivemark(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - PRUint32 dummy; - nsresult rv; - - // indent - if (!aIndent.IsEmpty()) { - rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // '
    Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // get folder id - PRInt64 folderId; - rv = aFolder->GetItemId(&folderId); - NS_ENSURE_SUCCESS(rv, rv); - - // get feed URI - nsString feedSpec; - rv = mAnnotationService->GetItemAnnotationString(folderId, - NS_LITERAL_CSTRING(LMANNO_FEEDURI), - feedSpec); - - NS_ENSURE_SUCCESS(rv, rv); - - // write feed URI - rv = aOutput->Write(kFeedURIAttribute, sizeof(kFeedURIAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(feedSpec), aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // get the optional site URI - nsString siteSpec; - rv = mAnnotationService->GetItemAnnotationString(folderId, - NS_LITERAL_CSTRING(LMANNO_SITEURI), - siteSpec); - if (NS_SUCCEEDED(rv) && !siteSpec.IsEmpty()) { - // write site URI - rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(siteSpec), aOutput); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - // '>' - rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // title - rv = WriteTitle(aFolder, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - // '\n' - rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // description - rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - - -// nsPlacesExportService::WriteSeparator -// -// "
    " -nsresult -nsPlacesExportService::WriteSeparator(nsINavHistoryResultNode* aItem, - const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - PRUint32 dummy; - nsresult rv; - - // indent - if (!aIndent.IsEmpty()) { - rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), - &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - - rv = aOutput->Write(kSeparator, sizeof(kSeparator)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // XXX: separator result nodes don't support the title getter yet - PRInt64 itemId; - rv = aItem->GetItemId(&itemId); - NS_ENSURE_SUCCESS(rv, rv); - - // Note: we can't write the separator ID or anything else other than NAME - // because it makes Firefox 2.x crash/hang - see bug #381129 - - nsCAutoString title; - rv = mBookmarksService->GetItemTitle(itemId, title); - if (NS_SUCCEEDED(rv) && !title.IsEmpty()) { - rv = aOutput->Write(kNameAttribute, strlen(kNameAttribute), &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - char* escapedTitle = nsEscapeHTML(title.get()); - if (escapedTitle) { - PRUint32 dummy; - rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); - nsMemory::Free(escapedTitle); - NS_ENSURE_SUCCESS(rv, rv); - rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - } - } - - // '>' - rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // line break - rv = aOutput->Write(NS_LINEBREAK, sizeof(NS_LINEBREAK)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - return rv; -} - - -// WriteEscapedUrl -// -// Writes the given string to the stream escaped as necessary for URLs. -// -// Unfortunately, the old bookmarks system uses a custom hardcoded and -// braindead escaping scheme that we need to emulate. It just replaces -// quotes with %22 and that's it. -nsresult -WriteEscapedUrl(const nsCString& aString, nsIOutputStream* aOutput) -{ - nsCAutoString escaped(aString); - PRInt32 offset; - while ((offset = escaped.FindChar('\"')) >= 0) { - escaped.Cut(offset, 1); - escaped.Insert(NS_LITERAL_CSTRING("%22"), offset); - } - PRUint32 dummy; - return aOutput->Write(escaped.get(), escaped.Length(), &dummy); -} - - -// nsPlacesExportService::WriteContainerContents -// -// The indent here is the indent of the parent. We will add an additional -// indent before writing data. -nsresult -nsPlacesExportService::WriteContainerContents(nsINavHistoryResultNode* aFolder, - const nsACString& aIndent, - nsIOutputStream* aOutput) -{ - nsCAutoString myIndent(aIndent); - myIndent.Append(kIndent); - - PRInt64 folderId; - nsresult rv = aFolder->GetItemId(&folderId); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr folderNode = do_QueryInterface(aFolder, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - rv = folderNode->SetContainerOpen(true); - NS_ENSURE_SUCCESS(rv, rv); - - PRUint32 childCount = 0; - folderNode->GetChildCount(&childCount); - for (PRUint32 i = 0; i < childCount; ++i) { - nsCOMPtr child; - rv = folderNode->GetChild(i, getter_AddRefs(child)); - NS_ENSURE_SUCCESS(rv, rv); - PRUint32 type = 0; - rv = child->GetType(&type); - NS_ENSURE_SUCCESS(rv, rv); - if (type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER) { - // bookmarks folder - PRInt64 childFolderId; - rv = child->GetItemId(&childFolderId); - NS_ENSURE_SUCCESS(rv, rv); - - // it could be a regular folder or it could be a livemark. - // Livemarks service is async, for now just workaround using annotations - // service. - bool isLivemark; - nsresult rv = mAnnotationService->ItemHasAnnotation(childFolderId, - NS_LITERAL_CSTRING(LMANNO_FEEDURI), - &isLivemark); - NS_ENSURE_SUCCESS(rv, rv); - - if (isLivemark) - rv = WriteLivemark(child, myIndent, aOutput); - else - rv = WriteContainer(child, myIndent, aOutput); - } - else if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) { - rv = WriteSeparator(child, myIndent, aOutput); - } - else { - rv = WriteItem(child, myIndent, aOutput); - } - NS_ENSURE_SUCCESS(rv, rv); - } - return NS_OK; -} - - - -NS_IMETHODIMP -nsPlacesExportService::ExportHTMLToFile(nsILocalFile* aBookmarksFile) -{ - NS_ENSURE_ARG(aBookmarksFile); - -#ifdef DEBUG_EXPORT - nsAutoString path; - aBookmarksFile->GetPath(path); - printf("\nExporting %s\n", NS_ConvertUTF16toUTF8(path).get()); - - PRTime startTime = PR_Now(); - printf("\nStart time: %lld\n", startTime); -#endif - - nsresult rv = EnsureServiceState(); - NS_ENSURE_SUCCESS(rv, rv); - - // get a safe output stream, so we don't clobber the bookmarks file unless - // all the writes succeeded. - nsCOMPtr out; - rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), - aBookmarksFile, - PR_WRONLY | PR_CREATE_FILE, - 0600, 0); - NS_ENSURE_SUCCESS(rv, rv); - - // We need a buffered output stream for performance. - // See bug 202477. - nsCOMPtr strm; - rv = NS_NewBufferedOutputStream(getter_AddRefs(strm), out, 4096); - NS_ENSURE_SUCCESS(rv, rv); - - // Get a new query object. - nsCOMPtr query; - rv = mHistoryService->GetNewQuery(getter_AddRefs(query)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr options; - rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(options)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr result; - - // We need the bookmarks menu root node to write out the title. - PRInt64 bookmarksMenuFolder; - rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); - NS_ENSURE_SUCCESS(rv, rv); - rv = query->SetFolders(&bookmarksMenuFolder, 1); - NS_ENSURE_SUCCESS(rv, rv); - rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr rootNode; - rv = result->GetRoot(getter_AddRefs(rootNode)); - NS_ENSURE_SUCCESS(rv, rv); - - // file header - PRUint32 dummy; - rv = strm->Write(kFileIntro, sizeof(kFileIntro)-1, &dummy); - NS_ENSURE_SUCCESS(rv, rv); - - // 'Write(kRootIntro, sizeof(kRootIntro)-1, &dummy); //

    Bookmarks

    - rv = strm->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); // > - NS_ENSURE_SUCCESS(rv, rv); - rv = WriteTitle(rootNode, strm); - NS_ENSURE_SUCCESS(rv, rv); - rv = strm->Write(kCloseRootH1, sizeof(kCloseRootH1)-1, &dummy); //

    - NS_ENSURE_SUCCESS(rv, rv); - - // Container's prologue. - rv = WriteContainerPrologue(EmptyCString(), strm); - NS_ENSURE_SUCCESS(rv, rv); - - // indents - nsCAutoString indent; - indent.Assign(kIndent); - - // Bookmarks Menu. - rv = WriteContainerContents(rootNode, EmptyCString(), strm); - NS_ENSURE_SUCCESS(rv, rv); - - // Bookmarks Toolbar. - // We write this folder under the bookmarks-menu for backwards compatibility. - PRInt64 toolbarFolder; - rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); - NS_ENSURE_SUCCESS(rv, rv); - rv = query->SetFolders(&toolbarFolder, 1); - NS_ENSURE_SUCCESS(rv, rv); - rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); - NS_ENSURE_SUCCESS(rv, rv); - rv = result->GetRoot(getter_AddRefs(rootNode)); - NS_ENSURE_SUCCESS(rv, rv); - // Write it out only if it's not empty. - rv = rootNode->SetContainerOpen(true); - NS_ENSURE_SUCCESS(rv, rv); - PRUint32 childCount = 0; - rv = rootNode->GetChildCount(&childCount); - NS_ENSURE_SUCCESS(rv, rv); - rv = rootNode->SetContainerOpen(false); - NS_ENSURE_SUCCESS(rv, rv); - if (childCount) { - rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Unfiled Bookmarks. - // We write this folder under the bookmarks-menu for backwards compatibility. - PRInt64 unfiledBookmarksFolder; - rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder); - NS_ENSURE_SUCCESS(rv, rv); - rv = query->SetFolders(&unfiledBookmarksFolder, 1); - NS_ENSURE_SUCCESS(rv, rv); - rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); - NS_ENSURE_SUCCESS(rv, rv); - rv = result->GetRoot(getter_AddRefs(rootNode)); - NS_ENSURE_SUCCESS(rv, rv); - // Write it out only if it's not empty. - rv = rootNode->SetContainerOpen(true); - NS_ENSURE_SUCCESS(rv, rv); - childCount = 0; - rootNode->GetChildCount(&childCount); - NS_ENSURE_SUCCESS(rv, rv); - rv = rootNode->SetContainerOpen(false); - NS_ENSURE_SUCCESS(rv, rv); - if (childCount) { - rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Container's epilogue. - rv = WriteContainerEpilogue(EmptyCString(), strm); - NS_ENSURE_SUCCESS(rv, rv); - - // commit the write - nsCOMPtr safeStream = do_QueryInterface(strm, &rv); - NS_ENSURE_SUCCESS(rv, rv); - rv = safeStream->Finish(); - NS_ENSURE_SUCCESS(rv, rv); - -#ifdef DEBUG_EXPORT - printf("\nTotal time in seconds: %lld\n", (PR_Now() - startTime)/1000000); -#endif - - return NS_OK; -} - - -NS_IMETHODIMP -nsPlacesExportService::BackupBookmarksFile() -{ - nsresult rv = EnsureServiceState(); - NS_ENSURE_SUCCESS(rv, rv); - - // get bookmarks file - nsCOMPtr bookmarksFileDir; - rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE, - getter_AddRefs(bookmarksFileDir)); - - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr bookmarksFile = do_QueryInterface(bookmarksFileDir); - NS_ENSURE_STATE(bookmarksFile); - - // Create the file if it doesn't exist. - bool exists; - rv = bookmarksFile->Exists(&exists); - if (NS_FAILED(rv) || !exists) { - rv = bookmarksFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600); - if (NS_FAILED(rv)) { - NS_WARNING("Unable to create bookmarks.html!"); - return rv; - } - } - - // export bookmarks.html - rv = ExportHTMLToFile(bookmarksFile); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} diff --git a/toolkit/components/places/nsPlacesImportExportService.cpp b/toolkit/components/places/nsPlacesImportExportService.cpp new file mode 100644 index 00000000000..1c4897c503e --- /dev/null +++ b/toolkit/components/places/nsPlacesImportExportService.cpp @@ -0,0 +1,2459 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla History System + * + * The Initial Developer of the Original Code is + * Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Brett Wilson + * Dietrich Ayala + * Drew Willcoxon + * Marco Bonardo + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Importer/exporter between the mozStorage-based bookmarks and the old-style + * "bookmarks.html" + * + * Format: + * + * Primary heading := h1 + * Old version used this to set attributes on the bookmarks RDF root, such + * as the last modified date. We only use H1 to check for the attribute + * PLACES_ROOT, which tells us that this hierarchy root is the places root. + * For backwards compatibility, if we don't find this, we assume that the + * hierarchy is rooted at the bookmarks menu. + * Heading := any heading other than h1 + * Old version used this to set attributes on the current container. We only + * care about the content of the heading container, which contains the title + * of the bookmark container. + * Bookmark := a + * HREF is the destination of the bookmark + * FEEDURL is the URI of the RSS feed if this is a livemark. + * LAST_CHARSET is stored as an annotation so that the next time we go to + * that page we remember the user's preference. + * WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar. + * ICON will be stored in the favicon service + * ICON_URI is new for places bookmarks.html, it refers to the original + * URI of the favicon so we don't have to make up favicon URLs. + * Text of the container is the name of the bookmark + * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2) + * Bookmark comment := dd + * This affects the previosly added bookmark + * Separator := hr + * Insert a separator into the current container + * The folder hierarchy is defined by
    /
      / (the old importing code + * handles all these cases, when we write, use
      ). + * + * Overall design + * -------------- + * + * We need to emulate a recursive parser. A "Bookmark import frame" is created + * corresponding to each folder we encounter. These are arranged in a stack, + * and contain all the state we need to keep track of. + * + * A frame is created when we find a heading, which defines a new container. + * The frame also keeps track of the nesting of
      s, (in well-formed + * bookmarks files, these will have a 1-1 correspondence with frames, but we + * try to be a little more flexible here). When the nesting count decreases + * to 0, then we know a frame is complete and to pop back to the previous + * frame. + * + * Note that a lot of things happen when tags are CLOSED because we need to + * get the text from the content of the tag. For example, link and heading tags + * both require the content (= title) before actually creating it. + */ + +#include "nsPlacesImportExportService.h" +#include "nsNetUtil.h" +#include "nsParserCIID.h" +#include "nsUnicharUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsToolkitCompsCID.h" +#include "nsIHTMLContentSink.h" +#include "nsIParser.h" +#include "prprf.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsPlacesMacros.h" +#include "mozilla/Util.h" +#include "Helpers.h" + +using namespace mozilla; +using namespace mozilla::places; + +static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); + +#define KEY_TOOLBARFOLDER_LOWER "personal_toolbar_folder" +#define KEY_BOOKMARKSMENU_LOWER "bookmarks_menu" +#define KEY_UNFILEDFOLDER_LOWER "unfiled_bookmarks_folder" +#define KEY_PLACESROOT_LOWER "places_root" +#define KEY_HREF_LOWER "href" +#define KEY_FEEDURL_LOWER "feedurl" +#define KEY_WEB_PANEL_LOWER "web_panel" +#define KEY_LASTCHARSET_LOWER "last_charset" +#define KEY_ICON_LOWER "icon" +#define KEY_ICON_URI_LOWER "icon_uri" +#define KEY_SHORTCUTURL_LOWER "shortcuturl" +#define KEY_POST_DATA_LOWER "post_data" +#define KEY_NAME_LOWER "name" +#define KEY_MICSUM_GEN_URI_LOWER "micsum_gen_uri" +#define KEY_DATE_ADDED_LOWER "add_date" +#define KEY_LAST_MODIFIED_LOWER "last_modified" +#define KEY_GENERATED_TITLE_LOWER "generated_title" + +#define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar") +#define DESCRIPTION_ANNO NS_LITERAL_CSTRING("bookmarkProperties/description") +#define POST_DATA_ANNO NS_LITERAL_CSTRING("bookmarkProperties/POSTData") + +#define BOOKMARKS_MENU_ICON_URI "chrome://browser/skin/places/bookmarksMenu.png" + +// The RESTORE_*_NSIOBSERVER_TOPIC #defines should match the constants of the +// same names in toolkit/components/places/src/utils.js +#define RESTORE_BEGIN_NSIOBSERVER_TOPIC "bookmarks-restore-begin" +#define RESTORE_SUCCESS_NSIOBSERVER_TOPIC "bookmarks-restore-success" +#define RESTORE_FAILED_NSIOBSERVER_TOPIC "bookmarks-restore-failed" +#define RESTORE_NSIOBSERVER_DATA NS_LITERAL_STRING("html") +#define RESTORE_INITIAL_NSIOBSERVER_DATA NS_LITERAL_STRING("html-initial") + +#define LMANNO_FEEDURI "livemark/feedURI" +#define LMANNO_SITEURI "livemark/siteURI" + +// define to get debugging messages on console about import/export +//#define DEBUG_IMPORT +//#define DEBUG_EXPORT + +#if defined(XP_WIN) || defined(XP_OS2) +#define NS_LINEBREAK "\015\012" +#else +#define NS_LINEBREAK "\012" +#endif + +class nsIOutputStream; +static const char kWhitespace[] = " \r\n\t\b"; +static nsresult WriteEscapedUrl(const nsCString &aString, nsIOutputStream* aOutput); + +class BookmarkImportFrame +{ +public: + BookmarkImportFrame(PRInt64 aID) : + mContainerID(aID), + mContainerNesting(0), + mLastContainerType(Container_Normal), + mInDescription(false), + mPreviousId(0), + mPreviousDateAdded(0), + mPreviousLastModifiedDate(0) + { + } + + enum ContainerType { Container_Normal, + Container_Places, + Container_Menu, + Container_Toolbar, + Container_Unfiled}; + + PRInt64 mContainerID; + + // How many
      s have been nested. Each frame/container should start + // with a heading, and is then followed by a
      ,
        , or . When + // that list is complete, then it is the end of this container and we need + // to pop back up one level for new items. If we never get an open tag for + // one of these things, we should assume that the container is empty and + // that things we find should be siblings of it. Normally, these
        s won't + // be nested so this will be 0 or 1. + PRInt32 mContainerNesting; + + // when we find a heading tag, it actually affects the title of the NEXT + // container in the list. This stores that heading tag and whether it was + // special. 'ConsumeHeading' resets this. + ContainerType mLastContainerType; + + // this contains the text from the last begin tag until now. It is reset + // at every begin tag. We can check it when we see a , or + // to see what the text content of that node should be. + nsString mPreviousText; + + // true when we hit a
        , which contains the description for the preceding + // tag. We can't just check for
        like we can for or + // because if there is a sub-folder, it is actually a child of the
        + // because the tag is never explicitly closed. If this is true and we see a + // new open tag, that means to commit the description to the previous + // bookmark. + // + // Additional weirdness happens when the previous
        tag contains a

        : + // this means there is a new folder with the given description, and whose + // children are contained in the following
        list. + // + // This is handled in OpenContainer(), which commits previous text if + // necessary. + bool mInDescription; + + // contains the URL of the previous bookmark created. This is used so that + // when we encounter a
        , we know what bookmark to associate the text with. + // This is cleared whenever we hit a

        , so that we know NOT to save this + // with a bookmark, but to keep it until + nsCOMPtr mPreviousLink; + + // contains the URL of the previous livemark, so that when the link ends, + // and the livemark title is known, we can create it. + nsCOMPtr mPreviousFeed; + + void ConsumeHeading(nsAString* aHeading, ContainerType* aContainerType) + { + *aHeading = mPreviousText; + *aContainerType = mLastContainerType; + mPreviousText.Truncate(); + } + + // Contains the id of an imported, or newly created bookmark. + PRInt64 mPreviousId; + + // Contains the date-added and last-modified-date of an imported item. + // Used to override the values set by insertBookmark, createFolder, etc. + PRTime mPreviousDateAdded; + PRTime mPreviousLastModifiedDate; +}; + +/** + * Copied from nsEscape.cpp, which requires internal string API. + */ +char* +nsEscapeHTML(const char* string) +{ + /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ + char* escaped = nsnull; + PRUint32 len = strlen(string); + if (len >= (PR_UINT32_MAX / 6)) + return nsnull; + + escaped = (char*)NS_Alloc((len * 6) + 1); + if (escaped) { + char* ptr = escaped; + for (; *string != '\0'; string++) { + switch(*string) { + case '<': + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + break; + case '>': + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + break; + case '&': + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + break; + case '"': + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + break; + case '\'': + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + break; + default: + *ptr++ = *string; + } + } + *ptr = '\0'; + } + return escaped; +} + +PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsPlacesImportExportService, gImportExportService) + +NS_IMPL_ISUPPORTS2(nsPlacesImportExportService, nsIPlacesImportExportService, + nsINavHistoryBatchCallback) + + +nsPlacesImportExportService::nsPlacesImportExportService() +{ + NS_ASSERTION(!gImportExportService, + "Attempting to create two instances of the service!"); + gImportExportService = this; +} + +nsPlacesImportExportService::~nsPlacesImportExportService() +{ + NS_ASSERTION(gImportExportService == this, + "Deleting a non-singleton instance of the service"); + if (gImportExportService == this) + gImportExportService = nsnull; +} + +nsresult +nsPlacesImportExportService::Init() +{ + // Be sure to call EnsureServiceState() before using services. + mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mHistoryService, NS_ERROR_OUT_OF_MEMORY); + mFaviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mFaviconService, NS_ERROR_OUT_OF_MEMORY); + mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mAnnotationService, NS_ERROR_OUT_OF_MEMORY); + mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mBookmarksService, NS_ERROR_OUT_OF_MEMORY); + mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mLivemarkService, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; +} + +/** + * The content sink stuff is based loosely on nsIHTMLContentSink. + */ +class BookmarkContentSink : public nsIHTMLContentSink +{ +public: + BookmarkContentSink(); + + nsresult Init(bool aAllowRootChanges, + PRInt64 aFolder, + bool aIsImportDefaults); + + NS_DECL_ISUPPORTS + + // nsIContentSink (superclass of nsIHTMLContentSink) + NS_IMETHOD WillParse() { return NS_OK; } + NS_IMETHOD WillInterrupt() { return NS_OK; } + NS_IMETHOD WillResume() { return NS_OK; } + NS_IMETHOD SetParser(nsParserBase* aParser) { return NS_OK; } + virtual void FlushPendingNotifications(mozFlushType aType) { } + NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; } + virtual nsISupports *GetTarget() { return nsnull; } + + // nsIHTMLContentSink + NS_IMETHOD OpenHead() { return NS_OK; } + NS_IMETHOD BeginContext(PRInt32 aPosition) { return NS_OK; } + NS_IMETHOD EndContext(PRInt32 aPosition) { return NS_OK; } + NS_IMETHOD IsEnabled(PRInt32 aTag, bool* aReturn) + { *aReturn = true; return NS_OK; } + NS_IMETHOD DidProcessTokens() { return NS_OK; } + NS_IMETHOD WillProcessAToken() { return NS_OK; } + NS_IMETHOD DidProcessAToken() { return NS_OK; } + NS_IMETHOD OpenContainer(const nsIParserNode& aNode); + NS_IMETHOD CloseContainer(const nsHTMLTag aTag); + NS_IMETHOD AddLeaf(const nsIParserNode& aNode); + NS_IMETHOD NotifyTagObservers(nsIParserNode* aNode) { return NS_OK; } + +protected: + nsCOMPtr mBookmarksService; + nsCOMPtr mHistoryService; + nsCOMPtr mAnnotationService; + nsCOMPtr mLivemarkService; + + // If set, we will move root items to from their existing position + // in the hierarchy, to where we find them in the bookmarks file + // being imported. This should be set when we are loading + // the default places html file, and should be unset when doing + // normal imports so that root folders will not get moved when + // importing bookmarks.html files. + bool mAllowRootChanges; + + // If set, this is an import of initial bookmarks.html content, + // so we don't want to kick off HTTP traffic + // and we want the imported personal toolbar folder + // to be set as the personal toolbar folder. (If not set + // we will treat it as a normal folder.) + bool mIsImportDefaults; + + // If a folder was specified to import into, then ignore flags to put + // bookmarks in the bookmarks menu or toolbar and keep them inside + // the folder. + bool mFolderSpecified; + + void HandleContainerBegin(const nsIParserNode& node); + void HandleContainerEnd(); + void HandleHead1Begin(const nsIParserNode& node); + void HandleHeadBegin(const nsIParserNode& node); + void HandleHeadEnd(); + void HandleLinkBegin(const nsIParserNode& node); + void HandleLinkEnd(); + void HandleSeparator(const nsIParserNode& node); + + // This is a list of frames. We really want a recursive parser, but the HTML + // parser gives us tags as a stream. This implements all the state on a stack + // so we can get the recursive information we need. Use "CurFrame" to get the + // top "stack frame" with the current state in it. + nsTArray mFrames; + BookmarkImportFrame& CurFrame() + { + NS_ASSERTION(mFrames.Length() > 0, "Asking for frame when there are none!"); + return mFrames[mFrames.Length() - 1]; + } + BookmarkImportFrame& PreviousFrame() + { + NS_ASSERTION(mFrames.Length() > 1, "Asking for frame when there are not enough!"); + return mFrames[mFrames.Length() - 2]; + } + nsresult NewFrame(); + nsresult PopFrame(); + + nsresult SetFaviconForURI(nsIURI* aPageURI, nsIURI* aFaviconURI, + const nsString& aData); + + PRTime ConvertImportedDateToInternalDate(const nsACString& aDate); + +#ifdef DEBUG_IMPORT + // prints spaces for indenting to the current frame depth + void PrintNesting() + { + for (PRUint32 i = 0; i < mFrames.Length(); i ++) + printf(" "); + } +#endif +}; + + +BookmarkContentSink::BookmarkContentSink() : mFrames(16) +{ +} + + +nsresult +BookmarkContentSink::Init(bool aAllowRootChanges, + PRInt64 aFolder, + bool aIsImportDefaults) +{ + mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mBookmarksService, NS_ERROR_OUT_OF_MEMORY); + mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mHistoryService, NS_ERROR_OUT_OF_MEMORY); + mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mAnnotationService, NS_ERROR_OUT_OF_MEMORY); + mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID); + NS_ENSURE_TRUE(mLivemarkService, NS_ERROR_OUT_OF_MEMORY); + + mAllowRootChanges = aAllowRootChanges; + mIsImportDefaults = aIsImportDefaults; + + // initialize the root frame with the menu root + PRInt64 menuRoot; + nsresult rv; + if (aFolder == 0) { + rv = mBookmarksService->GetBookmarksMenuFolder(&menuRoot); + NS_ENSURE_SUCCESS(rv, rv); + mFolderSpecified = false; + } + else { + menuRoot = aFolder; + mFolderSpecified = true; + } + if (!mFrames.AppendElement(BookmarkImportFrame(menuRoot))) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +NS_IMPL_ISUPPORTS2(BookmarkContentSink, + nsIContentSink, + nsIHTMLContentSink) + + +NS_IMETHODIMP +BookmarkContentSink::OpenContainer(const nsIParserNode& aNode) +{ + switch(aNode.GetNodeType()) { + case eHTMLTag_h1: + HandleHead1Begin(aNode); + break; + case eHTMLTag_h2: + case eHTMLTag_h3: + case eHTMLTag_h4: + case eHTMLTag_h5: + case eHTMLTag_h6: + HandleHeadBegin(aNode); + break; + case eHTMLTag_a: + HandleLinkBegin(aNode); + break; + case eHTMLTag_dl: + case eHTMLTag_ul: + case eHTMLTag_menu: + HandleContainerBegin(aNode); + break; + case eHTMLTag_dd: + CurFrame().mInDescription = true; + break; + } + return NS_OK; +} + + +NS_IMETHODIMP +BookmarkContentSink::CloseContainer(const nsHTMLTag aTag) +{ + BookmarkImportFrame& frame = CurFrame(); + + // see the comment for the definition of mInDescription. Basically, we commit + // any text in mPreviousText to the description of the node/folder if there + // is any. + if (frame.mInDescription) { + frame.mPreviousText.Trim(kWhitespace); // important! + if (!frame.mPreviousText.IsEmpty()) { + + PRInt64 itemId = !frame.mPreviousLink ? frame.mContainerID + : frame.mPreviousId; + + bool hasDescription = false; + nsresult rv = mAnnotationService->ItemHasAnnotation(itemId, + DESCRIPTION_ANNO, + &hasDescription); + if (NS_SUCCEEDED(rv) && !hasDescription) { + mAnnotationService->SetItemAnnotationString(itemId, DESCRIPTION_ANNO, + frame.mPreviousText, 0, + nsIAnnotationService::EXPIRE_NEVER); + } + frame.mPreviousText.Truncate(); + + // Set last-modified a 2nd time for all items with descriptions + // we need to set last-modified as the *last* step in processing + // any item type in the bookmarks.html file, so that we do + // not overwrite the imported value. for items without descriptions, + // setting this value after setting the item title is that + // last point at which we can save this value before it gets reset. + // for items with descriptions, it must set after that point. + // however, at the point at which we set the title, there's no way + // to determine if there will be a description following, + // so we need to set the last-modified-date at both places. + + PRTime lastModified; + if (!frame.mPreviousLink) { + lastModified = PreviousFrame().mPreviousLastModifiedDate; + } else { + lastModified = frame.mPreviousLastModifiedDate; + } + + if (itemId > 0 && lastModified > 0) { + rv = mBookmarksService->SetItemLastModified(itemId, lastModified); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed"); + } + } + frame.mInDescription = false; + } + + switch (aTag) { + case eHTMLTag_dl: + case eHTMLTag_ul: + case eHTMLTag_menu: + HandleContainerEnd(); + break; + case eHTMLTag_dt: + break; + case eHTMLTag_h1: + // ignore + break; + case eHTMLTag_h2: + case eHTMLTag_h3: + case eHTMLTag_h4: + case eHTMLTag_h5: + case eHTMLTag_h6: + HandleHeadEnd(); + break; + case eHTMLTag_a: + HandleLinkEnd(); + break; + default: + break; + } + return NS_OK; +} + + +// BookmarkContentSink::AddLeaf +// +// XXX on the branch, we should be calling CollectSkippedContent as in +// nsHTMLFragmentContentSink.cpp:AddLeaf when we encounter title, script, +// style, or server tags. Apparently if we don't, we'll leak the next DOM +// node. However, this requires that we keep a reference to the parser we'll +// introduce a circular reference because it has a reference to us. +// +// This is annoying to fix and these elements are not allowed in bookmarks +// files anyway. So if somebody tries to import a crazy bookmarks file, it +// will leak a little bit. + +NS_IMETHODIMP +BookmarkContentSink::AddLeaf(const nsIParserNode& aNode) +{ + switch (aNode.GetNodeType()) { + case eHTMLTag_text: + // save any text we find + CurFrame().mPreviousText += aNode.GetText(); + break; + case eHTMLTag_entity: { + nsAutoString tmp; + PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp); + if (unicode < 0) { + // invalid entity - just use the text of it + CurFrame().mPreviousText += aNode.GetText(); + } else { + CurFrame().mPreviousText.Append(unicode); + } + break; + } + case eHTMLTag_whitespace: + CurFrame().mPreviousText.Append(PRUnichar(' ')); + break; + case eHTMLTag_hr: + HandleSeparator(aNode); + break; + } + + return NS_OK; +} + + +void +BookmarkContentSink::HandleContainerBegin(const nsIParserNode& node) +{ + CurFrame().mContainerNesting ++; +} + + +// BookmarkContentSink::HandleContainerEnd +// +// Our "indent" count has decreased, and when we hit 0 that means that this +// container is complete and we need to pop back to the outer frame. Never +// pop the toplevel frame + +void +BookmarkContentSink::HandleContainerEnd() +{ + BookmarkImportFrame& frame = CurFrame(); + if (frame.mContainerNesting > 0) + frame.mContainerNesting --; + if (mFrames.Length() > 1 && frame.mContainerNesting == 0) { + // we also need to re-set the imported last-modified date here. Otherwise + // the addition of items will override the imported field. + BookmarkImportFrame& prevFrame = PreviousFrame(); + if (prevFrame.mPreviousLastModifiedDate > 0) { + (void)mBookmarksService->SetItemLastModified(frame.mContainerID, + prevFrame.mPreviousLastModifiedDate); + } + PopFrame(); + } +} + + +// BookmarkContentSink::HandleHead1Begin +// +// Handles

        . We check for the attribute PLACES_ROOT and reset the +// container id if it's found. Otherwise, the default bookmark menu +// root is assumed and imported things will go into the bookmarks menu. + +void +BookmarkContentSink::HandleHead1Begin(const nsIParserNode& node) +{ + PRInt32 attrCount = node.GetAttributeCount(); + for (PRInt32 i = 0; i < attrCount; i ++) { + if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) { + if (mFrames.Length() > 1) { + NS_WARNING("Trying to set the places root from the middle of the hierarchy. " + "This can only be set at the beginning."); + return; + } + + PRInt64 placesRoot; + DebugOnly rv = mBookmarksService->GetPlacesRoot(&placesRoot); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "could not get placesRoot"); + CurFrame().mContainerID = placesRoot; + break; + } + } +} + + +// BookmarkContentSink::HandleHeadBegin +// +// Called for h2,h3,h4,h5,h6. This just stores the correct information in +// the current frame; the actual new frame corresponding to the container +// associated with the heading will be created when the tag has been closed +// and we know the title (we don't know to create a new folder or to merge +// with an existing one until we have the title). + +void +BookmarkContentSink::HandleHeadBegin(const nsIParserNode& node) +{ + BookmarkImportFrame& frame = CurFrame(); + + // after a heading, a previous bookmark is not applicable (for example, for + // the descriptions contained in a
        ). Neither is any previous head type + frame.mPreviousLink = nsnull; + frame.mLastContainerType = BookmarkImportFrame::Container_Normal; + + // It is syntactically possible for a heading to appear after another heading + // but before the
        that encloses that folder's contents. This should not + // happen in practice, as the file will contain "
        " sequence for + // empty containers. + // + // Just to be on the safe side, if we encounter + //

        FOO

        + //

        BAR

        + //
        ...content 1...
        + //
        ...content 2...
        + // we'll pop the stack when we find the h3 for BAR, treating that as an + // implicit ending of the FOO container. The output will be FOO and BAR as + // siblings. If there's another
        following (as in "content 2"), those + // items will be treated as further siblings of FOO and BAR + if (frame.mContainerNesting == 0) + PopFrame(); + + // We have to check for some attributes to see if this is a "special" + // folder, which will have different creation rules when the end tag is + // processed. + PRInt32 attrCount = node.GetAttributeCount(); + frame.mLastContainerType = BookmarkImportFrame::Container_Normal; + for (PRInt32 i = 0; i < attrCount; ++i) { + if (!mFolderSpecified) { + if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_TOOLBARFOLDER_LOWER)) { + if (mIsImportDefaults) + frame.mLastContainerType = BookmarkImportFrame::Container_Toolbar; + break; + } + else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_BOOKMARKSMENU_LOWER)) { + if (mIsImportDefaults) + frame.mLastContainerType = BookmarkImportFrame::Container_Menu; + break; + } + else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_UNFILEDFOLDER_LOWER)) { + if (mIsImportDefaults) + frame.mLastContainerType = BookmarkImportFrame::Container_Unfiled; + break; + } + else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) { + if (mIsImportDefaults) + frame.mLastContainerType = BookmarkImportFrame::Container_Places; + break; + } + } + + if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER)) { + frame.mPreviousDateAdded = + ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i))); + } + else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER)) { + frame.mPreviousLastModifiedDate = + ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i))); + } + } + CurFrame().mPreviousText.Truncate(); +} + + +// BookmarkContentSink::HandleHeadEnd +// +// Creates the new frame for this heading now that we know the name of the +// container (tokens since the heading open tag will have been placed in +// mPreviousText). + +void +BookmarkContentSink::HandleHeadEnd() +{ + NewFrame(); +} + + +// BookmarkContentSink::HandleLinkBegin +// +// Handles " tags that have no href. + if (href.IsEmpty()) { + frame.mPreviousLink = nsnull; + // The exception is for feeds, where the href is an optional component + // indicating the source web site. + if (!frame.mPreviousFeed) + return; + } + else { + // Save the address if it's valid. Note that we ignore errors if this is a + // feed since href is optional for them. + nsresult rv = NS_NewURI(getter_AddRefs(frame.mPreviousLink), href, nsnull); + if (NS_FAILED(rv) && !frame.mPreviousFeed) { + frame.mPreviousLink = nsnull; + return; + } + } + + // Save bookmark's last modified date. + if (!lastModified.IsEmpty()) { + frame.mPreviousLastModifiedDate = + ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(lastModified)); + } + + // If this is a live bookmark, we will handle it in HandleLinkEnd(), so we + // can skip bookmark creation. + if (frame.mPreviousFeed) + return; + + // Create the bookmark. The title is unknown for now, we will set it later. + nsresult rv = mBookmarksService->InsertBookmark(frame.mContainerID, + frame.mPreviousLink, + mBookmarksService->DEFAULT_INDEX, + EmptyCString(), + &frame.mPreviousId); + if (NS_FAILED(rv)) { + // If inserting bookmark failed, there's nothing more we can do. + NS_WARNING("InserBookmark failed"); + return; + } + + // Set the date added value, if we have it. + if (!dateAdded.IsEmpty()) { + PRTime convertedDateAdded = + ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(dateAdded)); + if (convertedDateAdded) { + rv = mBookmarksService->SetItemDateAdded(frame.mPreviousId, convertedDateAdded); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemDateAdded failed"); + } + } + + // Save the favicon. + if (!icon.IsEmpty() || !iconUri.IsEmpty()) { + nsCOMPtr iconUriObject; + rv = NS_NewURI(getter_AddRefs(iconUriObject), iconUri); + if (!icon.IsEmpty() || NS_SUCCEEDED(rv)) { + rv = SetFaviconForURI(frame.mPreviousLink, iconUriObject, icon); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Import: unable to set favicon '"); + warnMsg.Append(NS_ConvertUTF16toUTF8(iconUri)); + warnMsg.Append("' for page '"); + nsCAutoString spec; + rv = frame.mPreviousLink->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + warnMsg.Append(spec); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + } + } + } + + // Save the keyword. + if (!keyword.IsEmpty()) { + rv = mBookmarksService->SetKeywordForBookmark(frame.mPreviousId, keyword); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetKeywordForBookmark failed"); + if (NS_SUCCEEDED(rv) && !postData.IsEmpty()) { + rv = mAnnotationService->SetItemAnnotationString(frame.mPreviousId, + POST_DATA_ANNO, + postData, 0, + nsIAnnotationService::EXPIRE_NEVER); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemAnnotationString failed"); + } + } + + // Set load-in-sidebar annotation for the bookmark. + if (webPanel.LowerCaseEqualsLiteral("true")) { + + rv = mAnnotationService->SetItemAnnotationInt32(frame.mPreviousId, + LOAD_IN_SIDEBAR_ANNO, + 1, 0, + nsIAnnotationService::EXPIRE_NEVER); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemAnnotationInt32 failed"); + } + + // Import last charset. + if (!lastCharset.IsEmpty()) { + rv = mHistoryService->SetCharsetForURI(frame.mPreviousLink,lastCharset); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "setCharsetForURI failed"); + } +} + + +// BookmarkContentSink::HandleLinkEnd +// +// Saves the title for the given bookmark. This only writes the user title. +// Any previous title will be untouched. If this is a new entry, it will have +// an empty "official" title until you visit it. + +void +BookmarkContentSink::HandleLinkEnd() +{ + nsresult rv; + BookmarkImportFrame& frame = CurFrame(); + frame.mPreviousText.Trim(kWhitespace); + + if (frame.mPreviousFeed) { + // The is a live bookmark. We create it here since in HandleLinkBegin we + // don't know the title. + jsval livemark = livemarkInfoToJSVal( + 0, EmptyCString(), frame.mPreviousText, frame.mContainerID, + mBookmarksService->DEFAULT_INDEX, frame.mPreviousFeed, frame.mPreviousLink + ); + + // Create the live bookmark. + rv = mLivemarkService->AddLivemark(livemark, nsnull); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "AddLivemark failed!"); + +#ifdef DEBUG_IMPORT + PrintNesting(); + printf("Created livemark '%s'\n", + NS_ConvertUTF16toUTF8(frame.mPreviousText).get()); +#endif + } + else if (frame.mPreviousLink) { + // This is a common bookmark. +#ifdef DEBUG_IMPORT + PrintNesting(); + printf("Created bookmark '%s' %lld\n", + NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId); +#endif + rv = mBookmarksService->SetItemTitle(frame.mPreviousId, + NS_ConvertUTF16toUTF8(frame.mPreviousText)); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemTitle failed"); + } + + // Set last modified date as the last change. + if (frame.mPreviousId > 0 && frame.mPreviousLastModifiedDate > 0) { + rv = mBookmarksService->SetItemLastModified(frame.mPreviousId, + frame.mPreviousLastModifiedDate); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed"); + // Note: don't clear mPreviousLastModifiedDate, because if this item has a + // description, we'll need to set it again. + } + + frame.mPreviousText.Truncate(); +} + + +// BookmarkContentSink::HandleSeparator +// +// Inserts a separator into the current container +void +BookmarkContentSink::HandleSeparator(const nsIParserNode& aNode) +{ + BookmarkImportFrame& frame = CurFrame(); + + // create the separator + +#ifdef DEBUG_IMPORT + PrintNesting(); + printf("--------\n"); +#endif + + nsresult rv = mBookmarksService->InsertSeparator(frame.mContainerID, + mBookmarksService->DEFAULT_INDEX, + &frame.mPreviousId); + if (NS_FAILED(rv)) { + NS_WARNING("InsertSeparator failed"); + return; + } + // Import separator title if set. + // Note that Places does not use separator titles, nor backup/restore them. + PRInt32 attrCount = aNode.GetAttributeCount(); + for (PRInt32 i = 0; i < attrCount; i ++) { + const nsAString& key = aNode.GetKeyAt(i); + + if (key.LowerCaseEqualsLiteral(KEY_NAME_LOWER)) { + nsAutoString name; + name = aNode.GetValueAt(i); + name.Trim(kWhitespace); + if (!name.IsEmpty()) { + rv = mBookmarksService->SetItemTitle(frame.mPreviousId, + NS_ConvertUTF16toUTF8(name)); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemTitle failed"); + } + } + } + + // Note: we do not need to import ADD_DATE or LAST_MODIFIED for separators + // because pre-Places bookmarks does not support them. + // and we can't write them out because attributes other than NAME + // will make Firefox 2.x crash/hang due to bug #381129 +} + + +// BookmarkContentSink::NewFrame +// +// This is called when there is a new folder found. The folder takes the +// name from the previous frame's heading. + +nsresult +BookmarkContentSink::NewFrame() +{ + nsresult rv; + + PRInt64 ourID = 0; + nsString containerName; + BookmarkImportFrame::ContainerType containerType; + BookmarkImportFrame& frame = CurFrame(); + frame.ConsumeHeading(&containerName, &containerType); + + bool updateFolder = false; + + switch (containerType) { + case BookmarkImportFrame::Container_Normal: + // append a new folder + rv = mBookmarksService->CreateFolder(CurFrame().mContainerID, + NS_ConvertUTF16toUTF8(containerName), + mBookmarksService->DEFAULT_INDEX, + &ourID); + NS_ENSURE_SUCCESS(rv, rv); + break; + case BookmarkImportFrame::Container_Places: + // places root, never reparent here, when we're building the initial + // hierarchy, it will only be defined at the top level + rv = mBookmarksService->GetPlacesRoot(&ourID); + NS_ENSURE_SUCCESS(rv, rv); + break; + case BookmarkImportFrame::Container_Menu: + // menu folder + rv = mBookmarksService->GetBookmarksMenuFolder(&ourID); + NS_ENSURE_SUCCESS(rv, rv); + if (mAllowRootChanges) + updateFolder = true; + break; + case BookmarkImportFrame::Container_Unfiled: + // unfiled bookmarks folder + rv = mBookmarksService->GetUnfiledBookmarksFolder(&ourID); + NS_ENSURE_SUCCESS(rv, rv); + if (mAllowRootChanges) + updateFolder = true; + break; + case BookmarkImportFrame::Container_Toolbar: + // get toolbar folder + rv = mBookmarksService->GetToolbarFolder(&ourID); + NS_ENSURE_SUCCESS(rv, rv); + + break; + default: + NS_NOTREACHED("Unknown container type"); + } + +#ifdef DEBUG_IMPORT + PrintNesting(); + printf("Folder %lld \'%s\'", ourID, NS_ConvertUTF16toUTF8(containerName).get()); +#endif + + if (updateFolder) { + // move the menu folder to the current position + rv = mBookmarksService->MoveItem(ourID, CurFrame().mContainerID, -1); + NS_ENSURE_SUCCESS(rv, rv); + rv = mBookmarksService->SetItemTitle(ourID, NS_ConvertUTF16toUTF8(containerName)); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef DEBUG_IMPORT + printf(" [reparenting]"); +#endif + } + +#ifdef DEBUG_IMPORT + printf("\n"); +#endif + + if (frame.mPreviousDateAdded > 0) { + rv = mBookmarksService->SetItemDateAdded(ourID, frame.mPreviousDateAdded); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemDateAdded failed"); + frame.mPreviousDateAdded = 0; + } + if (frame.mPreviousLastModifiedDate > 0) { + rv = mBookmarksService->SetItemLastModified(ourID, frame.mPreviousLastModifiedDate); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed"); + // don't clear last-modified, in case there's a description + } + + frame.mPreviousId = ourID; + + if (!mFrames.AppendElement(BookmarkImportFrame(ourID))) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +nsresult +BookmarkContentSink::PopFrame() +{ + // we must always have one frame + if (mFrames.Length() <= 1) { + NS_NOTREACHED("Trying to complete more bookmark folders than you started"); + return NS_ERROR_FAILURE; + } + mFrames.RemoveElementAt(mFrames.Length() - 1); + return NS_OK; +} + + +// BookmarkContentSink::SetFaviconForURI +// +// aData is a string that is a data URI for the favicon. Our job is to +// decode it and store it in the favicon service. +// +// When aIconURI is non-null, we will use that as the URI of the favicon +// when storing in the favicon service. +// +// When aIconURI is null, we have to make up a URI for this favicon so that +// it can be stored in the service. The real one will be set the next time +// the user visits the page. Our made up one should get expired when the +// page no longer references it. +nsresult +BookmarkContentSink::SetFaviconForURI(nsIURI* aPageURI, nsIURI* aIconURI, + const nsString& aData) +{ + nsresult rv; + static PRUint32 serialNumber = 0; // for made-up favicon URIs + + nsCOMPtr faviconService = + do_GetService(NS_FAVICONSERVICE_CONTRACTID); + NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY); + + // if the input favicon URI is a chrome: URI, then we just save it and don't + // worry about data + if (aIconURI) { + nsCString faviconScheme; + aIconURI->GetScheme(faviconScheme); + if (faviconScheme.EqualsLiteral("chrome")) { + return faviconService->SetFaviconUrlForPage(aPageURI, aIconURI); + } + } + + // some bookmarks have placeholder URIs that contain just "data:" + // ignore these + if (aData.Length() <= 5) + return NS_OK; + + nsCOMPtr faviconURI; + if (aIconURI) { + faviconURI = aIconURI; + } + else { + // make up favicon URL + nsCAutoString faviconSpec; + faviconSpec.AssignLiteral("http://www.mozilla.org/2005/made-up-favicon/"); + faviconSpec.AppendInt(serialNumber); + faviconSpec.AppendLiteral("-"); + char buf[32]; + PR_snprintf(buf, sizeof(buf), "%lld", PR_Now()); + faviconSpec.Append(buf); + rv = NS_NewURI(getter_AddRefs(faviconURI), faviconSpec); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Import: Unable to make up new favicon '"); + warnMsg.Append(faviconSpec); + warnMsg.Append("' for page '"); + nsCAutoString spec; + rv = aPageURI->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + warnMsg.Append(spec); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } + serialNumber++; + } + + // save the favicon data + // This could fail if the favicon is bigger than defined limit, in such a + // case data will not be saved to the db but we will still continue. + (void) faviconService->SetFaviconDataFromDataURL(faviconURI, aData, 0); + + rv = faviconService->SetFaviconUrlForPage(aPageURI, faviconURI); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +// Converts a string date in seconds to an int date in microseconds +PRTime +BookmarkContentSink::ConvertImportedDateToInternalDate(const nsACString& aDate) { + PRTime convertedDate = 0; + if (!aDate.IsEmpty()) { + nsresult rv; + convertedDate = PromiseFlatCString(aDate).ToInteger(&rv); + if (NS_SUCCEEDED(rv)) { + convertedDate *= 1000000; // in bookmarks.html this value is in seconds, not microseconds + } + else { + convertedDate = 0; + } + } + return convertedDate; +} + + +// SyncChannelStatus +// +// If a function returns an error, we need to set the channel status to be +// the same, but only if the channel doesn't have its own error. This returns +// the error code that should be sent to OnStopRequest. +static nsresult +SyncChannelStatus(nsIChannel* channel, nsresult status) +{ + nsresult channelStatus; + channel->GetStatus(&channelStatus); + if (NS_FAILED(channelStatus)) + return channelStatus; + + if (NS_SUCCEEDED(status)) + return NS_OK; // caller and the channel are happy + + // channel was OK, but caller wasn't: set the channel state + channel->Cancel(status); + return status; +} + + +static char kFileIntro[] = + "" NS_LINEBREAK + // Note: we write bookmarks in UTF-8 + "" NS_LINEBREAK + "" NS_LINEBREAK + "Bookmarks" NS_LINEBREAK; +static const char kRootIntro[] = "

        +// +// Goes after the container header (Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kBookmarkIntro, sizeof(kBookmarkIntro)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// WriteContainerEpilogue +// +//

        +// +// Goes after the container contents to close the container +static nsresult +WriteContainerEpilogue(const nsACString& aIndent, nsIOutputStream* aOutput) +{ + PRUint32 dummy; + nsresult rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kBookmarkClose, sizeof(kBookmarkClose)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// WriteFaviconAttribute +// +// This writes the 'ICON="data:asdlfkjas;ldkfja;skdljfasdf"' attribute for +// an item. We special-case chrome favicon URIs by just writing the chrome: +// URI. +static nsresult +WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput) +{ + PRUint32 dummy; + + // if favicon uri is invalid we skip the attribute silently, to avoid + // creating a corrupt file. + nsCOMPtr uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Export: Found invalid favicon '"); + warnMsg.Append(aURI); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } + + // get favicon + nsCOMPtr faviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr faviconURI; + rv = faviconService->GetFaviconForPage(uri, getter_AddRefs(faviconURI)); + if (rv == NS_ERROR_NOT_AVAILABLE) + return NS_OK; // no favicon + NS_ENSURE_SUCCESS(rv, rv); // anything else is error + + nsCAutoString faviconScheme; + nsCAutoString faviconSpec; + rv = faviconURI->GetSpec(faviconSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = faviconURI->GetScheme(faviconScheme); + NS_ENSURE_SUCCESS(rv, rv); + + // write favicon URI: 'ICON_URI="..."' + rv = aOutput->Write(kIconURIAttribute, sizeof(kIconURIAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteEscapedUrl(faviconSpec, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + if (!faviconScheme.EqualsLiteral("chrome")) { + // only store data for non-chrome URIs + + nsAutoString faviconContents; + rv = faviconService->GetFaviconDataAsDataURL(faviconURI, faviconContents); + NS_ENSURE_SUCCESS(rv, rv); + if (faviconContents.Length() > 0) { + rv = aOutput->Write(kIconAttribute, sizeof(kIconAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + NS_ConvertUTF16toUTF8 utf8Favicon(faviconContents); + rv = aOutput->Write(utf8Favicon.get(), utf8Favicon.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + + +// WriteDateAttribute +// +// This writes the '{attr value=}"{time in seconds}"' attribute for +// an item. +static nsresult +WriteDateAttribute(const char aAttributeStart[], PRInt32 aLength, PRTime aAttributeValue, nsIOutputStream* aOutput) +{ + // write attribute start + PRUint32 dummy; + nsresult rv = aOutput->Write(aAttributeStart, aLength, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // in bookmarks.html this value is in seconds, not microseconds + aAttributeValue /= 1000000; + + // write attribute value + char dateInSeconds[32]; + PR_snprintf(dateInSeconds, sizeof(dateInSeconds), "%lld", aAttributeValue); + rv = aOutput->Write(dateInSeconds, strlen(dateInSeconds), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +// nsPlacesImportExportService::WriteContainer +// +// Writes out all the necessary parts of a bookmarks folder. +nsresult +nsPlacesImportExportService::WriteContainer(nsINavHistoryResultNode* aFolder, + const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + nsresult rv = WriteContainerHeader(aFolder, aIndent, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteContainerPrologue(aIndent, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteContainerContents(aFolder, aIndent, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteContainerEpilogue(aIndent, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// nsPlacesImportExportService::WriteContainerHeader +// +// This writes '

        Title

        ' +// Remember folders can also have favicons, which we put in the H3 tag +nsresult +nsPlacesImportExportService::WriteContainerHeader(nsINavHistoryResultNode* aFolder, + const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + PRUint32 dummy; + nsresult rv; + + // indent + if (!aIndent.IsEmpty()) { + rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // "
        Write(kContainerIntro, sizeof(kContainerIntro)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // get folder id + PRInt64 folderId; + rv = aFolder->GetItemId(&folderId); + NS_ENSURE_SUCCESS(rv, rv); + + // write ADD_DATE + PRTime dateAdded = 0; + rv = aFolder->GetDateAdded(&dateAdded); + NS_ENSURE_SUCCESS(rv, rv); + + if (dateAdded) { + rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + } + + // write LAST_MODIFIED + PRTime lastModified = 0; + rv = aFolder->GetLastModified(&lastModified); + NS_ENSURE_SUCCESS(rv, rv); + + if (lastModified) { + rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + } + + PRInt64 placesRoot; + rv = mBookmarksService->GetPlacesRoot(&placesRoot); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64 bookmarksMenuFolder; + rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64 toolbarFolder; + rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64 unfiledBookmarksFolder; + rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // " PERSONAL_TOOLBAR_FOLDER="true"", etc. + if (folderId == placesRoot) { + rv = aOutput->Write(kPlacesRootAttribute, sizeof(kPlacesRootAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (folderId == bookmarksMenuFolder) { + rv = aOutput->Write(kBookmarksRootAttribute, sizeof(kBookmarksRootAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (folderId == unfiledBookmarksFolder) { + rv = aOutput->Write(kUnfiledBookmarksFolderAttribute, sizeof(kUnfiledBookmarksFolderAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (folderId == toolbarFolder) { + rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // ">" + rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // title + rv = WriteTitle(aFolder, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + // "

        \n" + rv = aOutput->Write(kContainerClose, sizeof(kContainerClose)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // description + rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_FOLDER, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + + +// nsPlacesImportExportService::WriteTitle +// +// Retrieves, escapes and writes the title to the stream. +nsresult +nsPlacesImportExportService::WriteTitle(nsINavHistoryResultNode* aItem, + nsIOutputStream* aOutput) +{ + // XXX Bug 381767 - support titles for separators + PRUint32 type = 0; + nsresult rv = aItem->GetType(&type); + NS_ENSURE_SUCCESS(rv, rv); + if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) + return NS_ERROR_INVALID_ARG; + + nsCAutoString title; + rv = aItem->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + + char* escapedTitle = nsEscapeHTML(title.get()); + if (escapedTitle) { + PRUint32 dummy; + rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); + nsMemory::Free(escapedTitle); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + + +// nsPlacesImportExportService::WriteDescription +// +// Write description out for all item types. +nsresult +nsPlacesImportExportService::WriteDescription(PRInt64 aItemId, PRInt32 aType, + nsIOutputStream* aOutput) +{ + bool hasDescription = false; + nsresult rv = mAnnotationService->ItemHasAnnotation(aItemId, + DESCRIPTION_ANNO, + &hasDescription); + if (NS_FAILED(rv) || !hasDescription) + return rv; + + nsAutoString description; + rv = mAnnotationService->GetItemAnnotationString(aItemId, DESCRIPTION_ANNO, + description); + NS_ENSURE_SUCCESS(rv, rv); + + char* escapedDesc = nsEscapeHTML(NS_ConvertUTF16toUTF8(description).get()); + if (escapedDesc) { + PRUint32 dummy; + rv = aOutput->Write(kDescriptionIntro, sizeof(kDescriptionIntro)-1, &dummy); + if (NS_FAILED(rv)) { + nsMemory::Free(escapedDesc); + return rv; + } + rv = aOutput->Write(escapedDesc, strlen(escapedDesc), &dummy); + nsMemory::Free(escapedDesc); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kDescriptionClose, sizeof(kDescriptionClose)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// nsBookmarks::WriteItem +// +// "
        Name" +nsresult +nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem, + const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + // before doing any attempt to write the item check that uri is valid, if the + // item has a bad uri we skip it silently, otherwise we could stop while + // exporting, generating a corrupt file. + nsCAutoString uri; + nsresult rv = aItem->GetUri(uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr pageURI; + rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Export: Found invalid item uri '"); + warnMsg.Append(uri); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } + + // indent + PRUint32 dummy; + if (!aIndent.IsEmpty()) { + rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // '
        Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // ' HREF="http://..."' - note that we need to call GetURI on the result + // node because some nodes (eg queries) generate this lazily. + rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteEscapedUrl(uri, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // write ADD_DATE + PRTime dateAdded = 0; + rv = aItem->GetDateAdded(&dateAdded); + NS_ENSURE_SUCCESS(rv, rv); + + if (dateAdded) { + rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + } + + // write LAST_MODIFIED + PRTime lastModified = 0; + rv = aItem->GetLastModified(&lastModified); + NS_ENSURE_SUCCESS(rv, rv); + + if (lastModified) { + rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + } + + // ' ICON="..."' + rv = WriteFaviconAttribute(uri, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + // get item id + PRInt64 itemId; + rv = aItem->GetItemId(&itemId); + NS_ENSURE_SUCCESS(rv, rv); + + // keyword (shortcuturl) + nsAutoString keyword; + rv = mBookmarksService->GetKeywordForBookmark(itemId, keyword); + NS_ENSURE_SUCCESS(rv, rv); + if (!keyword.IsEmpty()) { + rv = aOutput->Write(kKeywordAttribute, sizeof(kKeywordAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + char* escapedKeyword = nsEscapeHTML(NS_ConvertUTF16toUTF8(keyword).get()); + rv = aOutput->Write(escapedKeyword, strlen(escapedKeyword), &dummy); + nsMemory::Free(escapedKeyword); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // post data + bool hasPostData; + rv = mAnnotationService->ItemHasAnnotation(itemId, POST_DATA_ANNO, + &hasPostData); + NS_ENSURE_SUCCESS(rv, rv); + if (hasPostData) { + nsAutoString postData; + rv = mAnnotationService->GetItemAnnotationString(itemId, POST_DATA_ANNO, + postData); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kPostDataAttribute, sizeof(kPostDataAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + char* escapedPostData = nsEscapeHTML(NS_ConvertUTF16toUTF8(postData).get()); + rv = aOutput->Write(escapedPostData, strlen(escapedPostData), &dummy); + nsMemory::Free(escapedPostData); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Write WEB_PANEL="true" if the load-in-sidebar annotation is set for the + // item + bool loadInSidebar = false; + rv = mAnnotationService->ItemHasAnnotation(itemId, LOAD_IN_SIDEBAR_ANNO, + &loadInSidebar); + NS_ENSURE_SUCCESS(rv, rv); + if (loadInSidebar) + aOutput->Write(kWebPanelAttribute, sizeof(kWebPanelAttribute)-1, &dummy); + + // last charset + nsAutoString lastCharset; + if (NS_SUCCEEDED(mHistoryService->GetCharsetForURI(pageURI, lastCharset)) && + !lastCharset.IsEmpty()) { + rv = aOutput->Write(kLastCharsetAttribute, sizeof(kLastCharsetAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + char* escapedLastCharset = nsEscapeHTML(NS_ConvertUTF16toUTF8(lastCharset).get()); + rv = aOutput->Write(escapedLastCharset, strlen(escapedLastCharset), &dummy); + nsMemory::Free(escapedLastCharset); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // '>' + rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // title + nsCAutoString title; + rv = aItem->GetTitle(title); + NS_ENSURE_SUCCESS(rv, rv); + char* escapedTitle = nsEscapeHTML(title.get()); + if (escapedTitle) { + rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); + nsMemory::Free(escapedTitle); + NS_ENSURE_SUCCESS(rv, rv); + } + + // '\n' + rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // description + rv = WriteDescription(itemId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +// WriteLivemark +// +// Similar to WriteItem, this has an additional FEEDURL attribute and +// the HREF is optional and points to the source page. +nsresult +nsPlacesImportExportService::WriteLivemark(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + PRUint32 dummy; + nsresult rv; + + // indent + if (!aIndent.IsEmpty()) { + rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // '
        Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // get folder id + PRInt64 folderId; + rv = aFolder->GetItemId(&folderId); + NS_ENSURE_SUCCESS(rv, rv); + + // get feed URI + nsString feedSpec; + rv = mAnnotationService->GetItemAnnotationString(folderId, + NS_LITERAL_CSTRING(LMANNO_FEEDURI), + feedSpec); + + NS_ENSURE_SUCCESS(rv, rv); + + // write feed URI + rv = aOutput->Write(kFeedURIAttribute, sizeof(kFeedURIAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(feedSpec), aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // get the optional site URI + nsString siteSpec; + rv = mAnnotationService->GetItemAnnotationString(folderId, + NS_LITERAL_CSTRING(LMANNO_SITEURI), + siteSpec); + if (NS_SUCCEEDED(rv) && !siteSpec.IsEmpty()) { + // write site URI + rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(siteSpec), aOutput); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // '>' + rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // title + rv = WriteTitle(aFolder, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + // '\n' + rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // description + rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +// nsPlacesImportExportService::WriteSeparator +// +// "
        " +nsresult +nsPlacesImportExportService::WriteSeparator(nsINavHistoryResultNode* aItem, + const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + PRUint32 dummy; + nsresult rv; + + // indent + if (!aIndent.IsEmpty()) { + rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), + &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aOutput->Write(kSeparator, sizeof(kSeparator)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX: separator result nodes don't support the title getter yet + PRInt64 itemId; + rv = aItem->GetItemId(&itemId); + NS_ENSURE_SUCCESS(rv, rv); + + // Note: we can't write the separator ID or anything else other than NAME + // because it makes Firefox 2.x crash/hang - see bug #381129 + + nsCAutoString title; + rv = mBookmarksService->GetItemTitle(itemId, title); + if (NS_SUCCEEDED(rv) && !title.IsEmpty()) { + rv = aOutput->Write(kNameAttribute, strlen(kNameAttribute), &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + char* escapedTitle = nsEscapeHTML(title.get()); + if (escapedTitle) { + PRUint32 dummy; + rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); + nsMemory::Free(escapedTitle); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // '>' + rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // line break + rv = aOutput->Write(NS_LINEBREAK, sizeof(NS_LINEBREAK)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + + +// WriteEscapedUrl +// +// Writes the given string to the stream escaped as necessary for URLs. +// +// Unfortunately, the old bookmarks system uses a custom hardcoded and +// braindead escaping scheme that we need to emulate. It just replaces +// quotes with %22 and that's it. +nsresult +WriteEscapedUrl(const nsCString& aString, nsIOutputStream* aOutput) +{ + nsCAutoString escaped(aString); + PRInt32 offset; + while ((offset = escaped.FindChar('\"')) >= 0) { + escaped.Cut(offset, 1); + escaped.Insert(NS_LITERAL_CSTRING("%22"), offset); + } + PRUint32 dummy; + return aOutput->Write(escaped.get(), escaped.Length(), &dummy); +} + + +// nsPlacesImportExportService::WriteContainerContents +// +// The indent here is the indent of the parent. We will add an additional +// indent before writing data. +nsresult +nsPlacesImportExportService::WriteContainerContents(nsINavHistoryResultNode* aFolder, + const nsACString& aIndent, + nsIOutputStream* aOutput) +{ + nsCAutoString myIndent(aIndent); + myIndent.Append(kIndent); + + PRInt64 folderId; + nsresult rv = aFolder->GetItemId(&folderId); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr folderNode = do_QueryInterface(aFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = folderNode->SetContainerOpen(true); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 childCount = 0; + folderNode->GetChildCount(&childCount); + for (PRUint32 i = 0; i < childCount; ++i) { + nsCOMPtr child; + rv = folderNode->GetChild(i, getter_AddRefs(child)); + NS_ENSURE_SUCCESS(rv, rv); + PRUint32 type = 0; + rv = child->GetType(&type); + NS_ENSURE_SUCCESS(rv, rv); + if (type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER) { + // bookmarks folder + PRInt64 childFolderId; + rv = child->GetItemId(&childFolderId); + NS_ENSURE_SUCCESS(rv, rv); + + // it could be a regular folder or it could be a livemark. + // Livemarks service is async, for now just workaround using annotations + // service. + bool isLivemark; + nsresult rv = mAnnotationService->ItemHasAnnotation(childFolderId, + NS_LITERAL_CSTRING(LMANNO_FEEDURI), + &isLivemark); + NS_ENSURE_SUCCESS(rv, rv); + + if (isLivemark) + rv = WriteLivemark(child, myIndent, aOutput); + else + rv = WriteContainer(child, myIndent, aOutput); + } + else if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) { + rv = WriteSeparator(child, myIndent, aOutput); + } + else { + rv = WriteItem(child, myIndent, aOutput); + } + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + + +// NotifyImportObservers +// +// Notifies bookmarks-restore observers using nsIObserverService. This +// function is void and we simply return on failure because we don't want +// the import itself to fail if notifying observers does. +static void +NotifyImportObservers(const char* aTopic, + PRInt64 aFolderId, + bool aIsInitialImport) +{ + nsCOMPtr obs = services::GetObserverService(); + if (!obs) + return; + + nsCOMPtr folderIdSupp = nsnull; + if (aFolderId > 0) { + nsCOMPtr folderIdInt = + do_CreateInstance(NS_SUPPORTS_PRINT64_CONTRACTID); + if (!folderIdInt) + return; + + if (NS_FAILED(folderIdInt->SetData(aFolderId))) + return; + + folderIdSupp = do_QueryInterface(folderIdInt); + } + + obs->NotifyObservers(folderIdSupp, + aTopic, + (aIsInitialImport ? RESTORE_INITIAL_NSIOBSERVER_DATA + : RESTORE_NSIOBSERVER_DATA).get()); +} + + +NS_IMETHODIMP +nsPlacesImportExportService::ImportHTMLFromFile(nsILocalFile* aFile, + bool aIsInitialImport) +{ + NotifyImportObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC, -1, aIsInitialImport); + + // this version is exposed on the interface and disallows changing of roots + nsresult rv = ImportHTMLFromFileInternal(aFile, + false, + 0, + aIsInitialImport); + + if (NS_FAILED(rv)) { + NotifyImportObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC, + -1, + aIsInitialImport); + } + else { + NotifyImportObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC, + -1, + aIsInitialImport); + } + + return rv; +} + + +NS_IMETHODIMP +nsPlacesImportExportService::ImportHTMLFromURI(nsIURI* aURI, + bool aIsInitialImport) +{ + NotifyImportObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC, -1, aIsInitialImport); + + // this version is exposed on the interface and disallows changing of roots + nsresult rv = ImportHTMLFromURIInternal(aURI, + false, + 0, + aIsInitialImport); + + if (NS_FAILED(rv)) { + NotifyImportObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC, + -1, + aIsInitialImport); + } + else { + NotifyImportObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC, + -1, + aIsInitialImport); + } + + return rv; +} + + +nsresult +nsPlacesImportExportService::ImportHTMLFromFileInternal(nsILocalFile* aFile, + bool aAllowRootChanges, + PRInt64 aFolder, + bool aIsImportDefaults) +{ + nsresult rv; + + nsCOMPtr file = do_QueryInterface(aFile); + NS_ENSURE_STATE(file); + +#ifdef DEBUG_IMPORT + nsAutoString path; + file->GetPath(path); + printf("\nImporting %s\n", NS_ConvertUTF16toUTF8(path).get()); +#endif + + // Confirm file to be imported exists. + bool exists; + rv = file->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr ioservice = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr fileURI; + rv = ioservice->NewFileURI(file, getter_AddRefs(fileURI)); + NS_ENSURE_SUCCESS(rv, rv); + + return ImportHTMLFromURIInternal(fileURI, aAllowRootChanges, aFolder, aIsImportDefaults); +} + +nsresult +nsPlacesImportExportService::ImportHTMLFromURIInternal(nsIURI* aURI, + bool aAllowRootChanges, + PRInt64 aFolder, + bool aIsImportDefaults) +{ + nsresult rv = EnsureServiceState(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr parser = do_CreateInstance(kParserCID); + NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr sink = new BookmarkContentSink(); + NS_ENSURE_TRUE(sink, NS_ERROR_OUT_OF_MEMORY); + rv = sink->Init(aAllowRootChanges, aFolder, aIsImportDefaults); + NS_ENSURE_SUCCESS(rv, rv); + parser->SetContentSink(sink); + + // Set the content type on the channel, otherwise the default "unknown" type + // will confuse the parser. + nsCOMPtr ioservice = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = ioservice->NewChannelFromURI(aURI, getter_AddRefs(mImportChannel)); + NS_ENSURE_SUCCESS(rv, rv); + rv = mImportChannel->SetContentType(NS_LITERAL_CSTRING("text/html")); + NS_ENSURE_SUCCESS(rv, rv); + + // Init parser. + rv = parser->Parse(aURI, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + // Run the import in batch mode, so it will be executed in a transaction + // and will be faster. + mIsImportDefaults = aIsImportDefaults; + mBookmarksService->RunInBatchMode(this, parser); + mImportChannel = nsnull; + + return NS_OK; +} + + +NS_IMETHODIMP +nsPlacesImportExportService::RunBatched(nsISupports* aUserData) +{ + nsresult rv; + if (mIsImportDefaults) { + PRInt64 bookmarksMenuFolder; + rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mBookmarksService->RemoveFolderChildren(bookmarksMenuFolder); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64 toolbarFolder; + rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mBookmarksService->RemoveFolderChildren(toolbarFolder); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64 unfiledBookmarksFolder; + rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mBookmarksService->RemoveFolderChildren(unfiledBookmarksFolder); + NS_ENSURE_SUCCESS(rv, rv); + } + + // streams + nsCOMPtr stream; + rv = mImportChannel->Open(getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr bufferedstream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedstream), stream, 4096); + NS_ENSURE_SUCCESS(rv, rv); + + // feed the parser the data + // Note: on error, we always need to set the channel's status to be the + // same, and to always call OnStopRequest with the channel error. + nsCOMPtr listener = do_QueryInterface(aUserData, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = listener->OnStartRequest(mImportChannel, nsnull); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "OnStartRequest failed"); + rv = SyncChannelStatus(mImportChannel, rv); + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SyncChannelStatus failed"); + + while (NS_SUCCEEDED(rv)) + { + PRUint32 available; + rv = bufferedstream->Available(&available); + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + available = 0; + } + if (NS_FAILED(rv)) { + mImportChannel->Cancel(rv); + break; + } + if (!available) + break; // blocking input stream has none available when done + + rv = listener->OnDataAvailable(mImportChannel, nsnull, bufferedstream, 0, + available); + if (NS_FAILED(rv)) + break; + rv = SyncChannelStatus(mImportChannel, rv); + if (NS_FAILED(rv)) + break; + } + + rv = listener->OnStopRequest(mImportChannel, nsnull, rv); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +NS_IMETHODIMP +nsPlacesImportExportService::ExportHTMLToFile(nsILocalFile* aBookmarksFile) +{ + NS_ENSURE_ARG(aBookmarksFile); + +#ifdef DEBUG_EXPORT + nsAutoString path; + aBookmarksFile->GetPath(path); + printf("\nExporting %s\n", NS_ConvertUTF16toUTF8(path).get()); + + PRTime startTime = PR_Now(); + printf("\nStart time: %lld\n", startTime); +#endif + + nsresult rv = EnsureServiceState(); + NS_ENSURE_SUCCESS(rv, rv); + + // get a safe output stream, so we don't clobber the bookmarks file unless + // all the writes succeeded. + nsCOMPtr out; + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), + aBookmarksFile, + PR_WRONLY | PR_CREATE_FILE, + 0600, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // We need a buffered output stream for performance. + // See bug 202477. + nsCOMPtr strm; + rv = NS_NewBufferedOutputStream(getter_AddRefs(strm), out, 4096); + NS_ENSURE_SUCCESS(rv, rv); + + // Get a new query object. + nsCOMPtr query; + rv = mHistoryService->GetNewQuery(getter_AddRefs(query)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr options; + rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(options)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr result; + + // We need the bookmarks menu root node to write out the title. + PRInt64 bookmarksMenuFolder; + rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = query->SetFolders(&bookmarksMenuFolder, 1); + NS_ENSURE_SUCCESS(rv, rv); + rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr rootNode; + rv = result->GetRoot(getter_AddRefs(rootNode)); + NS_ENSURE_SUCCESS(rv, rv); + + // file header + PRUint32 dummy; + rv = strm->Write(kFileIntro, sizeof(kFileIntro)-1, &dummy); + NS_ENSURE_SUCCESS(rv, rv); + + // 'Write(kRootIntro, sizeof(kRootIntro)-1, &dummy); //

        Bookmarks

        + rv = strm->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); // > + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteTitle(rootNode, strm); + NS_ENSURE_SUCCESS(rv, rv); + rv = strm->Write(kCloseRootH1, sizeof(kCloseRootH1)-1, &dummy); //

        + NS_ENSURE_SUCCESS(rv, rv); + + // Container's prologue. + rv = WriteContainerPrologue(EmptyCString(), strm); + NS_ENSURE_SUCCESS(rv, rv); + + // indents + nsCAutoString indent; + indent.Assign(kIndent); + + // Bookmarks Menu. + rv = WriteContainerContents(rootNode, EmptyCString(), strm); + NS_ENSURE_SUCCESS(rv, rv); + + // Bookmarks Toolbar. + // We write this folder under the bookmarks-menu for backwards compatibility. + PRInt64 toolbarFolder; + rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = query->SetFolders(&toolbarFolder, 1); + NS_ENSURE_SUCCESS(rv, rv); + rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + rv = result->GetRoot(getter_AddRefs(rootNode)); + NS_ENSURE_SUCCESS(rv, rv); + // Write it out only if it's not empty. + rv = rootNode->SetContainerOpen(true); + NS_ENSURE_SUCCESS(rv, rv); + PRUint32 childCount = 0; + rv = rootNode->GetChildCount(&childCount); + NS_ENSURE_SUCCESS(rv, rv); + rv = rootNode->SetContainerOpen(false); + NS_ENSURE_SUCCESS(rv, rv); + if (childCount) { + rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Unfiled Bookmarks. + // We write this folder under the bookmarks-menu for backwards compatibility. + PRInt64 unfiledBookmarksFolder; + rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = query->SetFolders(&unfiledBookmarksFolder, 1); + NS_ENSURE_SUCCESS(rv, rv); + rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + rv = result->GetRoot(getter_AddRefs(rootNode)); + NS_ENSURE_SUCCESS(rv, rv); + // Write it out only if it's not empty. + rv = rootNode->SetContainerOpen(true); + NS_ENSURE_SUCCESS(rv, rv); + childCount = 0; + rootNode->GetChildCount(&childCount); + NS_ENSURE_SUCCESS(rv, rv); + rv = rootNode->SetContainerOpen(false); + NS_ENSURE_SUCCESS(rv, rv); + if (childCount) { + rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Container's epilogue. + rv = WriteContainerEpilogue(EmptyCString(), strm); + NS_ENSURE_SUCCESS(rv, rv); + + // commit the write + nsCOMPtr safeStream = do_QueryInterface(strm, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = safeStream->Finish(); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG_EXPORT + printf("\nTotal time in seconds: %lld\n", (PR_Now() - startTime)/1000000); +#endif + + return NS_OK; +} + + +NS_IMETHODIMP +nsPlacesImportExportService::BackupBookmarksFile() +{ + nsresult rv = EnsureServiceState(); + NS_ENSURE_SUCCESS(rv, rv); + + // get bookmarks file + nsCOMPtr bookmarksFileDir; + rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE, + getter_AddRefs(bookmarksFileDir)); + + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr bookmarksFile = do_QueryInterface(bookmarksFileDir); + NS_ENSURE_STATE(bookmarksFile); + + // Create the file if it doesn't exist. + bool exists; + rv = bookmarksFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + rv = bookmarksFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_FAILED(rv)) { + NS_WARNING("Unable to create bookmarks.html!"); + return rv; + } + } + + // export bookmarks.html + rv = ExportHTMLToFile(bookmarksFile); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/toolkit/components/places/nsPlacesExportService.h b/toolkit/components/places/nsPlacesImportExportService.h similarity index 68% rename from toolkit/components/places/nsPlacesExportService.h rename to toolkit/components/places/nsPlacesImportExportService.h index 0cac53620e9..ba647ccdbf8 100644 --- a/toolkit/components/places/nsPlacesExportService.h +++ b/toolkit/components/places/nsPlacesImportExportService.h @@ -1,5 +1,5 @@ -#ifndef nsPlacesExportService_h_ -#define nsPlacesExportService_h_ +#ifndef nsPlacesImportExportService_h__ +#define nsPlacesImportExportService_h__ #include "nsIPlacesImportExportService.h" @@ -13,17 +13,19 @@ #include "nsINavBookmarksService.h" #include "nsIChannel.h" -class nsPlacesExportService : public nsIPlacesImportExportService +class nsPlacesImportExportService : public nsIPlacesImportExportService, + public nsINavHistoryBatchCallback { public: NS_DECL_ISUPPORTS NS_DECL_NSIPLACESIMPORTEXPORTSERVICE - nsPlacesExportService(); + NS_DECL_NSINAVHISTORYBATCHCALLBACK + nsPlacesImportExportService(); /** * Obtains the service's object. */ - static nsPlacesExportService* GetSingleton(); + static nsPlacesImportExportService* GetSingleton(); /** * Initializes the service's object. This should only be called once. @@ -31,8 +33,8 @@ class nsPlacesExportService : public nsIPlacesImportExportService nsresult Init(); private: - static nsPlacesExportService* gExportService; - virtual ~nsPlacesExportService(); + static nsPlacesImportExportService* gImportExportService; + virtual ~nsPlacesImportExportService(); protected: nsCOMPtr mFaviconService; @@ -41,6 +43,13 @@ class nsPlacesExportService : public nsIPlacesImportExportService nsCOMPtr mHistoryService; nsCOMPtr mLivemarkService; + nsCOMPtr mImportChannel; + bool mIsImportDefaults; + + nsresult ImportHTMLFromFileInternal(nsILocalFile* aFile, bool aAllowRootChanges, + PRInt64 aFolder, bool aIsImportDefaults); + nsresult ImportHTMLFromURIInternal(nsIURI* aURI, bool aAllowRootChanges, + PRInt64 aFolder, bool aIsImportDefaults); nsresult WriteContainer(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput); nsresult WriteContainerHeader(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput); nsresult WriteTitle(nsINavHistoryResultNode* aItem, nsIOutputStream* aOutput); @@ -60,4 +69,4 @@ class nsPlacesExportService : public nsIPlacesImportExportService } }; -#endif // nsPlacesExportService_h_ +#endif // nsPlacesImportExportService_h__ diff --git a/toolkit/components/places/nsPlacesModule.cpp b/toolkit/components/places/nsPlacesModule.cpp index eb0724063da..809e12b2343 100644 --- a/toolkit/components/places/nsPlacesModule.cpp +++ b/toolkit/components/places/nsPlacesModule.cpp @@ -6,7 +6,7 @@ #include "nsNavHistory.h" #include "nsNavBookmarks.h" #include "nsFaviconService.h" -#include "nsPlacesExportService.h" +#include "nsPlacesImportExportService.h" #include "History.h" #include "nsDocShellCID.h" @@ -24,8 +24,8 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavBookmarks, nsNavBookmarks::GetSingleton) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFaviconService, nsFaviconService::GetSingleton) -NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPlacesExportService, - nsPlacesExportService::GetSingleton) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPlacesImportExportService, + nsPlacesImportExportService::GetSingleton) #ifdef MOZ_ANDROID_HISTORY NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsAndroidHistory, nsAndroidHistory::GetSingleton) #else @@ -57,7 +57,7 @@ const mozilla::Module::CIDEntry kPlacesCIDs[] = { #else { &kNS_HISTORYSERVICE_CID, false, NULL, HistoryConstructor }, #endif - { &kNS_PLACESIMPORTEXPORTSERVICE_CID, false, NULL, nsPlacesExportServiceConstructor }, + { &kNS_PLACESIMPORTEXPORTSERVICE_CID, false, NULL, nsPlacesImportExportServiceConstructor }, { NULL } };