From 0da751d790530a288147fc502299be4038dc82fe Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Fri, 23 Mar 2012 09:15:36 +0200 Subject: [PATCH 001/109] Bug 482911 - Reimplement Netscape bookmark import using the HTML5 parser. r=mak, sr=gavin. --HG-- rename : toolkit/components/places/nsPlacesImportExportService.cpp => toolkit/components/places/nsPlacesExportService.cpp rename : toolkit/components/places/nsPlacesImportExportService.h => toolkit/components/places/nsPlacesExportService.h --- .../src/nsBrowserProfileMigratorUtils.cpp | 17 - .../src/nsBrowserProfileMigratorUtils.h | 5 - .../migration/src/nsIEProfileMigrator.cpp | 5 - .../migration/src/nsSafariProfileMigrator.cpp | 5 - browser/components/nsBrowserGlue.js | 62 +- browser/components/places/content/places.js | 7 +- .../places/tests/unit/test_384370.js | 14 +- ...41-import-export-corrupt-bookmarks-html.js | 15 +- .../unit/test_bookmarksRestoreNotification.js | 50 +- .../places/tests/unit/test_bookmarks_html.js | 160 +- .../tests/unit/test_browserGlue_migrate.js | 7 +- .../tests/unit/test_browserGlue_prefs.js | 143 +- .../unit/test_browserGlue_smartBookmarks.js | 13 +- .../components/places/BookmarkHTMLUtils.jsm | 775 ++++++ toolkit/components/places/Helpers.cpp | 59 - toolkit/components/places/Helpers.h | 9 - toolkit/components/places/Makefile.in | 3 +- toolkit/components/places/PlacesUtils.jsm | 2 +- .../places/nsIPlacesImportExportService.idl | 29 +- .../places/nsPlacesExportService.cpp | 1163 ++++++++ ...xportService.h => nsPlacesExportService.h} | 25 +- .../places/nsPlacesImportExportService.cpp | 2459 ----------------- toolkit/components/places/nsPlacesModule.cpp | 8 +- 23 files changed, 2237 insertions(+), 2798 deletions(-) create mode 100644 toolkit/components/places/BookmarkHTMLUtils.jsm create mode 100644 toolkit/components/places/nsPlacesExportService.cpp rename toolkit/components/places/{nsPlacesImportExportService.h => nsPlacesExportService.h} (68%) delete mode 100644 toolkit/components/places/nsPlacesImportExportService.cpp diff --git a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp index 869970c347f6..eb80fe269785 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp @@ -163,20 +163,3 @@ 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 26c9fad4554c..a1e00a797a57 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.h +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.h @@ -99,10 +99,5 @@ 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 e153e65f82c9..8ce5d0ef7a83 100644 --- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -1398,11 +1398,6 @@ 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 dcfe9e0c8c2c..05feb249df37 100644 --- a/browser/components/migration/src/nsSafariProfileMigrator.cpp +++ b/browser/components/migration/src/nsSafariProfileMigrator.cpp @@ -974,11 +974,6 @@ 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 8e3e72c63644..9f99c6556aff 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -62,6 +62,9 @@ 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"); @@ -228,10 +231,6 @@ 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; @@ -257,13 +256,6 @@ 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")) @@ -288,6 +280,9 @@ BrowserGlue.prototype = { keywordURI); } break; + case "initial-migration": + this._initialMigrationPerformed = true; + break; } }, @@ -990,18 +985,9 @@ BrowserGlue.prototype = { // If the database is corrupt or has been newly created we should // import bookmarks. var dbStatus = PlacesUtils.history.databaseStatus; - 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; - } + var importBookmarks = !this._initialMigrationPerformed && + (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE || + dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT); // Check if user or an extension has required to import bookmarks.html var importBookmarksHTML = false; @@ -1058,6 +1044,9 @@ 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 { @@ -1091,25 +1080,28 @@ 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 { - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.importHTMLFromURI(bookmarksURI, true /* overwrite existing */); + 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)); } 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 ce30f248aa05..40e7cfa1d262 100644 --- a/browser/components/places/content/places.js +++ b/browser/components/places/content/places.js @@ -400,10 +400,9 @@ var PlacesOrganizer = { Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterHTML); if (fp.show() != Ci.nsIFilePicker.returnCancel) { - if (fp.file) { - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.importHTMLFromFile(fp.file, false); + if (fp.fileURL) { + Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); + BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false); } } }, diff --git a/browser/components/places/tests/unit/test_384370.js b/browser/components/places/tests/unit/test_384370.js index adb7c4ae34cd..a7a68708c51d 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 */ - // get places import/export service - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].getService(Ci.nsIPlacesImportExportService); + // import the importer + Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); // avoid creating the places smart folder during tests Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch). @@ -83,8 +83,14 @@ function run_test() { // 2. run the test-suite // Note: we do not empty the db before this import to catch bugs like 380999 try { - importer.importHTMLFromFile(bookmarksFileOld, true); + BookmarkHTMLUtils.importFromFile(bookmarksFileOld, true, after_import); } 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(); @@ -95,6 +101,8 @@ function run_test() { // 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 f53451a2cdd4..6e5c82d7100f 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,6 +55,7 @@ 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"; @@ -72,8 +73,14 @@ function run_test() { // import bookmarks from corrupt file var corruptBookmarksFile = do_get_file("bookmarks.corrupt.html"); try { - ies.importHTMLFromFile(corruptBookmarksFile, true); + BookmarkHTMLUtils.importFromFile(corruptBookmarksFile, true, after_import); } 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 @@ -105,14 +112,16 @@ function run_test() { // Import bookmarks try { - ies.importHTMLFromFile(bookmarksFile, true); + BookmarkHTMLUtils.importFromFile(bookmarksFile, true, before_database_check); } 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 4b2a55fb4c2a..c463272d0927 100644 --- a/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js +++ b/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js @@ -36,6 +36,8 @@ * * ***** 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. @@ -134,10 +136,14 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.html"); addBookmarks(); - importer.exportHTMLToFile(this.file); + exporter.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - importer.importHTMLFromFile(this.file, false); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -154,7 +160,11 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - importer.importHTMLFromFile(this.file, false); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -172,8 +182,12 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - importer.importHTMLFromFile(this.file, false); - do_throw(" Restore should have failed"); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + print("callback"); + if (success) { + do_throw(" Restore should have failed"); + } + }); } catch (e) {} } @@ -188,10 +202,14 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); addBookmarks(); - importer.exportHTMLToFile(this.file); + exporter.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - importer.importHTMLFromFile(this.file, true); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -208,7 +226,11 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - importer.importHTMLFromFile(this.file, true); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -226,13 +248,15 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - importer.importHTMLFromFile(this.file, true); - do_throw(" Restore should have failed"); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (success) { + do_throw(" Restore should have failed"); + } + }); } catch (e) {} } } - ]; // nsIObserver that observes bookmarks-restore-begin. @@ -281,7 +305,7 @@ var successAndFailedObserver = { do_check_eq(test.folderId, null); remove_all_bookmarks(); - doNextTest(); + do_execute_soon(doNextTest); } }; @@ -294,7 +318,7 @@ var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. var obssvc = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); -var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. +var exporter = 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 4423de000514..aa4772c7b73b 100644 --- a/browser/components/places/tests/unit/test_bookmarks_html.js +++ b/browser/components/places/tests/unit/test_bookmarks_html.js @@ -99,9 +99,11 @@ let gBookmarksFileOld; // Places bookmarks.html file pointer. let gBookmarksFileNew; -let importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. +let exporter = 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(); @@ -131,22 +133,26 @@ 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 { - importer.importHTMLFromFile(gBookmarksFileOld, true); - } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } + BookmarkHTMLUtils.importFromFile(gBookmarksFileOld, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - waitForAsyncUpdates(function () { - testImportedBookmarks(); + // Prepare for next tests. + try { + exporter.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } - // 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(); + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); + } else { + do_throw("couldn't import legacy bookmarks file."); + } }); - }); + } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } }); add_test(function test_import_new() @@ -156,17 +162,21 @@ add_test(function test_import_new() // 2. run the test-suite try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); + 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); } }); add_test(function test_emptytitle_export() @@ -180,42 +190,50 @@ add_test(function test_emptytitle_export() // 5. run the test-suite try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + 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 }); - 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); } - try { - importer.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } + remove_all_bookmarks(); - remove_all_bookmarks(); + try { + BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + // Cleanup. + test_bookmarks.unfiled.pop(); + PlacesUtils.bookmarks.removeItem(id); - waitForAsyncUpdates(function () { - testImportedBookmarks(); + try { + exporter.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } - // 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(); + 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."); + } }); - }); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } }); add_test(function test_import_ontop() @@ -229,23 +247,33 @@ add_test(function test_import_ontop() // 4. run the test-suite try { - 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); } + 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 () { - 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); } - 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); } }); 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 7f7bf012226e..85cefef882bf 100644 --- a/browser/components/places/tests/unit/test_browserGlue_migrate.js +++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js @@ -29,14 +29,15 @@ function run_test() { do_check_eq(PlacesUtils.history.databaseStatus, PlacesUtils.history.DATABASE_STATUS_CREATE); - // A migrator would run before nsBrowserGlue, so we mimic that behavior - // adding a bookmark. + //A migrator would run before nsBrowserGlue Places initialization, so mimic + //that behavior adding a bookmark and notifying the migration. 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.nsIBrowserGlue); + getService(Ci.nsIObserver); + bg.observe(null, "initial-migration", null) 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 74c2eb3fbb3a..0194510c96eb 100644 --- a/browser/components/places/tests/unit/test_browserGlue_prefs.js +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -17,6 +17,16 @@ 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. @@ -66,19 +76,20 @@ 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() @@ -98,19 +109,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"); + // 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() @@ -131,20 +143,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, 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() @@ -165,21 +178,22 @@ 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() @@ -198,19 +212,21 @@ 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() @@ -230,20 +246,21 @@ 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 f6532f0cf175..d7028b07b025 100644 --- a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js +++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js @@ -364,6 +364,15 @@ function run_test() { } catch(ex) {} - // Kick-off tests. - next_test(); + 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); } diff --git a/toolkit/components/places/BookmarkHTMLUtils.jsm b/toolkit/components/places/BookmarkHTMLUtils.jsm new file mode 100644 index 000000000000..e2cd4d0116df --- /dev/null +++ b/toolkit/components/places/BookmarkHTMLUtils.jsm @@ -0,0 +1,775 @@ +/* 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 1a5806ee7913..76bb3ee340d3 100644 --- a/toolkit/components/places/Helpers.cpp +++ b/toolkit/components/places/Helpers.cpp @@ -445,64 +445,5 @@ 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 600fa67df6b7..e77a214edd1b 100644 --- a/toolkit/components/places/Helpers.h +++ b/toolkit/components/places/Helpers.h @@ -297,15 +297,6 @@ 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 59120d76e381..e1d43e8f5b7c 100644 --- a/toolkit/components/places/Makefile.in +++ b/toolkit/components/places/Makefile.in @@ -92,7 +92,7 @@ CPPSRCS = \ SQLFunctions.cpp \ Helpers.cpp \ History.cpp \ - nsPlacesImportExportService.cpp \ + nsPlacesExportService.cpp \ AsyncFaviconHelpers.cpp \ PlaceInfo.cpp \ VisitInfo.cpp \ @@ -123,6 +123,7 @@ 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 4499df2800a2..20e914f2dcb6 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/nsPlacesImportExportService.cpp +// same names in browser/components/places/src/nsPlacesExportService.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 b6e60f4ba4bd..af9835dae6c1 100644 --- a/toolkit/components/places/nsIPlacesImportExportService.idl +++ b/toolkit/components/places/nsIPlacesImportExportService.idl @@ -41,34 +41,15 @@ interface nsILocalFile; interface nsIURI; /** - * The PlacesImportExport interface provides methods for importing - * and exporting Places data. + * 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. */ -[scriptable, uuid(08f4626e-af3f-4e84-bd01-cf09175c4f94)] +[scriptable, uuid(47a4a09e-c708-4e68-b2f2-664d982ce026)] 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 new file mode 100644 index 000000000000..ec70ec2769a2 --- /dev/null +++ b/toolkit/components/places/nsPlacesExportService.cpp @@ -0,0 +1,1163 @@ +/* -*- 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.h b/toolkit/components/places/nsPlacesExportService.h similarity index 68% rename from toolkit/components/places/nsPlacesImportExportService.h rename to toolkit/components/places/nsPlacesExportService.h index ba647ccdbf82..0cac53620e98 100644 --- a/toolkit/components/places/nsPlacesImportExportService.h +++ b/toolkit/components/places/nsPlacesExportService.h @@ -1,5 +1,5 @@ -#ifndef nsPlacesImportExportService_h__ -#define nsPlacesImportExportService_h__ +#ifndef nsPlacesExportService_h_ +#define nsPlacesExportService_h_ #include "nsIPlacesImportExportService.h" @@ -13,19 +13,17 @@ #include "nsINavBookmarksService.h" #include "nsIChannel.h" -class nsPlacesImportExportService : public nsIPlacesImportExportService, - public nsINavHistoryBatchCallback +class nsPlacesExportService : public nsIPlacesImportExportService { public: NS_DECL_ISUPPORTS NS_DECL_NSIPLACESIMPORTEXPORTSERVICE - NS_DECL_NSINAVHISTORYBATCHCALLBACK - nsPlacesImportExportService(); + nsPlacesExportService(); /** * Obtains the service's object. */ - static nsPlacesImportExportService* GetSingleton(); + static nsPlacesExportService* GetSingleton(); /** * Initializes the service's object. This should only be called once. @@ -33,8 +31,8 @@ class nsPlacesImportExportService : public nsIPlacesImportExportService, nsresult Init(); private: - static nsPlacesImportExportService* gImportExportService; - virtual ~nsPlacesImportExportService(); + static nsPlacesExportService* gExportService; + virtual ~nsPlacesExportService(); protected: nsCOMPtr mFaviconService; @@ -43,13 +41,6 @@ class nsPlacesImportExportService : 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); @@ -69,4 +60,4 @@ class nsPlacesImportExportService : public nsIPlacesImportExportService, } }; -#endif // nsPlacesImportExportService_h__ +#endif // nsPlacesExportService_h_ diff --git a/toolkit/components/places/nsPlacesImportExportService.cpp b/toolkit/components/places/nsPlacesImportExportService.cpp deleted file mode 100644 index 1c4897c503e9..000000000000 --- a/toolkit/components/places/nsPlacesImportExportService.cpp +++ /dev/null @@ -1,2459 +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 "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/nsPlacesModule.cpp b/toolkit/components/places/nsPlacesModule.cpp index 809e12b23431..eb0724063dae 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 "nsPlacesImportExportService.h" +#include "nsPlacesExportService.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(nsPlacesImportExportService, - nsPlacesImportExportService::GetSingleton) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPlacesExportService, + nsPlacesExportService::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, nsPlacesImportExportServiceConstructor }, + { &kNS_PLACESIMPORTEXPORTSERVICE_CID, false, NULL, nsPlacesExportServiceConstructor }, { NULL } }; From e37f0c92fe41231ac4e36cfb36137657600dfe4b Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Fri, 23 Mar 2012 00:40:14 -0700 Subject: [PATCH 002/109] add mozglue support for gonk (bug 738559, r=glandium) --- Makefile.in | 5 ++++ configure.in | 10 +++----- mozglue/Makefile.in | 4 +++ mozglue/build/Makefile.in | 7 ++++++ mozglue/gonk/GonkGlue.cpp | 53 +++++++++++++++++++++++++++++++++++++++ mozglue/gonk/Makefile.in | 29 +++++++++++++++++++++ 6 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 mozglue/gonk/GonkGlue.cpp create mode 100644 mozglue/gonk/Makefile.in diff --git a/Makefile.in b/Makefile.in index bc9007e2840d..767a0bf2bd32 100644 --- a/Makefile.in +++ b/Makefile.in @@ -72,6 +72,11 @@ tier_base_dirs += \ other-licenses/skia-npapi \ $(NULL) endif +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +tier_base_dirs += \ + other-licenses/android \ + $(NULL) +endif ifdef MOZ_MEMORY tier_base_dirs += memory/jemalloc diff --git a/configure.in b/configure.in index fa6642ec1879..8847436f6ecf 100644 --- a/configure.in +++ b/configure.in @@ -7235,13 +7235,11 @@ dnl We need to wrap dlopen and related functions on Android because we use dnl our own linker. if test "$OS_TARGET" = Android; then WRAP_LDFLAGS="${WRAP_LDFLAGS} -L$_objdir/dist/lib -lmozglue" - if test "$MOZ_WIDGET_TOOLKIT" = android; then - if test -n "$MOZ_OLD_LINKER"; then - WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=dlopen,--wrap=dlclose,--wrap=dlerror,--wrap=dlsym,--wrap=dladdr" - fi - WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=getaddrinfo,--wrap=freeaddrinfo,--wrap=gai_strerror" - WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=fork,--wrap=pthread_atfork" + if test -n "$MOZ_OLD_LINKER"; then + WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=dlopen,--wrap=dlclose,--wrap=dlerror,--wrap=dlsym,--wrap=dladdr" fi + WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=getaddrinfo,--wrap=freeaddrinfo,--wrap=gai_strerror" + WRAP_LDFLAGS="${WRAP_LDFLAGS} -Wl,--wrap=fork,--wrap=pthread_atfork" fi dnl ======================================================== diff --git a/mozglue/Makefile.in b/mozglue/Makefile.in index 81d95d4a3f2e..e1b88095ecc6 100644 --- a/mozglue/Makefile.in +++ b/mozglue/Makefile.in @@ -52,6 +52,10 @@ ifeq (android,$(MOZ_WIDGET_TOOLKIT)) DIRS += android endif +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +DIRS += gonk +endif + DIRS += build TEST_DIRS = tests diff --git a/mozglue/build/Makefile.in b/mozglue/build/Makefile.in index 21aef235bf67..847f0111881a 100644 --- a/mozglue/build/Makefile.in +++ b/mozglue/build/Makefile.in @@ -92,6 +92,13 @@ SHARED_LIBRARY_LIBS += $(call EXPAND_LIBNAME_PATH,android,$(DEPTH)/other-license SHARED_LIBRARY_LIBS += $(call EXPAND_LIBNAME_PATH,android,../android) endif +ifeq (gonk, $(MOZ_WIDGET_TOOLKIT)) +# To properly wrap jemalloc's pthread_atfork call. +EXTRA_DSO_LDOPTS += -Wl,--wrap=pthread_atfork +SHARED_LIBRARY_LIBS += $(call EXPAND_LIBNAME_PATH,android,$(DEPTH)/other-licenses/android) +SHARED_LIBRARY_LIBS += $(call EXPAND_LIBNAME_PATH,gonk,../gonk) +endif + ifdef MOZ_LINKER # Add custom dynamic linker SHARED_LIBRARY_LIBS += $(call EXPAND_LIBNAME_PATH,linker,../linker) diff --git a/mozglue/gonk/GonkGlue.cpp b/mozglue/gonk/GonkGlue.cpp new file mode 100644 index 000000000000..434fd9fd63ca --- /dev/null +++ b/mozglue/gonk/GonkGlue.cpp @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#define NS_EXPORT __attribute__ ((visibility("default"))) + +/* Android doesn't have pthread_atfork(), so we need to use our own. */ +struct AtForkFuncs { + void (*prepare)(void); + void (*parent)(void); + void (*child)(void); +}; +static std::vector atfork; + +extern "C" NS_EXPORT int +__wrap_pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) +{ + AtForkFuncs funcs; + funcs.prepare = prepare; + funcs.parent = parent; + funcs.child = child; + atfork.push_back(funcs); + return 0; +} + +extern "C" NS_EXPORT pid_t +__wrap_fork(void) +{ + pid_t pid; + for (std::vector::reverse_iterator it = atfork.rbegin(); + it < atfork.rend(); ++it) + if (it->prepare) + it->prepare(); + + switch ((pid = fork())) { + case 0: + for (std::vector::iterator it = atfork.begin(); + it < atfork.end(); ++it) + if (it->child) + it->child(); + break; + default: + for (std::vector::iterator it = atfork.begin(); + it < atfork.end(); ++it) + if (it->parent) + it->parent(); + } + return pid; +} diff --git a/mozglue/gonk/Makefile.in b/mozglue/gonk/Makefile.in new file mode 100644 index 000000000000..3a6c84422416 --- /dev/null +++ b/mozglue/gonk/Makefile.in @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = gonk +LIBRARY_NAME = gonk +FORCE_STATIC_LIB = 1 +STL_FLAGS= + +DEFINES += \ + -DANDROID_PACKAGE_NAME='"$(ANDROID_PACKAGE_NAME)"' \ + $(NULL) + +CPPSRCS = \ + GonkGlue.cpp \ + $(NULL) + +LOCAL_INCLUDES += -I$(DEPTH)/build +LOCAL_INCLUDES += -I$(topsrcdir)/xpcom/build + +include $(topsrcdir)/config/rules.mk From c2b04a653ba92d6baaf082d1b6dc6ef67fc09797 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Fri, 23 Mar 2012 11:25:08 +0200 Subject: [PATCH 003/109] Back out changeset d2693e86769d (bug 482911) due to crashes on Mac xpcshell tests. r=orange. --- .../src/nsBrowserProfileMigratorUtils.cpp | 17 + .../src/nsBrowserProfileMigratorUtils.h | 5 + .../migration/src/nsIEProfileMigrator.cpp | 5 + .../migration/src/nsSafariProfileMigrator.cpp | 5 + browser/components/nsBrowserGlue.js | 62 +- browser/components/places/content/places.js | 7 +- .../places/tests/unit/test_384370.js | 14 +- ...41-import-export-corrupt-bookmarks-html.js | 15 +- .../unit/test_bookmarksRestoreNotification.js | 50 +- .../places/tests/unit/test_bookmarks_html.js | 182 +- .../tests/unit/test_browserGlue_migrate.js | 7 +- .../tests/unit/test_browserGlue_prefs.js | 143 +- .../unit/test_browserGlue_smartBookmarks.js | 13 +- .../components/places/BookmarkHTMLUtils.jsm | 775 ------ toolkit/components/places/Helpers.cpp | 59 + toolkit/components/places/Helpers.h | 9 + toolkit/components/places/Makefile.in | 3 +- toolkit/components/places/PlacesUtils.jsm | 2 +- .../places/nsIPlacesImportExportService.idl | 29 +- .../places/nsPlacesExportService.cpp | 1163 -------- .../places/nsPlacesImportExportService.cpp | 2459 +++++++++++++++++ ...ervice.h => nsPlacesImportExportService.h} | 25 +- toolkit/components/places/nsPlacesModule.cpp | 8 +- 23 files changed, 2809 insertions(+), 2248 deletions(-) delete mode 100644 toolkit/components/places/BookmarkHTMLUtils.jsm delete mode 100644 toolkit/components/places/nsPlacesExportService.cpp create mode 100644 toolkit/components/places/nsPlacesImportExportService.cpp rename toolkit/components/places/{nsPlacesExportService.h => nsPlacesImportExportService.h} (68%) diff --git a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp index eb80fe269785..869970c347f6 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 a1e00a797a57..26c9fad4554c 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 8ce5d0ef7a83..e153e65f82c9 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 05feb249df37..dcfe9e0c8c2c 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 9f99c6556aff..8e3e72c63644 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 40e7cfa1d262..ce30f248aa05 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 a7a68708c51d..adb7c4ae34cd 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 6e5c82d7100f..f53451a2cdd4 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 c463272d0927..4b2a55fb4c2a 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 aa4772c7b73b..4423de000514 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 85cefef882bf..7f7bf012226e 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 0194510c96eb..74c2eb3fbb3a 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 d7028b07b025..f6532f0cf175 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 e2cd4d0116df..000000000000 --- 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 76bb3ee340d3..1a5806ee7913 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 e77a214edd1b..600fa67df6b7 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 e1d43e8f5b7c..59120d76e381 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 20e914f2dcb6..4499df2800a2 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 af9835dae6c1..b6e60f4ba4bd 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 ec70ec2769a2..000000000000 --- 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 000000000000..1c4897c503e9 --- /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 0cac53620e98..ba647ccdbf82 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 eb0724063dae..809e12b23431 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 } }; From 7c32133736a52950bf460b184265ab351a7d2b62 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Fri, 23 Mar 2012 11:30:57 +0000 Subject: [PATCH 004/109] Bug 737434 - Reuse tiles when resizing tiled textures. r=bgirard Instead of scrapping all tiles when a tiled texture is resized, reuse as many tiles as possible. Strictly speaking, more tiles could be reused, but the selected reuse strategy maintains the spacial structure of the texture, and we don't often resize in such a way as to make this inefficient. --- gfx/gl/GLContext.cpp | 102 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 11d9be3e29f1..5eec14d25a79 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -866,6 +866,8 @@ TiledTextureImage::TiledTextureImage(GLContext* aGL, : TextureImage(aSize, LOCAL_GL_CLAMP_TO_EDGE, aContentType, aUseNearestFilter) , mCurrentImage(0) , mInUpdate(false) + , mRows(0) + , mColumns(0) , mGL(aGL) , mUseNearestFilter(aUseNearestFilter) , mTextureState(Created) @@ -1114,30 +1116,98 @@ TiledTextureImage::ApplyFilter() } /* - * simple resize, just discards everything. we can be more clever just - * adding or discarding tiles, but do we want this? + * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per + * column. A tile on a column is reused if it hasn't changed size, otherwise it + * is discarded/replaced. Extra tiles on a column are pruned after iterating + * each column, and extra rows are pruned after iteration over the entire image + * finishes. */ void TiledTextureImage::Resize(const nsIntSize& aSize) { if (mSize == aSize && mTextureState != Created) { return; } - mSize = aSize; - mImages.Clear(); - // calculate rows and columns, rounding up - mColumns = (aSize.width + mTileSize - 1) / mTileSize; - mRows = (aSize.height + mTileSize - 1) / mTileSize; - for (unsigned int row = 0; row < mRows; row++) { - for (unsigned int col = 0; col < mColumns; col++) { - nsIntSize size( // use tilesize first, then the remainder - (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, - (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); - nsRefPtr teximg = - mGL->TileGenFunc(size, mContentType, mUseNearestFilter); - mImages.AppendElement(teximg.forget()); - } + // calculate rows and columns, rounding up + unsigned int columns = (aSize.width + mTileSize - 1) / mTileSize; + unsigned int rows = (aSize.height + mTileSize - 1) / mTileSize; + + // Iterate over old tile-store and insert/remove tiles as necessary + int row; + unsigned int i = 0; + for (row = 0; row < (int)rows; row++) { + // If we've gone beyond how many rows there were before, set mColumns to + // zero so that we only create new tiles. + if (row >= (int)mRows) + mColumns = 0; + + // Similarly, if we're on the last row of old tiles and the height has + // changed, discard all tiles in that row. + // This will cause the pruning of columns not to work, but we don't need + // to worry about that, as no more tiles will be reused past this point + // anyway. + if ((row == (int)mRows - 1) && (aSize.height != mSize.height)) + mColumns = 0; + + int col; + for (col = 0; col < (int)columns; col++) { + nsIntSize size( // use tilesize first, then the remainder + (col+1) * mTileSize > (unsigned int)aSize.width ? aSize.width % mTileSize : mTileSize, + (row+1) * mTileSize > (unsigned int)aSize.height ? aSize.height % mTileSize : mTileSize); + + bool replace = false; + + // Check if we can re-use old tiles. + if (col < (int)mColumns) { + // Reuse an existing tile. If the tile is an end-tile and the + // width differs, replace it instead. + if (mSize.width != aSize.width) { + if (col == (int)mColumns - 1) { + // Tile at the end of the old column, replace it with + // a new one. + replace = true; + } else if (col == (int)columns - 1) { + // Tile at the end of the new column, create a new one. + } else { + // Before the last column on both the old and new sizes, + // reuse existing tile. + i++; + continue; + } + } else { + // Width hasn't changed, reuse existing tile. + i++; + continue; + } + } + + // Create a new tile. + nsRefPtr teximg = + mGL->TileGenFunc(size, mContentType, mUseNearestFilter); + if (replace) + mImages.ReplaceElementAt(i, &teximg.forget()); + else + mImages.InsertElementAt(i, teximg.forget()); + i++; + } + + // Prune any unused tiles on the end of the column. + if (row < (int)mRows) { + for (col = (int)mColumns - col; col > 0; col--) { + mImages.RemoveElementAt(i); + } + } } + + // Prune any unused tiles at the end of the store. + unsigned int length = mImages.Length(); + for (; i < length; i++) + mImages.RemoveElementAt(mImages.Length()-1); + + // Reset tile-store properties. + mRows = rows; + mColumns = columns; + mSize = aSize; mTextureState = Allocated; } From a44c1911bab4b35c80bd48f1c32ae95f31a32a02 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 23 Mar 2012 12:13:33 +0000 Subject: [PATCH 005/109] bug 738197 - handle potential failures in gfxFont::GetShapedWord more robustly. r=roc --- gfx/thebes/gfxFont.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 733d7e721507..60a30a0d197f 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -1935,6 +1935,10 @@ gfxFont::GetShapedWord(gfxContext *aContext, aFlags); CacheHashEntry *entry = mWordCache.PutEntry(key); + if (!entry) { + NS_WARNING("failed to create word cache entry - expect missing text"); + return nsnull; + } gfxShapedWord *sw = entry->mShapedWord; Telemetry::Accumulate(Telemetry::WORD_CACHE_LOOKUP_LEN, aLength); Telemetry::Accumulate(Telemetry::WORD_CACHE_LOOKUP_SCRIPT, aRunScript); @@ -1950,20 +1954,21 @@ gfxFont::GetShapedWord(gfxContext *aContext, aRunScript, aAppUnitsPerDevUnit, aFlags); - NS_ASSERTION(sw != nsnull, - "failed to create gfxShapedWord - expect missing text"); if (!sw) { + NS_WARNING("failed to create gfxShapedWord - expect missing text"); return nsnull; } - bool ok; + bool ok = false; if (sizeof(T) == sizeof(PRUnichar)) { ok = ShapeWord(aContext, sw, (const PRUnichar*)aText); } else { nsAutoString utf16; AppendASCIItoUTF16(nsDependentCSubstring((const char*)aText, aLength), utf16); - ok = ShapeWord(aContext, sw, utf16.BeginReading()); + if (utf16.Length() == aLength) { + ok = ShapeWord(aContext, sw, utf16.BeginReading()); + } } NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); From 65d37d97ceee0638b0201aa03d71e6eadf7492bd Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 23 Mar 2012 12:13:44 +0000 Subject: [PATCH 006/109] bug 737412 - handle null sizeOfEntryExcludingThis pointer within ns{T,Base}Hashtable SizeOfExcludingThis functions. r=jfkthame --- xpcom/glue/nsBaseHashtable.h | 15 ++++++++------- xpcom/glue/nsTHashtable.h | 7 +++++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h index 6ab8590865a9..a1a8146ba417 100644 --- a/xpcom/glue/nsBaseHashtable.h +++ b/xpcom/glue/nsBaseHashtable.h @@ -274,14 +274,15 @@ public: size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, nsMallocSizeOfFun mallocSizeOf, void *userArg = nsnull) { - if (IsInitialized()) { - s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; - return PL_DHashTableSizeOfExcludingThis(&this->mTable, - s_SizeOfStub, - mallocSizeOf, - &args); + if (!IsInitialized()) { + return 0; } - return 0; + if (sizeOfEntryExcludingThis) { + s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; + return PL_DHashTableSizeOfExcludingThis(&this->mTable, s_SizeOfStub, + mallocSizeOf, &args); + } + return PL_DHashTableSizeOfExcludingThis(&this->mTable, NULL, mallocSizeOf); } protected: diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h index 762517fb0aa3..e2a001358586 100644 --- a/xpcom/glue/nsTHashtable.h +++ b/xpcom/glue/nsTHashtable.h @@ -288,11 +288,14 @@ public: size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, nsMallocSizeOfFun mallocSizeOf, void *userArg = NULL) const { - if (IsInitialized()) { + if (!IsInitialized()) { + return 0; + } + if (sizeOfEntryExcludingThis) { s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; return PL_DHashTableSizeOfExcludingThis(&mTable, s_SizeOfStub, mallocSizeOf, &args); } - return 0; + return PL_DHashTableSizeOfExcludingThis(&mTable, NULL, mallocSizeOf); } #ifdef DEBUG From 1324886d80f37afda506603bdd5ceb2551c09416 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 23 Mar 2012 12:13:56 +0000 Subject: [PATCH 007/109] bug 737863 - nsBaseHashtable::SizeOfExcludingThis should be declared const. r=njn --- xpcom/glue/nsBaseHashtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h index a1a8146ba417..4d0ec87ee37d 100644 --- a/xpcom/glue/nsBaseHashtable.h +++ b/xpcom/glue/nsBaseHashtable.h @@ -272,7 +272,7 @@ public: * @return the summed size of all the entries */ size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, - nsMallocSizeOfFun mallocSizeOf, void *userArg = nsnull) + nsMallocSizeOfFun mallocSizeOf, void *userArg = nsnull) const { if (!IsInitialized()) { return 0; From c278b0a18fd2952a89ee7a59ddc0ba6f1c1c1869 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 23 Mar 2012 12:14:06 +0000 Subject: [PATCH 008/109] bug 688125 - part 1 - add memory reporting for the platform font list. r=njn --- gfx/thebes/gfxDWriteFontList.cpp | 75 ++++++++++- gfx/thebes/gfxDWriteFontList.h | 17 ++- gfx/thebes/gfxFT2FontList.cpp | 17 +++ gfx/thebes/gfxFT2FontList.h | 5 + gfx/thebes/gfxFont.cpp | 64 +++++++++ gfx/thebes/gfxFont.h | 19 +++ gfx/thebes/gfxFontUtils.h | 16 ++- gfx/thebes/gfxGDIFontList.cpp | 46 ++++++- gfx/thebes/gfxGDIFontList.h | 10 +- gfx/thebes/gfxMacPlatformFontList.h | 6 + gfx/thebes/gfxMacPlatformFontList.mm | 25 +++- gfx/thebes/gfxPlatformFontList.cpp | 185 +++++++++++++++++++++++++-- gfx/thebes/gfxPlatformFontList.h | 33 +++++ 13 files changed, 477 insertions(+), 41 deletions(-) diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp index 0cdf0b1bc0a6..61077420fa2f 100644 --- a/gfx/thebes/gfxDWriteFontList.cpp +++ b/gfx/thebes/gfxDWriteFontList.cpp @@ -249,6 +249,23 @@ gfxDWriteFontFamily::LocalizedName(nsAString &aLocalizedName) aLocalizedName = nsDependentString(famName.Elements()); } +void +gfxDWriteFontFamily::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + gfxFontFamily::SizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for |mDWFamily| +} + +void +gfxDWriteFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + //////////////////////////////////////////////////////////////////////////////// // gfxDWriteFontEntry @@ -377,7 +394,8 @@ gfxDWriteFontEntry::ReadCMAP() unicodeFont, symbolFont); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); + NS_ConvertUTF16toUTF8(mName).get(), + mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -424,7 +442,8 @@ gfxDWriteFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); + NS_ConvertUTF16toUTF8(mName).get(), + mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -544,6 +563,23 @@ gfxDWriteFontEntry::IsCJKFont() return mIsCJK; } +void +gfxDWriteFontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + gfxFontEntry::SizeOfExcludingThis(aMallocSizeOf, aSizes); + // TODO: + // This doesn't currently account for the |mFont| and |mFontFile| members +} + +void +gfxDWriteFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + //////////////////////////////////////////////////////////////////////////////// // gfxDWriteFontList @@ -587,7 +623,6 @@ gfxFontEntry * gfxDWriteFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, const nsAString& aFullname) { - bool found; gfxFontEntry *lookup; // initialize name lookup tables if needed @@ -596,8 +631,8 @@ gfxDWriteFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, } // lookup in name lookup tables, return null if not found - if (!(lookup = mPostscriptNames.GetWeak(aFullname, &found)) && - !(lookup = mFullnames.GetWeak(aFullname, &found))) + if (!(lookup = mPostscriptNames.GetWeak(aFullname)) && + !(lookup = mFullnames.GetWeak(aFullname))) { return nsnull; } @@ -1182,8 +1217,8 @@ gfxDWriteFontList::ResolveFontName(const nsAString& aFontName, nsAutoString keyName(aFontName); BuildKeyNameFromFontName(keyName); - nsRefPtr ff; - if (mFontSubstitutes.Get(keyName, &ff)) { + gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName); + if (ff) { aResolvedFontName = ff->Name(); return true; } @@ -1195,6 +1230,32 @@ gfxDWriteFontList::ResolveFontName(const nsAString& aFontName, return gfxPlatformFontList::ResolveFontName(aFontName, aResolvedFontName); } +void +gfxDWriteFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + gfxPlatformFontList::SizeOfExcludingThis(aMallocSizeOf, aSizes); + + aSizes->mFontListSize += + mFontSubstitutes.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, + aMallocSizeOf); + + aSizes->mFontListSize += + mNonExistingFonts.SizeOfExcludingThis(aMallocSizeOf); + for (PRUint32 i = 0; i < mNonExistingFonts.Length(); ++i) { + aSizes->mFontListSize += + mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +} + +void +gfxDWriteFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + static nsresult GetFamilyName(IDWriteFont *aFont, nsString& aFamilyName) { HRESULT hr; diff --git a/gfx/thebes/gfxDWriteFontList.h b/gfx/thebes/gfxDWriteFontList.h index c1db3f0456f5..5287402cd24a 100644 --- a/gfx/thebes/gfxDWriteFontList.h +++ b/gfx/thebes/gfxDWriteFontList.h @@ -79,6 +79,11 @@ public: void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: /** This font family's directwrite fontfamily object */ nsRefPtr mDWFamily; @@ -179,6 +184,11 @@ public: void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } bool GetForceGDIClassic() { return mForceGDIClassic; } + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: friend class gfxDWriteFont; friend class gfxDWriteFontList; @@ -380,6 +390,11 @@ public: gfxFloat GetForceGDIClassicMaxFontSize() { return mForceGDIClassicMaxFontSize; } + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + private: friend class gfxDWriteFontFamily; @@ -401,7 +416,7 @@ private: */ nsTArray mNonExistingFonts; - typedef nsDataHashtable > FontTable; + typedef nsRefPtrHashtable FontTable; /** * Table of font substitutes, we grab this from the registry to get diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp index 9165207a106b..82c1819f57d6 100644 --- a/gfx/thebes/gfxFT2FontList.cpp +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -389,6 +389,23 @@ FT2FontEntry::GetFontTable(PRUint32 aTableTag, return NS_OK; } +void +FT2FontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + gfxFontEntry::SizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontListSize += + mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +void +FT2FontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + /* * FT2FontFamily * A standard gfxFontFamily; just adds a method used to support sending diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h index 9d430c452c96..9299b9418ee6 100644 --- a/gfx/thebes/gfxFT2FontList.h +++ b/gfx/thebes/gfxFT2FontList.h @@ -102,6 +102,11 @@ public: nsresult ReadCMAP(); nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + FT_Face mFTFace; cairo_font_face_t *mFontFace; diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 60a30a0d197f..850bc5678e1f 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -402,6 +402,44 @@ gfxFontEntry::CheckForGraphiteTables() } #endif +/* static */ size_t +gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis + (FontTableHashEntry *aEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + if (aEntry->mBlob) { + FontListSizes *sizes = static_cast(aUserArg); + sizes->mFontTableCacheSize += aMallocSizeOf(aEntry->mBlob); + sizes->mFontTableCacheSize += + aMallocSizeOf(hb_blob_get_data(aEntry->mBlob, NULL)); + } + + // the size of the blob is recorded in the FontListSizes record, + // so we return 0 here for the function result + return 0; +} + +void +gfxFontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + aSizes->mCharMapsSize += mCharacterMap.SizeOfExcludingThis(aMallocSizeOf); + aSizes->mFontTableCacheSize += + mFontTableCache.SizeOfExcludingThis( + FontTableHashEntry::SizeOfEntryExcludingThis, + aMallocSizeOf, aSizes); +} + +void +gfxFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + ////////////////////////////////////////////////////////////////////////////// // // class gfxFontFamily @@ -976,6 +1014,32 @@ gfxFontFamily::FindFont(const nsAString& aPostscriptName) return nsnull; } +void +gfxFontFamily::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += + mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + aSizes->mCharMapsSize += mCharacterMap.SizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); + for (PRUint32 i = 0; i < mAvailableFonts.Length(); ++i) { + gfxFontEntry *fe = mAvailableFonts[i]; + if (fe) { + fe->SizeOfIncludingThis(aMallocSizeOf, aSizes); + } + } +} + +void +gfxFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + /* * gfxFontCache - global cache of gfxFont instances. * Expires unused fonts after a short interval; diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index 1a246a51c239..65fc389f1d76 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -90,6 +90,8 @@ struct THEBES_API gfxFontFeature { // to features that select among multiple alternatives }; +struct FontListSizes; + inline bool operator<(const gfxFontFeature& a, const gfxFontFeature& b) { @@ -313,6 +315,12 @@ public: hb_blob_t *ShareFontTableAndGetBlob(PRUint32 aTag, FallibleTArray* aTable); + // For memory reporting + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + nsString mName; bool mItalic : 1; @@ -467,6 +475,11 @@ private: void Clear(); + static size_t + SizeOfEntryExcludingThis(FontTableHashEntry *aEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg); + private: static void DeleteFontTableBlobData(void *aBlobData); // not implemented @@ -633,6 +646,12 @@ public: // if so set the mIsSimpleFamily flag (defaults to False before we've checked) void CheckForSimpleFamily(); + // For memory reporter + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: // fills in an array with weights of faces that match style, // returns whether any matching entries found diff --git a/gfx/thebes/gfxFontUtils.h b/gfx/thebes/gfxFontUtils.h index e7f853f4893a..3d6ba81f7d93 100644 --- a/gfx/thebes/gfxFontUtils.h +++ b/gfx/thebes/gfxFontUtils.h @@ -264,14 +264,18 @@ public: } } - PRUint32 GetSize() { - PRUint32 size = 0; + size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + size_t total = mBlocks.SizeOfExcludingThis(aMallocSizeOf); for (PRUint32 i = 0; i < mBlocks.Length(); i++) { - if (mBlocks[i]) - size += sizeof(Block); - size += sizeof(nsAutoPtr); + if (mBlocks[i]) { + total += aMallocSizeOf(mBlocks[i]); + } } - return size; + return total; + } + + size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } // clear out all blocks in the array diff --git a/gfx/thebes/gfxGDIFontList.cpp b/gfx/thebes/gfxGDIFontList.cpp index 3230fa287fcb..c40304bf73f0 100644 --- a/gfx/thebes/gfxGDIFontList.cpp +++ b/gfx/thebes/gfxGDIFontList.cpp @@ -246,7 +246,8 @@ GDIFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); + NS_ConvertUTF16toUTF8(mName).get(), + mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -459,6 +460,14 @@ GDIFontEntry::CreateFontEntry(const nsAString& aName, return fe; } +void +GDIFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + /*************************************************************** * * GDIFontFamily @@ -658,7 +667,7 @@ gfxGDIFontList::GetFontSubstitutes() nsAutoString substituteName; substituteName.AssignLiteral("Courier"); BuildKeyNameFromFontName(substituteName); - if (!mFontSubstitutes.Get(substituteName)) { + if (!mFontSubstitutes.GetWeak(substituteName)) { gfxFontFamily *ff; nsAutoString actualFontName; actualFontName.AssignLiteral("Courier New"); @@ -743,7 +752,6 @@ gfxFontEntry* gfxGDIFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, const nsAString& aFullname) { - bool found; gfxFontEntry *lookup; // initialize name lookup tables if needed @@ -752,8 +760,8 @@ gfxGDIFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, } // lookup in name lookup tables, return null if not found - if (!(lookup = mPostscriptNames.GetWeak(aFullname, &found)) && - !(lookup = mFullnames.GetWeak(aFullname, &found))) + if (!(lookup = mPostscriptNames.GetWeak(aFullname)) && + !(lookup = mFullnames.GetWeak(aFullname))) { return nsnull; } @@ -1038,8 +1046,8 @@ gfxGDIFontList::ResolveFontName(const nsAString& aFontName, nsAString& aResolved nsAutoString keyName(aFontName); BuildKeyNameFromFontName(keyName); - nsRefPtr ff; - if (mFontSubstitutes.Get(keyName, &ff)) { + gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName); + if (ff) { aResolvedFontName = ff->Name(); return true; } @@ -1052,3 +1060,27 @@ gfxGDIFontList::ResolveFontName(const nsAString& aFontName, nsAString& aResolved return false; } + +void +gfxGDIFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + gfxPlatformFontList::SizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontListSize += + mFontSubstitutes.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, + aMallocSizeOf); + aSizes->mFontListSize += + mNonExistingFonts.SizeOfExcludingThis(aMallocSizeOf); + for (PRUint32 i = 0; i < mNonExistingFonts.Length(); ++i) { + aSizes->mFontListSize += + mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +} + +void +gfxGDIFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxGDIFontList.h b/gfx/thebes/gfxGDIFontList.h index 320879b3d73b..96834e023b53 100644 --- a/gfx/thebes/gfxGDIFontList.h +++ b/gfx/thebes/gfxGDIFontList.h @@ -274,6 +274,9 @@ public: virtual bool TestCharacterMap(PRUint32 aCh); + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + // create a font entry for a font with a given name static GDIFontEntry* CreateFontEntry(const nsAString& aName, gfxWindowsFontType aFontType, @@ -347,6 +350,11 @@ public: virtual bool ResolveFontName(const nsAString& aFontName, nsAString& aResolvedFontName); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + private: friend class gfxWindowsPlatform; @@ -361,7 +369,7 @@ private: DWORD fontType, LPARAM lParam); - typedef nsDataHashtable > FontTable; + typedef nsRefPtrHashtable FontTable; FontTable mFontSubstitutes; nsTArray mNonExistingFonts; diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h index 30d527b240e4..91ec2c73080f 100644 --- a/gfx/thebes/gfxMacPlatformFontList.h +++ b/gfx/thebes/gfxMacPlatformFontList.h @@ -111,6 +111,9 @@ public: virtual nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: virtual bool HasFontTable(PRUint32 aTableTag); @@ -134,6 +137,9 @@ public: virtual nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: virtual bool HasFontTable(PRUint32 aTableTag); }; diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm index 65e78092118a..01a75ec7c0c5 100644 --- a/gfx/thebes/gfxMacPlatformFontList.mm +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -270,7 +270,7 @@ MacOSFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", NS_ConvertUTF16toUTF8(mName).get(), - mCharacterMap.GetSize())); + mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -398,6 +398,14 @@ ATSFontEntry::HasFontTable(PRUint32 aTableTag) (::ATSFontGetTable(fontRef, aTableTag, 0, 0, 0, &size) == noErr); } +void +ATSFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + /* CGFontEntry - used on Mac OS X 10.6+ */ #pragma mark- @@ -486,6 +494,14 @@ CGFontEntry::HasFontTable(PRUint32 aTableTag) return true; } +void +CGFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + /* gfxMacFontFamily */ #pragma mark- @@ -811,10 +827,9 @@ gfxMacPlatformFontList::InitSingleFaceList() #endif // add only if doesn't exist already - bool found; - gfxFontFamily *familyEntry; - if (!(familyEntry = mFontFamilies.GetWeak(key, &found))) { - familyEntry = new gfxSingleFaceMacFontFamily(familyName); + if (!mFontFamilies.GetWeak(key)) { + gfxFontFamily *familyEntry = + new gfxSingleFaceMacFontFamily(familyName); familyEntry->AddFontEntry(fontEntry); familyEntry->SetHasStyles(true); mFontFamilies.Put(key, familyEntry); diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp index b65675f8ad93..5c467b767681 100644 --- a/gfx/thebes/gfxPlatformFontList.cpp +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -130,6 +130,60 @@ gfxFontListPrefObserver::Observe(nsISupports *aSubject, return NS_OK; } +NS_IMPL_ISUPPORTS1(gfxPlatformFontList::MemoryReporter, nsIMemoryMultiReporter) + +NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontListMallocSizeOf, "font-list") + +NS_IMETHODIMP +gfxPlatformFontList::MemoryReporter::GetName(nsACString &aName) +{ + aName.AssignLiteral("font-list"); + return NS_OK; +} + +NS_IMETHODIMP +gfxPlatformFontList::MemoryReporter::CollectReports + (nsIMemoryMultiReporterCallback* aCb, + nsISupports* aClosure) +{ + FontListSizes sizes; + + gfxPlatformFontList::PlatformFontList()->SizeOfIncludingThis(&FontListMallocSizeOf, + &sizes); + + aCb->Callback(EmptyCString(), + NS_LITERAL_CSTRING("explicit/gfx/font-list"), + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + sizes.mFontListSize, + NS_LITERAL_CSTRING("Memory used to manage the list of font families and faces."), + aClosure); + + aCb->Callback(EmptyCString(), + NS_LITERAL_CSTRING("explicit/gfx/font-charmaps"), + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + sizes.mCharMapsSize, + NS_LITERAL_CSTRING("Memory used to record the character coverage of individual fonts."), + aClosure); + + if (sizes.mFontTableCacheSize) { + aCb->Callback(EmptyCString(), + NS_LITERAL_CSTRING("explicit/gfx/font-tables"), + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + sizes.mFontTableCacheSize, + NS_LITERAL_CSTRING("Memory used for cached font metrics and layout tables."), + aClosure); + } + + return NS_OK; +} + +NS_IMETHODIMP +gfxPlatformFontList::MemoryReporter::GetExplicitNonHeap(PRInt64* aAmount) +{ + // This reporter only measures heap memory. + *aAmount = 0; + return NS_OK; +} gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames) : mNeedFullnamePostscriptNames(aNeedFullnamePostscriptNames), @@ -184,6 +238,8 @@ gfxPlatformFontList::InitFontList() mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls + NS_RegisterMemoryMultiReporter(new MemoryReporter); + sPlatformFontList = this; return NS_OK; @@ -244,12 +300,11 @@ gfxPlatformFontList::PreloadNamesList() PRUint32 numFonts = preloadFonts.Length(); for (PRUint32 i = 0; i < numFonts; i++) { - bool found; nsAutoString key; GenerateFontListKey(preloadFonts[i], key); // only search canonical names! - gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key, &found); + gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key); if (familyEntry) { familyEntry->ReadOtherFamilyNames(this); } @@ -549,18 +604,17 @@ gfxPlatformFontList::FindFamily(const nsAString& aFamily) { nsAutoString key; gfxFontFamily *familyEntry; - bool found; GenerateFontListKey(aFamily, key); NS_ASSERTION(mFontFamilies.Count() != 0, "system font list was not initialized correctly"); // lookup in canonical (i.e. English) family name list - if ((familyEntry = mFontFamilies.GetWeak(key, &found))) { + if ((familyEntry = mFontFamilies.GetWeak(key))) { return familyEntry; } // lookup in other family names list (mostly localized names) - if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found)) != nsnull) { + if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nsnull) { return familyEntry; } @@ -571,7 +625,7 @@ gfxPlatformFontList::FindFamily(const nsAString& aFamily) // in practice so avoid pulling in names at startup if (!mOtherFamilyNamesInitialized && !IsASCII(aFamily)) { InitOtherFamilyNames(); - if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found)) != nsnull) { + if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nsnull) { return familyEntry; } } @@ -608,10 +662,9 @@ void gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& aOtherFamilyName) { nsAutoString key; - bool found; GenerateFontListKey(aOtherFamilyName, key); - if (!mOtherFamilyNames.GetWeak(key, &found)) { + if (!mOtherFamilyNames.GetWeak(key)) { mOtherFamilyNames.Put(key, aFamilyEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-otherfamily) canonical family: %s, " @@ -627,9 +680,7 @@ gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& void gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname) { - bool found; - - if (!mFullnames.GetWeak(aFullname, &found)) { + if (!mFullnames.GetWeak(aFullname)) { mFullnames.Put(aFullname, aFontEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n", @@ -642,9 +693,7 @@ gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname) void gfxPlatformFontList::AddPostscriptName(gfxFontEntry *aFontEntry, nsAString& aPostscriptName) { - bool found; - - if (!mPostscriptNames.GetWeak(aPostscriptName, &found)) { + if (!mPostscriptNames.GetWeak(aPostscriptName)) { mPostscriptNames.Put(aPostscriptName, aFontEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n", @@ -714,3 +763,111 @@ gfxPlatformFontList::FinishLoader() mFontFamiliesToLoad.Clear(); mNumFamilies = 0; } + +// Support for memory reporting + +static size_t +SizeOfFamilyEntryExcludingThis(const nsAString& aKey, + const nsRefPtr& aFamily, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + FontListSizes *sizes = static_cast(aUserArg); + aFamily->SizeOfExcludingThis(aMallocSizeOf, sizes); + + sizes->mFontListSize += aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + + // we return zero here because the measurements have been added directly + // to the relevant fields of the FontListSizes record + return 0; +} + +// this is also used by subclasses that hold additional hashes of family names +/*static*/ size_t +gfxPlatformFontList::SizeOfFamilyNameEntryExcludingThis + (const nsAString& aKey, + const nsRefPtr& aFamily, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + // we don't count the size of the family here, because this is an *extra* + // reference to a family that will have already been counted in the main list + return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +static size_t +SizeOfFontNameEntryExcludingThis(const nsAString& aKey, + const nsRefPtr& aFont, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + // the font itself is counted by its owning family; here we only care about + // the name stored in the hashtable key + return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +static size_t +SizeOfPrefFontEntryExcludingThis + (const PRUint32& aKey, + const nsTArray >& aList, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + // again, we only care about the size of the array itself; we don't follow + // the refPtrs stored in it, because they point to entries already owned + // and accounted-for by the main font list + return aList.SizeOfExcludingThis(aMallocSizeOf); +} + +static size_t +SizeOfStringEntryExcludingThis(nsStringHashKey* aHashEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + return aHashEntry->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +void +gfxPlatformFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += + mFontFamilies.SizeOfExcludingThis(SizeOfFamilyEntryExcludingThis, + aMallocSizeOf, aSizes); + + aSizes->mFontListSize += + mOtherFamilyNames.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, + aMallocSizeOf); + + if (mNeedFullnamePostscriptNames) { + aSizes->mFontListSize += + mFullnames.SizeOfExcludingThis(SizeOfFontNameEntryExcludingThis, + aMallocSizeOf); + aSizes->mFontListSize += + mPostscriptNames.SizeOfExcludingThis(SizeOfFontNameEntryExcludingThis, + aMallocSizeOf); + } + + aSizes->mFontListSize += + mCodepointsWithNoFonts.SizeOfExcludingThis(aMallocSizeOf); + aSizes->mFontListSize += + mReplacementCharFallbackFamily.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + aSizes->mFontListSize += + mFontFamiliesToLoad.SizeOfExcludingThis(aMallocSizeOf); + + aSizes->mFontListSize += + mPrefFonts.SizeOfExcludingThis(SizeOfPrefFontEntryExcludingThis, + aMallocSizeOf); + + aSizes->mFontListSize += + mBadUnderlineFamilyNames.SizeOfExcludingThis(SizeOfStringEntryExcludingThis, + aMallocSizeOf); +} + +void +gfxPlatformFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const +{ + aSizes->mFontListSize += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h index 59509552660d..e1cdc36c9025 100644 --- a/gfx/thebes/gfxPlatformFontList.h +++ b/gfx/thebes/gfxPlatformFontList.h @@ -47,6 +47,7 @@ #include "gfxFont.h" #include "gfxPlatform.h" +#include "nsIMemoryReporter.h" #include "mozilla/FunctionTimer.h" // gfxPlatformFontList is an abstract class for the global font list on the system; @@ -57,6 +58,18 @@ // Much of this is based on the old gfxQuartzFontCache, but adapted for use on all platforms. +struct FontListSizes { + FontListSizes() + : mFontListSize(0), mFontTableCacheSize(0), mCharMapsSize(0) + { } + + size_t mFontListSize; // size of the font list and dependent objects + // (font family and face names, etc), but NOT + // including the font table cache and the cmaps + size_t mFontTableCacheSize; // memory used for the gfxFontEntry table caches + size_t mCharMapsSize; // memory used for cmap coverage info +}; + class gfxPlatformFontList : protected gfxFontInfoLoader { public: @@ -141,7 +154,20 @@ public: // (platforms may override, eg Mac) virtual bool GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontListSizes* aSizes) const; + protected: + class MemoryReporter + : public nsIMemoryMultiReporter + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYMULTIREPORTER + }; + gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true); static gfxPlatformFontList *sPlatformFontList; @@ -200,6 +226,13 @@ protected: virtual bool RunLoader(); virtual void FinishLoader(); + // used by memory reporter to accumulate sizes of family names in the hash + static size_t + SizeOfFamilyNameEntryExcludingThis(const nsAString& aKey, + const nsRefPtr& aFamily, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg); + // canonical family name ==> family entry (unique, one name per family entry) nsRefPtrHashtable mFontFamilies; From 5ffe5203cff4491f45417e1438c7209aa446c5be Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 23 Mar 2012 12:14:16 +0000 Subject: [PATCH 009/109] bug 688125 - part 2 - add memory reporting for fonts in the gfxFontCache. r=njn --- gfx/thebes/gfxDWriteFonts.cpp | 17 +++++ gfx/thebes/gfxDWriteFonts.h | 5 ++ gfx/thebes/gfxFT2Fonts.cpp | 17 +++++ gfx/thebes/gfxFT2Fonts.h | 5 ++ gfx/thebes/gfxFont.cpp | 139 ++++++++++++++++++++++++++++++++-- gfx/thebes/gfxFont.h | 46 ++++++++++- gfx/thebes/gfxGDIFont.cpp | 17 +++++ gfx/thebes/gfxGDIFont.h | 5 ++ gfx/thebes/gfxMacFont.cpp | 16 ++++ gfx/thebes/gfxMacFont.h | 5 ++ 10 files changed, 260 insertions(+), 12 deletions(-) diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp index b5e9beb8231c..48d35cce0016 100644 --- a/gfx/thebes/gfxDWriteFonts.cpp +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -762,3 +762,20 @@ gfxDWriteFont::MeasureGlyphWidth(PRUint16 aGlyph) } return 0; } + +void +gfxDWriteFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += aMallocSizeOf(mMetrics) + + mGlyphWidths.SizeOfExcludingThis(nsnull, aMallocSizeOf); +} + +void +gfxDWriteFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h index 0338776f8dcb..135903ae91d6 100644 --- a/gfx/thebes/gfxDWriteFonts.h +++ b/gfx/thebes/gfxDWriteFonts.h @@ -92,6 +92,11 @@ public: virtual mozilla::TemporaryRef GetGlyphRenderingOptions(); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: friend class gfxDWriteShaper; diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index a2cd06e40803..f4e1d6cf27f1 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -661,3 +661,20 @@ gfxFT2Font::FillGlyphDataForChar(PRUint32 ch, CachedGlyphData *gd) gd->rsbDelta = face->glyph->rsb_delta; gd->xAdvance = face->glyph->advance.x; } + +void +gfxFT2Font::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += + mCharGlyphCache.SizeOfExcludingThis(nsnull, aMallocSizeOf); +} + +void +gfxFT2Font::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h index 61c8b51093ed..7abf39f4416c 100644 --- a/gfx/thebes/gfxFT2Fonts.h +++ b/gfx/thebes/gfxFT2Fonts.h @@ -96,6 +96,11 @@ public: // new functions return &entry->mData; } + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: virtual bool ShapeWord(gfxContext *aContext, gfxShapedWord *aShapedWord, diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 850bc5678e1f..a72edd8cb411 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -47,7 +47,6 @@ #include "nsReadableUtils.h" #include "nsExpirationTracker.h" #include "nsILanguageAtomService.h" -#include "nsIMemoryReporter.h" #include "nsITimer.h" #include "gfxFont.h" @@ -1048,6 +1047,52 @@ gfxFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, * shaped-word caches to free up memory. */ +NS_IMPL_ISUPPORTS1(gfxFontCache::MemoryReporter, nsIMemoryMultiReporter) + +NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontCacheMallocSizeOf, "font-cache") + +NS_IMETHODIMP +gfxFontCache::MemoryReporter::GetName(nsACString &aName) +{ + aName.AssignLiteral("font-cache"); + return NS_OK; +} + +NS_IMETHODIMP +gfxFontCache::MemoryReporter::CollectReports + (nsIMemoryMultiReporterCallback* aCb, + nsISupports* aClosure) +{ + FontCacheSizes sizes; + + gfxFontCache::GetCache()->SizeOfIncludingThis(&FontCacheMallocSizeOf, + &sizes); + + aCb->Callback(EmptyCString(), + NS_LITERAL_CSTRING("explicit/gfx/font-cache"), + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + sizes.mFontInstances, + NS_LITERAL_CSTRING("Memory used for active font instances."), + aClosure); + + aCb->Callback(EmptyCString(), + NS_LITERAL_CSTRING("explicit/gfx/font-shaped-words"), + nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, + sizes.mShapedWords, + NS_LITERAL_CSTRING("Memory used to cache shaped glyph data."), + aClosure); + + return NS_OK; +} + +NS_IMETHODIMP +gfxFontCache::MemoryReporter::GetExplicitNonHeap(PRInt64* aAmount) +{ + // This reporter only measures heap memory. + *aAmount = 0; + return NS_OK; +} + // Observer for the memory-pressure notification, to trigger // flushing of the shaped-word caches class MemoryPressureObserver : public nsIObserver, @@ -1079,7 +1124,11 @@ gfxFontCache::Init() { NS_ASSERTION(!gGlobalCache, "Where did this come from?"); gGlobalCache = new gfxFontCache(); - return gGlobalCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + if (!gGlobalCache) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_RegisterMemoryMultiReporter(new MemoryReporter); + return NS_OK; } void @@ -1240,6 +1289,39 @@ gfxFontCache::ClearCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) return PL_DHASH_NEXT; } +/*static*/ +size_t +gfxFontCache::SizeOfFontEntryExcludingThis(HashEntry* aHashEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + HashEntry *entry = static_cast(aHashEntry); + FontCacheSizes *sizes = static_cast(aUserArg); + entry->mFont->SizeOfExcludingThis(aMallocSizeOf, sizes); + + // The font records its size in the |sizes| parameter, so we return zero + // here to the hashtable enumerator. + return 0; +} + +void +gfxFontCache::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + // TODO: add the overhead of the expiration tracker (generation arrays) + + mFonts.SizeOfExcludingThis(SizeOfFontEntryExcludingThis, + aMallocSizeOf, aSizes); +} + +void +gfxFontCache::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) { @@ -2589,10 +2671,40 @@ gfxFont::SynthesizeSpaceWidth(PRUint32 aCh) } } +/*static*/ size_t +gfxFont::WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg) +{ + return aMallocSizeOf(aHashEntry->mShapedWord.get()); +} + +void +gfxFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + for (PRUint32 i = 0; i < mGlyphExtentsArray.Length(); ++i) { + aSizes->mFontInstances += + mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); + } + aSizes->mShapedWords += + mWordCache.SizeOfExcludingThis(WordCacheEntrySizeOfExcludingThis, + aMallocSizeOf); +} + +void +gfxFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} + gfxGlyphExtents::~gfxGlyphExtents() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - gGlyphExtentsWidthsTotalSize += mContainedGlyphWidths.ComputeSize(); + gGlyphExtentsWidthsTotalSize += + mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); gGlyphExtentsCount++; #endif MOZ_COUNT_DTOR(gfxGlyphExtents); @@ -2636,21 +2748,19 @@ gfxGlyphExtents::GlyphWidths::~GlyphWidths() } } -#ifdef DEBUG PRUint32 -gfxGlyphExtents::GlyphWidths::ComputeSize() +gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const { PRUint32 i; - PRUint32 size = mBlocks.Capacity()*sizeof(PtrBits); + PRUint32 size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); for (i = 0; i < mBlocks.Length(); ++i) { PtrBits bits = mBlocks[i]; if (bits && !(bits & 0x1)) { - size += BLOCK_SIZE*sizeof(PRUint16); + size += aMallocSizeOf(reinterpret_cast(bits)); } } return size; } -#endif void gfxGlyphExtents::GlyphWidths::Set(PRUint32 aGlyphID, PRUint16 aWidth) @@ -2702,6 +2812,19 @@ gfxGlyphExtents::SetTightGlyphExtents(PRUint32 aGlyphID, const gfxRect& aExtents entry->height = aExtentsAppUnits.Height(); } +size_t +gfxGlyphExtents::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + + mTightGlyphExtents.SizeOfExcludingThis(nsnull, aMallocSizeOf); +} + +size_t +gfxGlyphExtents::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const +{ + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); +} + gfxFontGroup::gfxFontGroup(const nsAString& aFamilies, const gfxFontStyle *aStyle, gfxUserFontSet *aUserFontSet) : mFamilies(aFamilies), mStyle(*aStyle), mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) { diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index 65fc389f1d76..dd44ddd91d97 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -59,6 +59,7 @@ #include "nsISupportsImpl.h" #include "gfxPattern.h" #include "mozilla/HashFunctions.h" +#include "nsIMemoryReporter.h" typedef struct _cairo_scaled_font cairo_scaled_font_t; @@ -742,6 +743,15 @@ struct gfxTextRange { * completely, with all its words, and avoid the cost of aging the words * individually. That only happens with longer-lived fonts. */ +struct FontCacheSizes { + FontCacheSizes() + : mFontInstances(0), mShapedWords(0) + { } + + size_t mFontInstances; // memory used by instances of gfxFont subclasses + size_t mShapedWords; // memory used by the per-font shapedWord caches +}; + class THEBES_API gfxFontCache MOZ_FINAL : public nsExpirationTracker { public: enum { @@ -795,7 +805,20 @@ public: mFonts.EnumerateEntries(ClearCachedWordsForFont, nsnull); } + void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: + class MemoryReporter + : public nsIMemoryMultiReporter + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYMULTIREPORTER + }; + void DestroyFont(gfxFont *aFont); static gfxFontCache *gGlobalCache; @@ -828,6 +851,10 @@ protected: gfxFont* mFont; }; + static size_t SizeOfFontEntryExcludingThis(HashEntry* aHashEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg); + nsTHashtable mFonts; static PLDHashOperator ClearCachedWordsForFont(HashEntry* aHashEntry, void*); @@ -999,6 +1026,9 @@ public: PRUint32 GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; } + size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const; + private: class HashEntry : public nsUint32HashKey { public: @@ -1035,9 +1065,7 @@ private: return widths[indexInBlock]; } -#ifdef DEBUG - PRUint32 ComputeSize(); -#endif + PRUint32 SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; ~GlyphWidths(); @@ -1056,7 +1084,7 @@ private: nsTArray mBlocks; }; - + GlyphWidths mContainedGlyphWidths; nsTHashtable mTightGlyphExtents; PRUint32 mAppUnitsPerDevUnit; @@ -1493,6 +1521,11 @@ public: } } + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: // Call the appropriate shaper to generate glyphs for aText and store // them into aShapedWord. @@ -1577,6 +1610,11 @@ protected: nsAutoPtr mShapedWord; }; + static size_t + WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, + nsMallocSizeOfFun aMallocSizeOf, + void* aUserArg); + nsTHashtable mWordCache; static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData); diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp index b9b15b9b048b..73c690209aed 100644 --- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -560,3 +560,20 @@ gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID) return -1; } + +void +gfxGDIFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); + aSizes->mFontInstances += aMallocSizeOf(mMetrics) + + mGlyphWidths.SizeOfExcludingThis(nsnull, aMallocSizeOf); +} + +void +gfxGDIFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h index 99dba1355928..bdaf34b452ad 100644 --- a/gfx/thebes/gfxGDIFont.h +++ b/gfx/thebes/gfxGDIFont.h @@ -88,6 +88,11 @@ public: // get hinted glyph width in pixels as 16.16 fixed-point value virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: virtual void CreatePlatformShaper(); diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp index 4407c04f9002..b7744825a7bf 100644 --- a/gfx/thebes/gfxMacFont.cpp +++ b/gfx/thebes/gfxMacFont.cpp @@ -509,3 +509,19 @@ gfxMacFont::GetScaledFont() return mAzureFont; } +void +gfxMacFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); + // mCGFont is shared with the font entry, so not counted here; + // and we don't have APIs to measure the cairo mFontFace object +} + +void +gfxMacFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const +{ + aSizes->mFontInstances += aMallocSizeOf(this); + SizeOfExcludingThis(aMallocSizeOf, aSizes); +} diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h index 6806cc582c92..2f0ce551e178 100644 --- a/gfx/thebes/gfxMacFont.h +++ b/gfx/thebes/gfxMacFont.h @@ -81,6 +81,11 @@ public: mozilla::RefPtr GetScaledFont(); + virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, + FontCacheSizes* aSizes) const; + protected: virtual void CreatePlatformShaper(); From 6a5eac8ed1d272e7c3bb76a4d5dcdffcc774625c Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Fri, 23 Mar 2012 21:21:31 +0900 Subject: [PATCH 010/109] Bug 706784 - drop action accessible ref on linkable accessible when linkable gets unbound from tree, r=tbsaunde --- accessible/src/base/nsAccessible.h | 2 +- accessible/src/base/nsBaseWidgetAccessible.cpp | 10 ++++++++++ accessible/src/base/nsBaseWidgetAccessible.h | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/accessible/src/base/nsAccessible.h b/accessible/src/base/nsAccessible.h index d6052c961f3d..e42030167562 100644 --- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -650,7 +650,7 @@ protected: * Set accessible parent and index in parent. */ virtual void BindToParent(nsAccessible* aParent, PRUint32 aIndexInParent); - void UnbindFromParent(); + virtual void UnbindFromParent(); /** * Return sibling accessible at the given offset. diff --git a/accessible/src/base/nsBaseWidgetAccessible.cpp b/accessible/src/base/nsBaseWidgetAccessible.cpp index 18e3a685196a..837ab7967e8f 100644 --- a/accessible/src/base/nsBaseWidgetAccessible.cpp +++ b/accessible/src/base/nsBaseWidgetAccessible.cpp @@ -248,6 +248,16 @@ nsLinkableAccessible::BindToParent(nsAccessible* aParent, } } +void +nsLinkableAccessible::UnbindFromParent() +{ + mActionAcc = nsnull; + mIsLink = false; + mIsOnclick = false; + + nsAccessibleWrap::UnbindFromParent(); +} + //////////////////////////////////////////////////////////////////////////////// // nsEnumRoleAccessible //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/base/nsBaseWidgetAccessible.h b/accessible/src/base/nsBaseWidgetAccessible.h index 9e061ac2469f..edb3bb23c975 100644 --- a/accessible/src/base/nsBaseWidgetAccessible.h +++ b/accessible/src/base/nsBaseWidgetAccessible.h @@ -108,6 +108,7 @@ public: protected: // nsAccessible virtual void BindToParent(nsAccessible* aParent, PRUint32 aIndexInParent); + virtual void UnbindFromParent(); /** * Parent accessible that provides an action for this linkable accessible. From 064885cfdf6d3330d63465d9224353a670c52fee Mon Sep 17 00:00:00 2001 From: Josh Aas Date: Fri, 23 Mar 2012 08:33:15 -0400 Subject: [PATCH 011/109] Bug 726734: Reload plugin instances when the src/data URL changes. r=jst --- content/base/src/nsObjectLoadingContent.cpp | 5 ++- dom/plugins/test/mochitest/Makefile.in | 1 + .../test/mochitest/test_src_url_change.html | 40 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 dom/plugins/test/mochitest/test_src_url_change.html diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 56c1079d1695..3d5a88a51cf8 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -1283,13 +1283,14 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, // Only do a URI equality check for things that aren't stopped plugins. // This is because we still need to load again if the plugin has been stopped. if (mType == eType_Document || mType == eType_Image || mInstanceOwner) { - if (mURI && aURI && !aForceLoad) { + if (mURI && aURI) { bool equal; nsresult rv = mURI->Equals(aURI, &equal); - if (NS_SUCCEEDED(rv) && equal) { + if (NS_SUCCEEDED(rv) && equal && !aForceLoad) { // URI didn't change, do nothing return NS_OK; } + StopPluginInstance(); } } diff --git a/dom/plugins/test/mochitest/Makefile.in b/dom/plugins/test/mochitest/Makefile.in index c19a56748c71..897150fcdec3 100644 --- a/dom/plugins/test/mochitest/Makefile.in +++ b/dom/plugins/test/mochitest/Makefile.in @@ -111,6 +111,7 @@ _MOCHITEST_FILES = \ test_instance_unparent3.html \ test_pluginstream_referer.html \ plugin-stream-referer.sjs \ + test_src_url_change.html \ $(NULL) # test_plugin_scroll_painting.html \ bug 596491 diff --git a/dom/plugins/test/mochitest/test_src_url_change.html b/dom/plugins/test/mochitest/test_src_url_change.html new file mode 100644 index 000000000000..386a41f67c85 --- /dev/null +++ b/dom/plugins/test/mochitest/test_src_url_change.html @@ -0,0 +1,40 @@ + + + + Test changing src attribute + + + + + +

            + + + + + + + From f1d63ee3708426def41143f130eb787737bbe052 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Fri, 23 Mar 2012 09:15:36 +0200 Subject: [PATCH 012/109] Bug 482911 - Reimplement Netscape bookmark import using the HTML5 parser. r=mak, sr=gavin. --HG-- rename : toolkit/components/places/nsPlacesImportExportService.cpp => toolkit/components/places/nsPlacesExportService.cpp rename : toolkit/components/places/nsPlacesImportExportService.h => toolkit/components/places/nsPlacesExportService.h --- .../src/nsBrowserProfileMigratorUtils.cpp | 17 - .../src/nsBrowserProfileMigratorUtils.h | 5 - .../migration/src/nsIEProfileMigrator.cpp | 5 - .../migration/src/nsSafariProfileMigrator.cpp | 5 - browser/components/nsBrowserGlue.js | 62 +- browser/components/places/content/places.js | 7 +- .../places/tests/unit/test_384370.js | 14 +- ...41-import-export-corrupt-bookmarks-html.js | 15 +- .../unit/test_bookmarksRestoreNotification.js | 50 +- .../places/tests/unit/test_bookmarks_html.js | 160 +- .../tests/unit/test_browserGlue_migrate.js | 7 +- .../tests/unit/test_browserGlue_prefs.js | 143 +- .../unit/test_browserGlue_smartBookmarks.js | 13 +- .../components/places/BookmarkHTMLUtils.jsm | 775 ++++++ toolkit/components/places/Helpers.cpp | 59 - toolkit/components/places/Helpers.h | 9 - toolkit/components/places/Makefile.in | 3 +- toolkit/components/places/PlacesUtils.jsm | 2 +- .../places/nsIPlacesImportExportService.idl | 29 +- .../places/nsPlacesExportService.cpp | 1163 ++++++++ ...xportService.h => nsPlacesExportService.h} | 25 +- .../places/nsPlacesImportExportService.cpp | 2459 ----------------- toolkit/components/places/nsPlacesModule.cpp | 8 +- 23 files changed, 2237 insertions(+), 2798 deletions(-) create mode 100644 toolkit/components/places/BookmarkHTMLUtils.jsm create mode 100644 toolkit/components/places/nsPlacesExportService.cpp rename toolkit/components/places/{nsPlacesImportExportService.h => nsPlacesExportService.h} (68%) delete mode 100644 toolkit/components/places/nsPlacesImportExportService.cpp diff --git a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp index 869970c347f6..eb80fe269785 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.cpp @@ -163,20 +163,3 @@ 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 26c9fad4554c..a1e00a797a57 100644 --- a/browser/components/migration/src/nsBrowserProfileMigratorUtils.h +++ b/browser/components/migration/src/nsBrowserProfileMigratorUtils.h @@ -99,10 +99,5 @@ 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 e153e65f82c9..8ce5d0ef7a83 100644 --- a/browser/components/migration/src/nsIEProfileMigrator.cpp +++ b/browser/components/migration/src/nsIEProfileMigrator.cpp @@ -1398,11 +1398,6 @@ 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 dcfe9e0c8c2c..05feb249df37 100644 --- a/browser/components/migration/src/nsSafariProfileMigrator.cpp +++ b/browser/components/migration/src/nsSafariProfileMigrator.cpp @@ -974,11 +974,6 @@ 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 8e3e72c63644..9f99c6556aff 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -62,6 +62,9 @@ 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"); @@ -228,10 +231,6 @@ 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; @@ -257,13 +256,6 @@ 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")) @@ -288,6 +280,9 @@ BrowserGlue.prototype = { keywordURI); } break; + case "initial-migration": + this._initialMigrationPerformed = true; + break; } }, @@ -990,18 +985,9 @@ BrowserGlue.prototype = { // If the database is corrupt or has been newly created we should // import bookmarks. var dbStatus = PlacesUtils.history.databaseStatus; - 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; - } + var importBookmarks = !this._initialMigrationPerformed && + (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE || + dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT); // Check if user or an extension has required to import bookmarks.html var importBookmarksHTML = false; @@ -1058,6 +1044,9 @@ 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 { @@ -1091,25 +1080,28 @@ 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 { - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.importHTMLFromURI(bookmarksURI, true /* overwrite existing */); + 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)); } 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 ce30f248aa05..40e7cfa1d262 100644 --- a/browser/components/places/content/places.js +++ b/browser/components/places/content/places.js @@ -400,10 +400,9 @@ var PlacesOrganizer = { Ci.nsIFilePicker.modeOpen); fp.appendFilters(Ci.nsIFilePicker.filterHTML); if (fp.show() != Ci.nsIFilePicker.returnCancel) { - if (fp.file) { - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. - getService(Ci.nsIPlacesImportExportService); - importer.importHTMLFromFile(fp.file, false); + if (fp.fileURL) { + Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); + BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false); } } }, diff --git a/browser/components/places/tests/unit/test_384370.js b/browser/components/places/tests/unit/test_384370.js index adb7c4ae34cd..a7a68708c51d 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 */ - // get places import/export service - var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].getService(Ci.nsIPlacesImportExportService); + // import the importer + Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); // avoid creating the places smart folder during tests Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch). @@ -83,8 +83,14 @@ function run_test() { // 2. run the test-suite // Note: we do not empty the db before this import to catch bugs like 380999 try { - importer.importHTMLFromFile(bookmarksFileOld, true); + BookmarkHTMLUtils.importFromFile(bookmarksFileOld, true, after_import); } 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(); @@ -95,6 +101,8 @@ function run_test() { // 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 f53451a2cdd4..6e5c82d7100f 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,6 +55,7 @@ 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"; @@ -72,8 +73,14 @@ function run_test() { // import bookmarks from corrupt file var corruptBookmarksFile = do_get_file("bookmarks.corrupt.html"); try { - ies.importHTMLFromFile(corruptBookmarksFile, true); + BookmarkHTMLUtils.importFromFile(corruptBookmarksFile, true, after_import); } 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 @@ -105,14 +112,16 @@ function run_test() { // Import bookmarks try { - ies.importHTMLFromFile(bookmarksFile, true); + BookmarkHTMLUtils.importFromFile(bookmarksFile, true, before_database_check); } 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 4b2a55fb4c2a..c463272d0927 100644 --- a/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js +++ b/browser/components/places/tests/unit/test_bookmarksRestoreNotification.js @@ -36,6 +36,8 @@ * * ***** 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. @@ -134,10 +136,14 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.html"); addBookmarks(); - importer.exportHTMLToFile(this.file); + exporter.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - importer.importHTMLFromFile(this.file, false); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -154,7 +160,11 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - importer.importHTMLFromFile(this.file, false); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -172,8 +182,12 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - importer.importHTMLFromFile(this.file, false); - do_throw(" Restore should have failed"); + BookmarkHTMLUtils.importFromFile(this.file, false, function (success) { + print("callback"); + if (success) { + do_throw(" Restore should have failed"); + } + }); } catch (e) {} } @@ -188,10 +202,14 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); addBookmarks(); - importer.exportHTMLToFile(this.file); + exporter.exportHTMLToFile(this.file); remove_all_bookmarks(); try { - importer.importHTMLFromFile(this.file, true); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -208,7 +226,11 @@ var tests = [ run: function () { this.file = createFile("bookmarks-test_restoreNotification.init.html"); try { - importer.importHTMLFromFile(this.file, true); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (!success) { + do_throw(" Restore should not have failed"); + } + }); } catch (e) { do_throw(" Restore should not have failed"); @@ -226,13 +248,15 @@ var tests = [ this.file = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this.file.append("this file doesn't exist because nobody created it"); try { - importer.importHTMLFromFile(this.file, true); - do_throw(" Restore should have failed"); + BookmarkHTMLUtils.importFromFile(this.file, true, function (success) { + if (success) { + do_throw(" Restore should have failed"); + } + }); } catch (e) {} } } - ]; // nsIObserver that observes bookmarks-restore-begin. @@ -281,7 +305,7 @@ var successAndFailedObserver = { do_check_eq(test.folderId, null); remove_all_bookmarks(); - doNextTest(); + do_execute_soon(doNextTest); } }; @@ -294,7 +318,7 @@ var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. var obssvc = Cc["@mozilla.org/observer-service;1"]. getService(Ci.nsIObserverService); -var importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. +var exporter = 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 4423de000514..aa4772c7b73b 100644 --- a/browser/components/places/tests/unit/test_bookmarks_html.js +++ b/browser/components/places/tests/unit/test_bookmarks_html.js @@ -99,9 +99,11 @@ let gBookmarksFileOld; // Places bookmarks.html file pointer. let gBookmarksFileNew; -let importer = Cc["@mozilla.org/browser/places/import-export-service;1"]. +let exporter = 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(); @@ -131,22 +133,26 @@ 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 { - importer.importHTMLFromFile(gBookmarksFileOld, true); - } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } + BookmarkHTMLUtils.importFromFile(gBookmarksFileOld, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - waitForAsyncUpdates(function () { - testImportedBookmarks(); + // Prepare for next tests. + try { + exporter.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } - // 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(); + waitForAsyncUpdates(function () { + remove_all_bookmarks(); + run_next_test(); + }); + }); + } else { + do_throw("couldn't import legacy bookmarks file."); + } }); - }); + } catch(ex) { do_throw("couldn't import legacy bookmarks file: " + ex); } }); add_test(function test_import_new() @@ -156,17 +162,21 @@ add_test(function test_import_new() // 2. run the test-suite try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - waitForAsyncUpdates(function () { - testImportedBookmarks(); - - waitForAsyncUpdates(function () { - remove_all_bookmarks(); - run_next_test(); + 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); } }); add_test(function test_emptytitle_export() @@ -180,42 +190,50 @@ add_test(function test_emptytitle_export() // 5. run the test-suite try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + 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 }); - 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); } - try { - importer.exportHTMLToFile(gBookmarksFileNew); - } catch(ex) { do_throw("couldn't export to file: " + ex); } + remove_all_bookmarks(); - remove_all_bookmarks(); + try { + BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true, function(success) { + if (success) { + waitForAsyncUpdates(function () { + testImportedBookmarks(); - try { - importer.importHTMLFromFile(gBookmarksFileNew, true); - } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + // Cleanup. + test_bookmarks.unfiled.pop(); + PlacesUtils.bookmarks.removeItem(id); - waitForAsyncUpdates(function () { - testImportedBookmarks(); + try { + exporter.exportHTMLToFile(gBookmarksFileNew); + } catch(ex) { do_throw("couldn't export to file: " + ex); } - // 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(); + 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."); + } }); - }); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } }); add_test(function test_import_ontop() @@ -229,23 +247,33 @@ add_test(function test_import_ontop() // 4. run the test-suite try { - 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); } + 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 () { - 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); } - 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); } }); 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 7f7bf012226e..85cefef882bf 100644 --- a/browser/components/places/tests/unit/test_browserGlue_migrate.js +++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js @@ -29,14 +29,15 @@ function run_test() { do_check_eq(PlacesUtils.history.databaseStatus, PlacesUtils.history.DATABASE_STATUS_CREATE); - // A migrator would run before nsBrowserGlue, so we mimic that behavior - // adding a bookmark. + //A migrator would run before nsBrowserGlue Places initialization, so mimic + //that behavior adding a bookmark and notifying the migration. 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.nsIBrowserGlue); + getService(Ci.nsIObserver); + bg.observe(null, "initial-migration", null) 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 74c2eb3fbb3a..0194510c96eb 100644 --- a/browser/components/places/tests/unit/test_browserGlue_prefs.js +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -17,6 +17,16 @@ 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. @@ -66,19 +76,20 @@ 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() @@ -98,19 +109,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"); + // 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() @@ -131,20 +143,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, 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() @@ -165,21 +178,22 @@ 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() @@ -198,19 +212,21 @@ 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() @@ -230,20 +246,21 @@ 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 f6532f0cf175..d7028b07b025 100644 --- a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js +++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js @@ -364,6 +364,15 @@ function run_test() { } catch(ex) {} - // Kick-off tests. - next_test(); + 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); } diff --git a/toolkit/components/places/BookmarkHTMLUtils.jsm b/toolkit/components/places/BookmarkHTMLUtils.jsm new file mode 100644 index 000000000000..e2cd4d0116df --- /dev/null +++ b/toolkit/components/places/BookmarkHTMLUtils.jsm @@ -0,0 +1,775 @@ +/* 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 1a5806ee7913..76bb3ee340d3 100644 --- a/toolkit/components/places/Helpers.cpp +++ b/toolkit/components/places/Helpers.cpp @@ -445,64 +445,5 @@ 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 600fa67df6b7..e77a214edd1b 100644 --- a/toolkit/components/places/Helpers.h +++ b/toolkit/components/places/Helpers.h @@ -297,15 +297,6 @@ 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 59120d76e381..e1d43e8f5b7c 100644 --- a/toolkit/components/places/Makefile.in +++ b/toolkit/components/places/Makefile.in @@ -92,7 +92,7 @@ CPPSRCS = \ SQLFunctions.cpp \ Helpers.cpp \ History.cpp \ - nsPlacesImportExportService.cpp \ + nsPlacesExportService.cpp \ AsyncFaviconHelpers.cpp \ PlaceInfo.cpp \ VisitInfo.cpp \ @@ -123,6 +123,7 @@ 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 4499df2800a2..20e914f2dcb6 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/nsPlacesImportExportService.cpp +// same names in browser/components/places/src/nsPlacesExportService.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 b6e60f4ba4bd..af9835dae6c1 100644 --- a/toolkit/components/places/nsIPlacesImportExportService.idl +++ b/toolkit/components/places/nsIPlacesImportExportService.idl @@ -41,34 +41,15 @@ interface nsILocalFile; interface nsIURI; /** - * The PlacesImportExport interface provides methods for importing - * and exporting Places data. + * 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. */ -[scriptable, uuid(08f4626e-af3f-4e84-bd01-cf09175c4f94)] +[scriptable, uuid(47a4a09e-c708-4e68-b2f2-664d982ce026)] 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 new file mode 100644 index 000000000000..ec70ec2769a2 --- /dev/null +++ b/toolkit/components/places/nsPlacesExportService.cpp @@ -0,0 +1,1163 @@ +/* -*- 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.h b/toolkit/components/places/nsPlacesExportService.h similarity index 68% rename from toolkit/components/places/nsPlacesImportExportService.h rename to toolkit/components/places/nsPlacesExportService.h index ba647ccdbf82..0cac53620e98 100644 --- a/toolkit/components/places/nsPlacesImportExportService.h +++ b/toolkit/components/places/nsPlacesExportService.h @@ -1,5 +1,5 @@ -#ifndef nsPlacesImportExportService_h__ -#define nsPlacesImportExportService_h__ +#ifndef nsPlacesExportService_h_ +#define nsPlacesExportService_h_ #include "nsIPlacesImportExportService.h" @@ -13,19 +13,17 @@ #include "nsINavBookmarksService.h" #include "nsIChannel.h" -class nsPlacesImportExportService : public nsIPlacesImportExportService, - public nsINavHistoryBatchCallback +class nsPlacesExportService : public nsIPlacesImportExportService { public: NS_DECL_ISUPPORTS NS_DECL_NSIPLACESIMPORTEXPORTSERVICE - NS_DECL_NSINAVHISTORYBATCHCALLBACK - nsPlacesImportExportService(); + nsPlacesExportService(); /** * Obtains the service's object. */ - static nsPlacesImportExportService* GetSingleton(); + static nsPlacesExportService* GetSingleton(); /** * Initializes the service's object. This should only be called once. @@ -33,8 +31,8 @@ class nsPlacesImportExportService : public nsIPlacesImportExportService, nsresult Init(); private: - static nsPlacesImportExportService* gImportExportService; - virtual ~nsPlacesImportExportService(); + static nsPlacesExportService* gExportService; + virtual ~nsPlacesExportService(); protected: nsCOMPtr mFaviconService; @@ -43,13 +41,6 @@ class nsPlacesImportExportService : 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); @@ -69,4 +60,4 @@ class nsPlacesImportExportService : public nsIPlacesImportExportService, } }; -#endif // nsPlacesImportExportService_h__ +#endif // nsPlacesExportService_h_ diff --git a/toolkit/components/places/nsPlacesImportExportService.cpp b/toolkit/components/places/nsPlacesImportExportService.cpp deleted file mode 100644 index 1c4897c503e9..000000000000 --- a/toolkit/components/places/nsPlacesImportExportService.cpp +++ /dev/null @@ -1,2459 +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 "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/nsPlacesModule.cpp b/toolkit/components/places/nsPlacesModule.cpp index 809e12b23431..eb0724063dae 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 "nsPlacesImportExportService.h" +#include "nsPlacesExportService.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(nsPlacesImportExportService, - nsPlacesImportExportService::GetSingleton) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPlacesExportService, + nsPlacesExportService::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, nsPlacesImportExportServiceConstructor }, + { &kNS_PLACESIMPORTEXPORTSERVICE_CID, false, NULL, nsPlacesExportServiceConstructor }, { NULL } }; From e29edd2e13c13e988402530927bfbc7d7ecd6c32 Mon Sep 17 00:00:00 2001 From: Henri Sivonen Date: Fri, 23 Mar 2012 16:57:13 +0200 Subject: [PATCH 013/109] Bug 482911 addendum - Tell nsBrowserGlue not to import initial bookmarks in a couple of tests to avoid fatal assertions on Mac. r=mak. --- browser/components/places/tests/unit/test_421483.js | 2 ++ .../places/tests/unit/test_clearHistory_shutdown.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/browser/components/places/tests/unit/test_421483.js b/browser/components/places/tests/unit/test_421483.js index c32a9257f57d..738583c8de00 100644 --- a/browser/components/places/tests/unit/test_421483.js +++ b/browser/components/places/tests/unit/test_421483.js @@ -56,6 +56,8 @@ try { try { var gluesvc = Cc["@mozilla.org/browser/browserglue;1"]. getService(Ci.nsIBrowserGlue); + // Avoid default bookmarks import. + gluesvc.QueryInterface(Ci.nsIObserver).observe(null, "initial-migration", null); } catch(ex) { do_throw("Could not get BrowserGlue service\n"); } diff --git a/browser/components/places/tests/unit/test_clearHistory_shutdown.js b/browser/components/places/tests/unit/test_clearHistory_shutdown.js index 968d4c02a0b6..ac0e8adb623a 100644 --- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js +++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js @@ -110,7 +110,9 @@ function run_test() { do_test_pending(); print("Initialize browserglue before Places"); - Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIBrowserGlue); + // Avoid default bookmarks import. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver) + .observe(null, "initial-migration", null); Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true); Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true); From 45a106cae13654bb7c171440188c97d12ed638c2 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 23 Mar 2012 11:19:57 -0400 Subject: [PATCH 014/109] Bug 738649 - Incorrect return value check in nsHTMLDocument::EditingStateChanged; r=khuey --- content/html/document/src/nsHTMLDocument.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/html/document/src/nsHTMLDocument.cpp b/content/html/document/src/nsHTMLDocument.cpp index 539f0c970fed..7a52b1c2cf7f 100644 --- a/content/html/document/src/nsHTMLDocument.cpp +++ b/content/html/document/src/nsHTMLDocument.cpp @@ -2681,8 +2681,8 @@ nsHTMLDocument::EditingStateChanged() rv = LoadChromeSheetSync(uri, true, getter_AddRefs(sheet)); NS_ENSURE_TRUE(sheet, rv); - rv = agentSheets.AppendObject(sheet); - NS_ENSURE_SUCCESS(rv, rv); + bool result = agentSheets.AppendObject(sheet); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); // Should we update the editable state of all the nodes in the document? We // need to do this when the designMode value changes, as that overrides @@ -2695,8 +2695,8 @@ nsHTMLDocument::EditingStateChanged() rv = LoadChromeSheetSync(uri, true, getter_AddRefs(sheet)); NS_ENSURE_TRUE(sheet, rv); - rv = agentSheets.AppendObject(sheet); - NS_ENSURE_SUCCESS(rv, rv); + result = agentSheets.AppendObject(sheet); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); // Disable scripting and plugins. rv = editSession->DisableJSAndPlugins(window); From 8db1c17bacaf1e336d5267d6a301463e6ccef6df Mon Sep 17 00:00:00 2001 From: Josh Aas Date: Fri, 23 Mar 2012 11:25:16 -0400 Subject: [PATCH 015/109] Bug 738542: Increase min required JRE to 1.6. r=smichaud --- modules/libpref/src/init/all.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 7d4478ca4a55..ffc3392a1f18 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -2063,7 +2063,7 @@ pref("print.print_extra_margin", 90); // twips (90 twips is an eigth of an inch) pref("print.extend_native_print_dialog", true); // Locate Java by scanning the Sun JRE installation directory with a minimum version -pref("plugin.scan.SunJRE", "1.3"); +pref("plugin.scan.SunJRE", "1.6"); // Locate plugins by scanning the Adobe Acrobat installation directory with a minimum version pref("plugin.scan.Acrobat", "5.0"); From 2df3a82068b183b1bf60bee35d5ab1551637664d Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 23 Mar 2012 11:35:40 -0400 Subject: [PATCH 016/109] Bug 737889 - Make sure that clearing the readonly attribute does not disable spell checking; r=roc --- .../html/content/src/nsHTMLInputElement.cpp | 4 ++++ .../html/content/src/nsHTMLTextAreaElement.cpp | 1 + content/html/content/src/nsTextEditorState.h | 7 +++++++ layout/reftests/editor/reftest.list | 1 + ...pellcheck-textarea-focused-notreadonly.html | 18 ++++++++++++++++++ 5 files changed, 31 insertions(+) create mode 100644 layout/reftests/editor/spellcheck-textarea-focused-notreadonly.html diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index e8252a31f764..cecae045a911 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -855,6 +855,10 @@ nsHTMLInputElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, } UpdateEditableState(aNotify); + nsTextEditorState *state = GetEditorState(); + if (state) { + state->UpdateEditableState(aNotify); + } UpdateState(aNotify); } diff --git a/content/html/content/src/nsHTMLTextAreaElement.cpp b/content/html/content/src/nsHTMLTextAreaElement.cpp index 27aa55026000..39157e400178 100644 --- a/content/html/content/src/nsHTMLTextAreaElement.cpp +++ b/content/html/content/src/nsHTMLTextAreaElement.cpp @@ -1293,6 +1293,7 @@ nsHTMLTextAreaElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, if (aName == nsGkAtoms::readonly) { UpdateEditableState(aNotify); + mState->UpdateEditableState(aNotify); } UpdateState(aNotify); } diff --git a/content/html/content/src/nsTextEditorState.h b/content/html/content/src/nsTextEditorState.h index 5ddccb8a6be0..c16fff20a929 100644 --- a/content/html/content/src/nsTextEditorState.h +++ b/content/html/content/src/nsTextEditorState.h @@ -44,6 +44,7 @@ #include "nsITextControlElement.h" #include "nsITextControlFrame.h" #include "nsCycleCollectionParticipant.h" +#include "nsIContent.h" class nsTextInputListener; class nsTextControlFrame; @@ -238,6 +239,12 @@ public: void WillInitEagerly() { mSelectionRestoreEagerInit = true; } bool HasNeverInitializedBefore() const { return !mEverInited; } + void UpdateEditableState(bool aNotify) { + if (mRootNode) { + mRootNode->UpdateEditableState(aNotify); + } + } + private: friend class RestoreSelectionState; diff --git a/layout/reftests/editor/reftest.list b/layout/reftests/editor/reftest.list index 6367ae2ccb21..028bbb4d7a87 100644 --- a/layout/reftests/editor/reftest.list +++ b/layout/reftests/editor/reftest.list @@ -44,6 +44,7 @@ fails-if(Android) != spellcheck-input-property-dynamic-override-inherit.html spe fails-if(Android) != spellcheck-textarea-attr.html spellcheck-textarea-ref.html needs-focus == spellcheck-textarea-focused.html spellcheck-textarea-ref.html needs-focus == spellcheck-textarea-focused-reframe.html spellcheck-textarea-ref.html +needs-focus == spellcheck-textarea-focused-notreadonly.html spellcheck-textarea-ref.html fails-if(Android) != spellcheck-textarea-nofocus.html spellcheck-textarea-ref.html fails-if(Android) != spellcheck-textarea-disabled.html spellcheck-textarea-ref.html fails-if(Android) != spellcheck-textarea-attr-inherit.html spellcheck-textarea-ref.html diff --git a/layout/reftests/editor/spellcheck-textarea-focused-notreadonly.html b/layout/reftests/editor/spellcheck-textarea-focused-notreadonly.html new file mode 100644 index 000000000000..967cba3e12e6 --- /dev/null +++ b/layout/reftests/editor/spellcheck-textarea-focused-notreadonly.html @@ -0,0 +1,18 @@ + + + + + + + + + From d408136238864dda5a3d0664826c314dd500c873 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 23 Mar 2012 11:35:40 -0400 Subject: [PATCH 017/109] Bug 737889 - Part 2: Stop the propagarion of drag/drop events over non-editable content; r=roc --- editor/libeditor/base/nsEditorEventListener.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/editor/libeditor/base/nsEditorEventListener.cpp b/editor/libeditor/base/nsEditorEventListener.cpp index 48164bf52ce4..ff54dc7b77e2 100644 --- a/editor/libeditor/base/nsEditorEventListener.cpp +++ b/editor/libeditor/base/nsEditorEventListener.cpp @@ -676,11 +676,7 @@ nsEditorEventListener::DragOver(nsIDOMDragEvent* aDragEvent) nsCOMPtr dropParent = do_QueryInterface(parent); NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE); - if (!dropParent->IsEditable()) { - return NS_OK; - } - - if (CanDrop(aDragEvent)) { + if (dropParent->IsEditable() && CanDrop(aDragEvent)) { aDragEvent->PreventDefault(); // consumed if (mCaret) { @@ -754,11 +750,7 @@ nsEditorEventListener::Drop(nsIDOMDragEvent* aMouseEvent) nsCOMPtr dropParent = do_QueryInterface(parent); NS_ENSURE_TRUE(dropParent, NS_ERROR_FAILURE); - if (!dropParent->IsEditable()) { - return NS_OK; - } - - if (!CanDrop(aMouseEvent)) { + if (!dropParent->IsEditable() || !CanDrop(aMouseEvent)) { // was it because we're read-only? if (mEditor->IsReadonly() || mEditor->IsDisabled()) { From caccd1b976e09612b80c6b495b811a1d22ae5bcd Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Fri, 23 Mar 2012 11:43:10 -0400 Subject: [PATCH 018/109] Bug 738554 - Fix a couple of race conditions where we could start listening for events after they occur. r=mfinkle --- mobile/android/base/tests/testAboutPage.java.in | 8 ++++++-- mobile/android/base/tests/testBookmark.java.in | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mobile/android/base/tests/testAboutPage.java.in b/mobile/android/base/tests/testAboutPage.java.in index 61f31daf0815..845a4f8536aa 100644 --- a/mobile/android/base/tests/testAboutPage.java.in +++ b/mobile/android/base/tests/testAboutPage.java.in @@ -32,13 +32,17 @@ public class testAboutPage extends BaseTest { mSolo.waitForText("^Settings$"); mSolo.clickOnText("^Settings$"); + // Set up listeners to catch the page load we're about to do + Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added"); + Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded"); + // Tap on the "About Xxxx" setting mSolo.waitForText("About (Fennec|Nightly|Aurora|Firefox)"); mSolo.clickOnText("About (Fennec|Nightly|Aurora|Firefox)"); // Wait for the new tab and page to load - mActions.expectGeckoEvent("Tab:Added").blockForEvent(); - mActions.expectGeckoEvent("DOMContentLoaded").blockForEvent(); + tabEventExpecter.blockForEvent(); + contentEventExpecter.blockForEvent(); // Grab the title to make sure the about: page was loaded awesomebar = mDriver.findElement(getActivity(), "awesome_bar"); diff --git a/mobile/android/base/tests/testBookmark.java.in b/mobile/android/base/tests/testBookmark.java.in index 6cacaa8ce8b2..1c32d7cae7e8 100644 --- a/mobile/android/base/tests/testBookmark.java.in +++ b/mobile/android/base/tests/testBookmark.java.in @@ -33,10 +33,10 @@ public class testBookmark extends BaseTest { "bookmarks list has 2 children (the bookmark we added and the hidden header)"); // Click on the bookmark we created (the first item is the header view) + // and wait for the bookmarked page to load + Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded"); mSolo.clickInList(2); - - // Wait for the bookmarked page to load - mActions.expectGeckoEvent("DOMContentLoaded").blockForEvent(); + contentEventExpecter.blockForEvent(); // Clean up the bookmark we created cleanUpBookmark(); From ba8dd960e8c0c56013615c4ce4671f0c771c4283 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Fri, 23 Mar 2012 11:58:03 -0400 Subject: [PATCH 019/109] Bug 738556 - Optimize frame time measurement and checkerboard time measurement in robocop a little. r=jmaher --- .../mobile/robocop/FennecNativeDriver.java.in | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/build/mobile/robocop/FennecNativeDriver.java.in b/build/mobile/robocop/FennecNativeDriver.java.in index 8cef7b0e3a54..dea231f04d82 100644 --- a/build/mobile/robocop/FennecNativeDriver.java.in +++ b/build/mobile/robocop/FennecNativeDriver.java.in @@ -70,6 +70,8 @@ import org.json.*; import com.jayway.android.robotium.solo.Solo; public class FennecNativeDriver implements Driver { + private static final int FRAME_TIME_THRESHOLD = 17; // allow 17ms per frame (~60fps) + // Map of IDs to element names. private HashMap mLocators = null; private Activity mActivity; @@ -225,21 +227,15 @@ public class FennecNativeDriver implements Driver { public int stopFrameRecording() { Class [] parameters = new Class[1]; parameters[0] = null; - List frames; try { Object [] params = null; - frames = (List)_stopFrameRecording.invoke(null, params); - Object [] framearray = frames.toArray(); - Long last = new Long(0); - Long threshold = new Long(17); + List frames = (List)_stopFrameRecording.invoke(null, params); int numDelays = 0; - for (int i=0; i < framearray.length; i++) { - Long val = (Long)framearray[i]; - if ((val - last) > threshold) { + for (int i = 1; i < frames.size(); i++) { + if (frames.get(i) - frames.get(i-1) > FRAME_TIME_THRESHOLD) { numDelays++; } - last = val; } return numDelays; } catch (IllegalAccessException e) { @@ -265,18 +261,15 @@ public class FennecNativeDriver implements Driver { public float stopCheckerboardRecording() { Class [] parameters = new Class[1]; parameters[0] = null; - List checkerboard; try { Object [] params = null; - checkerboard = (List)_stopCheckerboardRecording.invoke(null, params); - Object [] amountarray = checkerboard.toArray(); - double completeness = 0; - for (Object obj : amountarray) { - float val = (Float)obj; - completeness += (1.0 - (double)val) / (double)amountarray.length; + List checkerboard = (List)_stopCheckerboardRecording.invoke(null, params); + float completeness = 0; + for (float val : checkerboard) { + completeness += (1.0f - val); } - return (float)completeness; + return completeness / (float)checkerboard.size(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { From d280dfdfaa9182bd0b17f212c00c90b9f87a9601 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Fri, 23 Mar 2012 11:58:06 -0400 Subject: [PATCH 020/109] Bug 738556 - Improve testCheck to use a variety of scroll amounts and more reliable dragging code. r=Cwiiis --- mobile/android/base/tests/testCheck.java.in | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mobile/android/base/tests/testCheck.java.in b/mobile/android/base/tests/testCheck.java.in index 60494c03ab91..cb559bcab586 100644 --- a/mobile/android/base/tests/testCheck.java.in +++ b/mobile/android/base/tests/testCheck.java.in @@ -14,7 +14,7 @@ public class testCheck extends PixelTest { public void testCheck() { setTestType("talos"); - String url = getAbsoluteUrl("/startup_test/fennecmark/wikipedia.html"); + String url = getAbsoluteUrl("/startup_test/fennecmark/timecube.html"); mActions.expectGeckoEvent("Gecko:Ready").blockForEvent(); @@ -23,26 +23,26 @@ public class testCheck extends PixelTest { mDriver.setupScrollHandling(); // Setup scrolling coordinates. - int midX = mDriver.getGeckoLeft() + mDriver.getGeckoWidth()/2; - int midY = mDriver.getGeckoTop() + mDriver.getGeckoHeight()/2; - int endY = mDriver.getGeckoTop() + mDriver.getGeckoHeight()/6; + MotionEventHelper meh = new MotionEventHelper(getInstrumentation(), mDriver.getGeckoLeft(), mDriver.getGeckoTop()); + int midX = mDriver.getGeckoWidth() / 2; + int height = mDriver.getGeckoHeight(); + int topY = height / 8; mDriver.startCheckerboardRecording(); - int i = 0; - // Scroll repeatedly downwards, then upwards. This test should take - // approximately 15 seconds. - do { + // Scroll repeatedly downwards, then upwards. On each iteration of i, + // increase the scroll distance to test different scroll amounts. + for (int i = 2; i < 7; i++) { + int botY = (height * i / 8); for (int j = 0; j < 3; j++) { - mActions.drag(midX, midX, midY, endY); - pause(500); + meh.dragSync(midX, botY, midX, topY, 200); + pause(1000); } for (int j = 0; j < 3; j++) { - mActions.drag(midX, midX, endY, midY); - pause(500); + meh.dragSync(midX, topY, midX, botY, 200); + pause(1000); } - i++; - } while (i < 5); + } float completeness = mDriver.stopCheckerboardRecording(); mAsserter.dumpLog("__start_report" + completeness + "__end_report"); From 5bdbd66803d6eedf152f5d1e1ec24790817f63a7 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Mon, 19 Mar 2012 13:09:48 -0400 Subject: [PATCH 021/109] Bug 736529 - Calling waitForFocus() and then finishing before you get focus should cause the test to fail; r=jmaher --- testing/mochitest/tests/SimpleTest/SimpleTest.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/testing/mochitest/tests/SimpleTest/SimpleTest.js b/testing/mochitest/tests/SimpleTest/SimpleTest.js index a93630f4611a..52758678c075 100644 --- a/testing/mochitest/tests/SimpleTest/SimpleTest.js +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js @@ -467,6 +467,7 @@ SimpleTest.requestLongerTimeout = function (factor) { SimpleTest.waitForFocus_started = false; SimpleTest.waitForFocus_loaded = false; SimpleTest.waitForFocus_focused = false; +SimpleTest._pendingWaitForFocusCount = 0; /** * If the page is not yet loaded, waits for the load event. In addition, if @@ -487,6 +488,7 @@ SimpleTest.waitForFocus_focused = false; * true if targetWindow.location is 'about:blank'. Defaults to false */ SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) { + SimpleTest._pendingWaitForFocusCount++; if (!targetWindow) targetWindow = window; @@ -508,6 +510,7 @@ SimpleTest.waitForFocus = function (callback, targetWindow, expectBlankPage) { if (SimpleTest.waitForFocus_loaded && SimpleTest.waitForFocus_focused && !SimpleTest.waitForFocus_started) { + SimpleTest._pendingWaitForFocusCount--; SimpleTest.waitForFocus_started = true; setTimeout(callback, 0, targetWindow); } @@ -675,6 +678,15 @@ SimpleTest.finish = function () { if (SimpleTest._expectingUncaughtException) { SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!"); } + if (SimpleTest._pendingWaitForFocusCount != 0) { + SimpleTest.is(SimpleTest._pendingWaitForFocusCount, 0, + "[SimpleTest.finish()] waitForFocus() was called a " + + "different number of times from the number of " + + "callbacks run. Maybe the test terminated " + + "prematurely -- be sure to use " + + "SimpleTest.waitForExplicitFinish()."); + } + if (parentRunner) { /* We're running in an iframe, and the parent has a TestRunner */ parentRunner.testFinished(SimpleTest._tests); From 93bfe8f6cd15d44b11fe28d31c4a4c54de47d468 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Fri, 16 Mar 2012 16:07:16 -0400 Subject: [PATCH 022/109] Bug 735805 part 5 - Mochitests that run no tests should fail; r=jmaher --- testing/mochitest/tests/SimpleTest/SimpleTest.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/testing/mochitest/tests/SimpleTest/SimpleTest.js b/testing/mochitest/tests/SimpleTest/SimpleTest.js index 52758678c075..cfc0279d1303 100644 --- a/testing/mochitest/tests/SimpleTest/SimpleTest.js +++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js @@ -315,11 +315,6 @@ SimpleTest.report = function () { var failed = 0; var todo = 0; - // Report tests which did not actually check anything. - if (SimpleTest._tests.length == 0) - // ToDo: Do s/todo/ok/ when all the tests are fixed. (Bug 483407) - SimpleTest.todo(false, "[SimpleTest.report()] No checks actually run."); - var tallyAndCreateDiv = function (test) { var cls, msg, div; var diag = test.diag ? " - " + test.diag : ""; @@ -686,6 +681,13 @@ SimpleTest.finish = function () { + "prematurely -- be sure to use " + "SimpleTest.waitForExplicitFinish()."); } + if (SimpleTest._tests.length == 0) { + SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. " + + "(You need to call ok(), is(), or similar " + + "functions at least once. Make sure you use " + + "SimpleTest.waitForExplicitFinish() if you need " + + "it.)"); + } if (parentRunner) { /* We're running in an iframe, and the parent has a TestRunner */ From 6ece6d173581e6c01d1a60427083344cf9f07a0d Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Thu, 22 Mar 2012 08:01:03 +0100 Subject: [PATCH 023/109] Bug 732480 - Don't build and package update-settings.ini when updater is disabled. r=khuey --- browser/installer/package-manifest.in | 2 ++ build/Makefile.in | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index e24a21052086..e790b3a09c4e 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -99,7 +99,9 @@ @BINPATH@/@MOZ_APP_NAME@ #endif @BINPATH@/application.ini +#ifdef MOZ_UPDATER @BINPATH@/update-settings.ini +#endif @BINPATH@/platform.ini #ifndef XP_OS2 @BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@ diff --git a/build/Makefile.in b/build/Makefile.in index 2b9b252e12c3..562a94b68b4f 100644 --- a/build/Makefile.in +++ b/build/Makefile.in @@ -69,8 +69,10 @@ ifdef MOZ_APP_BASENAME DIST_FILES = application.ini ifneq (android,$(MOZ_WIDGET_TOOLKIT)) +ifdef MOZ_UPDATER DIST_FILES += update-settings.ini endif +endif ifdef LIBXUL_SDK GRE_MILESTONE = $(shell $(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(LIBXUL_DIST)/bin/platform.ini Build Milestone) @@ -160,10 +162,12 @@ leaktest.py: leaktest.py.in GARBAGE += leaktest.py ifneq (android,$(MOZ_WIDGET_TOOLKIT)) +ifdef MOZ_UPDATER update-settings.ini: update-settings.ini.in $(APP_INI_DEPS) $(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $< > $@ GARBAGE += update-settings.ini endif +endif ifdef MOZ_APP_BASENAME application.ini: application.ini.in $(APP_INI_DEPS) From 1b07eff8834f97ea0d364d80c8610610c9dafc8d Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 23 Mar 2012 17:35:09 +0100 Subject: [PATCH 024/109] Bug 734050 - Build stlport as part of our build process. r=ted --- allmakefiles.sh | 6 ++++ build/Makefile.in | 4 +++ build/stlport/Makefile.in | 30 ++++++++++++++++++++ build/stlport/stl/config/_android.h.in | 23 ++++++++++++++++ config/autoconf.mk.in | 1 + configure.in | 12 ++++++-- js/src/configure.in | 38 ++++++++++++++------------ 7 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 build/stlport/Makefile.in create mode 100644 build/stlport/stl/config/_android.h.in diff --git a/allmakefiles.sh b/allmakefiles.sh index 7edae292440b..b4f8a75782f9 100755 --- a/allmakefiles.sh +++ b/allmakefiles.sh @@ -74,6 +74,12 @@ extensions/Makefile " if [ ! "$LIBXUL_SDK" ]; then + if [ "$STLPORT_SOURCES" ]; then + add_makefiles " + build/stlport/Makefile + build/stlport/stl/config/_android.h + " + fi add_makefiles " memory/mozalloc/Makefile mozglue/Makefile diff --git a/build/Makefile.in b/build/Makefile.in index 562a94b68b4f..ddd7de498381 100644 --- a/build/Makefile.in +++ b/build/Makefile.in @@ -53,6 +53,10 @@ ifeq (WINNT,$(OS_ARCH)) DIRS = win32 endif +ifdef STLPORT_SOURCES +DIRS += stlport +endif + DIRS += pgo TEST_DIRS += autoconf/test diff --git a/build/stlport/Makefile.in b/build/stlport/Makefile.in new file mode 100644 index 000000000000..15310143df7c --- /dev/null +++ b/build/stlport/Makefile.in @@ -0,0 +1,30 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULES = stlport +LIBRARY_NAME = stlport_static +FORCE_STATIC_LIB = 1 +STL_FLAGS = + +# Force to build a static library, instead of a fake library, without +# installing it in dist/lib. +LIBRARY = $(LIB_PREFIX)$(LIBRARY_NAME).$(LIB_SUFFIX) + +VPATH += $(STLPORT_SOURCES)/src + +CPPSRCS = $(notdir $(wildcard $(STLPORT_SOURCES)/src/*.cpp)) +CSRCS = $(notdir $(wildcard $(STLPORT_SOURCES)/src/*.c)) + +include $(topsrcdir)/config/rules.mk + +DEFINES += -D_GNU_SOURCE +CXXFLAGS += -fuse-cxa-atexit +INCLUDES += -I$(STLPORT_SOURCES)/stlport diff --git a/build/stlport/stl/config/_android.h.in b/build/stlport/stl/config/_android.h.in new file mode 100644 index 000000000000..f04984f8e4ca --- /dev/null +++ b/build/stlport/stl/config/_android.h.in @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_stl_config__android_h +#define mozilla_stl_config__android_h + +#include "@STLPORT_SOURCES@/stlport/stl/config/_android.h" + +// No rtti support +#undef _STLP_NO_RTTI +#define _STLP_NO_RTTI 1 + +// No throwing exceptions +#undef _STLP_NO_EXCEPTIONS +#define _STLP_NO_EXCEPTIONS 1 + +#undef _STLP_NATIVE_CPP_C_HEADER +#define _STLP_NATIVE_CPP_C_HEADER(header) <../../system/include/header> +#undef _STLP_NATIVE_CPP_RUNTIME_HEADER +#define _STLP_NATIVE_CPP_RUNTIME_HEADER(header) <../../system/include/header> + +#endif /* mozilla_stl_config__android_h */ diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index 1f48e4b63742..1a7c2c6538ed 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -691,6 +691,7 @@ ANDROID_PLATFORM = @ANDROID_PLATFORM@ ANDROID_SDK = @ANDROID_SDK@ ANDROID_PLATFORM_TOOLS = @ANDROID_PLATFORM_TOOLS@ ANDROID_VERSION = @ANDROID_VERSION@ +STLPORT_SOURCES = @STLPORT_SOURCES@ ANDROID_PACKAGE_NAME = @ANDROID_PACKAGE_NAME@ diff --git a/configure.in b/configure.in index 7b4b44afadf2..89e4252a6e49 100644 --- a/configure.in +++ b/configure.in @@ -1616,9 +1616,10 @@ if test "$OS_TARGET" = "Android" -a -z "$gonkdir"; then STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/gnu-libstdc++/include -I$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/include -D_GLIBCXX_PERMIT_BACKWARD_HASH" STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH" STLPORT_LIBS="-lstdc++" - elif test -e "$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then - STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" - STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/" + elif test -e "$android_ndk/sources/cxx-stl/stlport/src/iostream.cpp" ; then + STLPORT_SOURCES="$android_ndk/sources/cxx-stl/stlport" + STLPORT_CPPFLAGS="-I$_objdir/build/stlport -I$android_ndk/sources/cxx-stl/stlport/stlport" + STLPORT_LDFLAGS="-L$_objdir/build/stlport -L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/" STLPORT_LIBS="-lstlport_static" elif test -e "$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" @@ -1633,6 +1634,8 @@ if test "$OS_TARGET" = "Android" -a -z "$gonkdir"; then LIBS="$LIBS $STLPORT_LIBS" fi +AC_SUBST([STLPORT_SOURCES]) + dnl ======================================================== dnl Suppress Clang Argument Warnings dnl ======================================================== @@ -9105,6 +9108,9 @@ if test -n "$MOZ_GLUE_PROGRAM_LDFLAGS"; then export MOZ_GLUE_PROGRAM_LDFLAGS fi export MOZ_APP_NAME +export STLPORT_CPPFLAGS +export STLPORT_LDFLAGS +export STLPORT_LIBS AC_OUTPUT_SUBDIRS(js/src) ac_configure_args="$_SUBDIR_CONFIG_ARGS" diff --git a/js/src/configure.in b/js/src/configure.in index 8072423e7431..48d5ac765af5 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -1530,24 +1530,26 @@ if test "$OS_TARGET" = "Android"; then fi if test "$OS_TARGET" = "Android" -a -z "$gonkdir"; then - if test -n "$MOZ_ANDROID_LIBSTDCXX" ; then - if test ! -e "$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/libstdc++.a" ; then - AC_MSG_ERROR([Cannot find path to libstdc++ (NDK version >= 5?)]) - fi - STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/gnu-libstdc++/include -I$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/include -D_GLIBCXX_PERMIT_BACKWARD_HASH" - STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH" - STLPORT_LIBS="-lstdc++" - elif test -e "$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then - STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" - STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/" - STLPORT_LIBS="-lstlport_static" - elif test -e "$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then - STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" - STLPORT_LDFLAGS="-L$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH" - STLPORT_LIBS="-lstlport_static" - elif test "$target" != "arm-android-eabi"; then - dnl fail if we're not building with NDKr4 - AC_MSG_ERROR([Couldn't find path to stlport in the android ndk]) + if test -z "$STLPORT_CPPFLAGS$STLPORT_LDFLAGS$STLPORT_LIBS"; then + if test -n "$MOZ_ANDROID_LIBSTDCXX" ; then + if test ! -e "$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/libstdc++.a" ; then + AC_MSG_ERROR([Cannot find path to libstdc++ (NDK version >= 5?)]) + fi + STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/gnu-libstdc++/include -I$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH/include -D_GLIBCXX_PERMIT_BACKWARD_HASH" + STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/gnu-libstdc++/libs/$ANDROID_CPU_ARCH" + STLPORT_LIBS="-lstdc++" + elif test -e "$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then + STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" + STLPORT_LDFLAGS="-L$android_ndk/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/" + STLPORT_LIBS="-lstlport_static" + elif test -e "$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH/libstlport_static.a" ; then + STLPORT_CPPFLAGS="-I$android_ndk/sources/cxx-stl/stlport/stlport" + STLPORT_LDFLAGS="-L$android_ndk/tmp/ndk-digit/build/install/sources/cxx-stl/stlport/libs/$ANDROID_CPU_ARCH" + STLPORT_LIBS="-lstlport_static" + elif test "$target" != "arm-android-eabi"; then + dnl fail if we're not building with NDKr4 + AC_MSG_ERROR([Couldn't find path to stlport in the android ndk]) + fi fi CXXFLAGS="$CXXFLAGS $STLPORT_CPPFLAGS" LDFLAGS="$LDFLAGS $STLPORT_LDFLAGS" From 4b167a591342844653b17085d5208730ae4a1dc6 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 16 Mar 2012 18:32:02 +0100 Subject: [PATCH 025/109] Bug 736519 - Package jssubloader cache in omnijar. r=khuey --- toolkit/mozapps/installer/packager.mk | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk index a55e4beb167e..bd411d404e8d 100644 --- a/toolkit/mozapps/installer/packager.mk +++ b/toolkit/mozapps/installer/packager.mk @@ -485,10 +485,10 @@ endif # Silence the unzip step so we don't print any binary data from the comment field. GENERATE_CACHE = \ $(_ABS_RUN_TEST_PROGRAM) $(LIBXUL_DIST)/bin/xpcshell$(BIN_SUFFIX) -g "$(PRECOMPILE_GRE)" -a "$$PWD" -f $(call core_abspath,$(MOZILLA_DIR)/toolkit/mozapps/installer/precompile_cache.js) -e "populate_startupcache('$(PRECOMPILE_DIR)', '$(OMNIJAR_NAME)', 'startupCache.zip');" && \ - rm -rf jsloader && \ + rm -rf jsloader jssubloader && \ $(UNZIP) -q startupCache.zip && \ rm startupCache.zip && \ - $(ZIP) -r9m $(OMNIJAR_NAME) jsloader/resource/$(PRECOMPILE_RESOURCE) + $(ZIP) -r9m $(OMNIJAR_NAME) jsloader/resource/$(PRECOMPILE_RESOURCE) jssubloader/*/resource/$(PRECOMPILE_RESOURCE) else GENERATE_CACHE = true endif @@ -507,6 +507,7 @@ OMNIJAR_FILES = \ defaults \ greprefs.js \ jsloader \ + jssubloader \ hyphenation \ update.locale \ $(NULL) From a0c1ba62ca46ab3ce22e2e96391cda982f4e7ceb Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Fri, 23 Mar 2012 12:45:02 -0400 Subject: [PATCH 026/109] Bug 710126 - When loading images, scale them to fit on screen. r=Cwiiis --- mobile/android/chrome/content/browser.js | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 766e1239b57d..9fdc6bb10502 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -2218,18 +2218,32 @@ Tab.prototype = { // Is it on the top level? let contentDocument = aSubject; if (contentDocument == this.browser.contentDocument) { - // reset CSS viewport and zoom to default on new page + // reset CSS viewport and zoom to default on new page, and then calculate + // them properly using the actual metadata from the page. note that the + // updateMetadata call takes into account the existing CSS viewport size + // and zoom when calculating the new ones, so we need to reset these + // things here before calling updateMetadata. this.setBrowserSize(kDefaultCSSViewportWidth, kDefaultCSSViewportHeight); this.setResolution(gScreenWidth / this.browserWidth, false); - // and then use the metadata to figure out how it needs to be updated ViewportHandler.updateMetadata(this); - // If we draw without a display-port, things can go wrong. While it's - // almost certain a display-port has been set via the - // MozScrolledAreaChanged event, make sure by sending a viewport - // update here. As it's the first paint, this will end up being a - // display-port request only. - this.sendViewportUpdate(); + // Note that if we draw without a display-port, things can go wrong. By the + // time we execute this, it's almost certain a display-port has been set via + // the MozScrolledAreaChanged event. If that didn't happen, the updateMetadata + // call above does so at the end of the updateViewportSize function. As long + // as that is happening, we don't need to do it again here. + + if (contentDocument instanceof ImageDocument) { + // for images, scale to fit width. this needs to happen *after* the call + // to updateMetadata above, because that call sets the CSS viewport which + // will affect the page size (i.e. contentDocument.body.scroll*) that we + // use in this calculation. also we call sendViewportUpdate after changing + // the resolution so that the display port gets recalculated appropriately. + let fitZoom = Math.min(gScreenWidth / contentDocument.body.scrollWidth, + gScreenHeight / contentDocument.body.scrollHeight); + this.setResolution(fitZoom, false); + this.sendViewportUpdate(); + } BrowserApp.displayedDocumentChanged(); this.contentDocumentIsDisplayed = true; From 9aa9e71a8a18b9f53c6f15d2d6bdc5017489f4e0 Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Sat, 24 Mar 2012 02:05:09 +0900 Subject: [PATCH 027/109] Bug 727942 - boundaries of imagemap may be incorrect when page is zoomed, r=marcoz --- .../src/html/nsHTMLImageMapAccessible.cpp | 69 ++++++------------- .../src/html/nsHTMLImageMapAccessible.h | 5 +- .../tests/mochitest/bounds/test_zoom.html | 26 ++++++- accessible/tests/mochitest/common.js | 4 +- accessible/tests/mochitest/layout.js | 36 ++++++++-- 5 files changed, 80 insertions(+), 60 deletions(-) diff --git a/accessible/src/html/nsHTMLImageMapAccessible.cpp b/accessible/src/html/nsHTMLImageMapAccessible.cpp index 9231bed7bc0b..4d31c17b58fb 100644 --- a/accessible/src/html/nsHTMLImageMapAccessible.cpp +++ b/accessible/src/html/nsHTMLImageMapAccessible.cpp @@ -216,53 +216,6 @@ nsHTMLAreaAccessible::Description(nsString& aDescription) area->GetShape(aDescription); } -NS_IMETHODIMP -nsHTMLAreaAccessible::GetBounds(PRInt32 *aX, PRInt32 *aY, - PRInt32 *aWidth, PRInt32 *aHeight) -{ - NS_ENSURE_ARG_POINTER(aX); - *aX = 0; - NS_ENSURE_ARG_POINTER(aY); - *aY = 0; - NS_ENSURE_ARG_POINTER(aWidth); - *aWidth = 0; - NS_ENSURE_ARG_POINTER(aHeight); - *aHeight = 0; - - if (IsDefunct()) - return NS_ERROR_FAILURE; - - // Essentially this uses GetRect on mAreas of nsImageMap from nsImageFrame. - nsPresContext *presContext = GetPresContext(); - NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); - - nsIFrame *frame = GetFrame(); - NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); - nsImageFrame *imageFrame = do_QueryFrame(frame); - - nsImageMap* map = imageFrame->GetImageMap(); - NS_ENSURE_TRUE(map, NS_ERROR_FAILURE); - - nsRect rect; - nsresult rv = map->GetBoundsForAreaContent(mContent, rect); - NS_ENSURE_SUCCESS(rv, rv); - - *aX = presContext->AppUnitsToDevPixels(rect.x); - *aY = presContext->AppUnitsToDevPixels(rect.y); - - // XXX Areas are screwy; they return their rects as a pair of points, one pair - // stored into the width and height. - *aWidth = presContext->AppUnitsToDevPixels(rect.width - rect.x); - *aHeight = presContext->AppUnitsToDevPixels(rect.height - rect.y); - - // Put coords in absolute screen coords - nsIntRect orgRectPixels = frame->GetScreenRectExternal(); - *aX += orgRectPixels.x; - *aY += orgRectPixels.y; - - return NS_OK; -} - //////////////////////////////////////////////////////////////////////////////// // nsHTMLAreaAccessible: nsAccessNode public @@ -326,3 +279,25 @@ nsHTMLAreaAccessible::CacheChildren() { // No children for aria accessible. } + +void +nsHTMLAreaAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame) +{ + nsIFrame* frame = GetFrame(); + if (!frame) + return; + + nsImageFrame* imageFrame = do_QueryFrame(frame); + nsImageMap* map = imageFrame->GetImageMap(); + + nsresult rv = map->GetBoundsForAreaContent(mContent, aBounds); + if (NS_FAILED(rv)) + return; + + // XXX Areas are screwy; they return their rects as a pair of points, one pair + // stored into the width and height. + aBounds.width -= aBounds.x; + aBounds.height -= aBounds.y; + + *aBoundingFrame = frame; +} diff --git a/accessible/src/html/nsHTMLImageMapAccessible.h b/accessible/src/html/nsHTMLImageMapAccessible.h index 0246e94f1fd6..af04f819eab1 100644 --- a/accessible/src/html/nsHTMLImageMapAccessible.h +++ b/accessible/src/html/nsHTMLImageMapAccessible.h @@ -96,10 +96,6 @@ public: nsHTMLAreaAccessible(nsIContent* aContent, nsDocAccessible* aDoc); - // nsIAccessible - - NS_IMETHOD GetBounds(PRInt32 *x, PRInt32 *y, PRInt32 *width, PRInt32 *height); - // nsAccessNode virtual bool IsPrimaryForNode() const; @@ -118,6 +114,7 @@ protected: // nsAccessible virtual void CacheChildren(); + virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame); }; #endif diff --git a/accessible/tests/mochitest/bounds/test_zoom.html b/accessible/tests/mochitest/bounds/test_zoom.html index ea79495f365e..872ee26a3737 100644 --- a/accessible/tests/mochitest/bounds/test_zoom.html +++ b/accessible/tests/mochitest/bounds/test_zoom.html @@ -7,6 +7,8 @@ + @@ -21,24 +23,42 @@ function doTest() { var tabDocument = currentTabDocument(); - var p1 = tabDocument.body.firstElementChild; - var p2 = tabDocument.body.lastElementChild; + var p1 = tabDocument.getElementById("p1"); + var p2 = tabDocument.getElementById("p2"); + + var imgMap = tabDocument.getElementById("imgmap"); + ensureImageMapTree(imgMap); + var imgMapAcc = getAccessible(imgMap); + var area = imgMapAcc.firstChild; testBounds(p1); testBounds(p2); + testBounds(area); zoomDocument(tabDocument, 2.0); testBounds(p1); testBounds(p2); + testBounds(area); closeBrowserWindow(); SimpleTest.finish(); } + var url = "data:text/html,"; + url += "

                para 1

                para 2

                "; + url += ""; + url += "

                para 1

                para 2

                ", + url, { left: 0, top: 0, width: 600, height: 600 }); diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index 97e92a40e34c..1ffad0387923 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -494,7 +494,9 @@ function ensureImageMapTree(aID) // XXX: We send a useless mouse move to the image to force it to setup its // image map, because flushing layout won't do it. Hopefully bug 135040 // will make this not suck. - synthesizeMouse(getNode(aID), 10, 10, { type: "mousemove" }); + var image = getNode(aID); + synthesizeMouse(image, 10, 10, { type: "mousemove" }, + image.ownerDocument.defaultView); // XXX This may affect a11y more than other code because imagemaps may not // get drawn or have an mouse event over them. Bug 570322 tracks a11y diff --git a/accessible/tests/mochitest/layout.js b/accessible/tests/mochitest/layout.js index f22742575bc3..d0696327ab25 100644 --- a/accessible/tests/mochitest/layout.js +++ b/accessible/tests/mochitest/layout.js @@ -107,16 +107,42 @@ function getBounds(aID) */ function getBoundsForDOMElm(aID) { + var x = 0, y = 0, width = 0, height = 0; + var elm = getNode(aID); + if (elm.localName == "area") { + var mapName = elm.parentNode.getAttribute("name"); + var selector = "[usemap='#" + mapName + "']"; + var img = elm.ownerDocument.querySelector(selector); + + var areaCoords = elm.coords.split(","); + var areaX = parseInt(areaCoords[0]); + var areaY = parseInt(areaCoords[1]); + var areaWidth = parseInt(areaCoords[2]) - areaX; + var areaHeight = parseInt(areaCoords[3]) - areaY; + + var rect = img.getBoundingClientRect(); + x = rect.left + areaX; + y = rect.top + areaY; + width = areaWidth; + height = areaHeight; + } + else { + var rect = elm.getBoundingClientRect(); + x = rect.left; + y = rect.top; + width = rect.width; + height = rect.height; + } + var elmWindow = elm.ownerDocument.defaultView; var winUtil = elmWindow. QueryInterface(Components.interfaces.nsIInterfaceRequestor). getInterface(Components.interfaces.nsIDOMWindowUtils); var ratio = winUtil.screenPixelsPerCSSPixel; - var rect = elm.getBoundingClientRect(); - return [ (rect.left + elmWindow.mozInnerScreenX) * ratio, - (rect.top + elmWindow.mozInnerScreenY) * ratio, - rect.width * ratio, - rect.height * ratio ]; + return [ (x + elmWindow.mozInnerScreenX) * ratio, + (y + elmWindow.mozInnerScreenY) * ratio, + width * ratio, + height * ratio ]; } From 87210365400dad522b68dbefd3d99e7b64fce120 Mon Sep 17 00:00:00 2001 From: Mark Capella Date: Fri, 23 Mar 2012 18:13:29 +0100 Subject: [PATCH 028/109] Bug 734023 - Remove language arguments from nsIScriptGlobalObject methods, r=jst, f=ms2ger --- content/base/src/nsScriptLoader.cpp | 7 ++--- content/events/src/nsEventListenerManager.cpp | 4 +-- content/xbl/src/nsXBLDocumentInfo.cpp | 30 ++++++++----------- content/xbl/src/nsXBLPrototypeHandler.cpp | 3 +- content/xul/content/src/nsXULElement.cpp | 10 +++---- content/xul/document/src/nsXULDocument.cpp | 8 ++--- .../document/src/nsXULPrototypeDocument.cpp | 24 ++++++--------- docshell/base/nsDocShell.cpp | 6 ++-- dom/base/nsGlobalWindow.cpp | 24 ++++++--------- dom/base/nsGlobalWindow.h | 6 ++-- dom/base/nsIScriptGlobalObject.h | 8 ++--- dom/base/nsJSUtils.cpp | 2 +- 12 files changed, 53 insertions(+), 79 deletions(-) diff --git a/content/base/src/nsScriptLoader.cpp b/content/base/src/nsScriptLoader.cpp index f2d120f88ed4..7cc5a6035c10 100644 --- a/content/base/src/nsScriptLoader.cpp +++ b/content/base/src/nsScriptLoader.cpp @@ -417,8 +417,7 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement) return false; } - nsIScriptContext *context = globalObject->GetScriptContext( - nsIProgrammingLanguage::JAVASCRIPT); + nsIScriptContext *context = globalObject->GetScriptContext(); // If scripts aren't enabled in the current context, there's no // point in going on. @@ -890,14 +889,14 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, PRUint32 stid = scriptContent ? scriptContent->GetScriptTypeID() : nsIProgrammingLanguage::JAVASCRIPT; // and make sure we are setup for this type of script. - rv = globalObject->EnsureScriptEnvironment(stid); + rv = globalObject->EnsureScriptEnvironment(); if (NS_FAILED(rv)) return rv; // Make sure context is a strong reference since we access it after // we've executed a script, which may cause all other references to // the context to go away. - nsCOMPtr context = globalObject->GetScriptContext(stid); + nsCOMPtr context = globalObject->GetScriptContext(); if (!context) { return NS_ERROR_FAILURE; } diff --git a/content/events/src/nsEventListenerManager.cpp b/content/events/src/nsEventListenerManager.cpp index 4ba9ef3cdbbf..a12813bef350 100644 --- a/content/events/src/nsEventListenerManager.cpp +++ b/content/events/src/nsEventListenerManager.cpp @@ -532,12 +532,12 @@ nsEventListenerManager::AddScriptEventListener(nsIAtom *aName, // This might be the first reference to this language in the global // We must init the language before we attempt to fetch its context. - if (NS_FAILED(global->EnsureScriptEnvironment(aLanguage))) { + if (NS_FAILED(global->EnsureScriptEnvironment())) { NS_WARNING("Failed to setup script environment for this language"); // but fall through and let the inevitable failure below handle it. } - nsIScriptContext* context = global->GetScriptContext(aLanguage); + nsIScriptContext* context = global->GetScriptContext(); NS_ENSURE_TRUE(context, NS_ERROR_FAILURE); JSObject* scope = global->GetGlobalJSObject(); diff --git a/content/xbl/src/nsXBLDocumentInfo.cpp b/content/xbl/src/nsXBLDocumentInfo.cpp index 848b26931953..9efe93f3bbfb 100644 --- a/content/xbl/src/nsXBLDocumentInfo.cpp +++ b/content/xbl/src/nsXBLDocumentInfo.cpp @@ -78,8 +78,8 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS // nsIScriptGlobalObject methods - virtual nsresult EnsureScriptEnvironment(PRUint32 aLangID); - virtual nsresult SetScriptContext(PRUint32 lang_id, nsIScriptContext *aContext); + virtual nsresult EnsureScriptEnvironment(); + virtual nsresult SetScriptContext(nsIScriptContext *aContext); virtual nsIScriptContext *GetContext(); virtual JSObject *GetGlobalJSObject(); @@ -103,10 +103,10 @@ protected: virtual ~nsXBLDocGlobalObject(); void SetContext(nsIScriptContext *aContext); - nsIScriptContext *GetScriptContext(PRUint32 language); + nsIScriptContext *GetScriptContext(); nsCOMPtr mScriptContext; - JSObject *mJSObject; // XXX JS language rabies bigotry badness + JSObject *mJSObject; nsIScriptGlobalObjectOwner* mGlobalObjectOwner; // weak reference static JSClass gSharedGlobalClass; @@ -282,28 +282,21 @@ nsXBLDocGlobalObject::SetContext(nsIScriptContext *aScriptContext) } nsresult -nsXBLDocGlobalObject::SetScriptContext(PRUint32 lang_id, nsIScriptContext *aContext) +nsXBLDocGlobalObject::SetScriptContext(nsIScriptContext *aContext) { - NS_ASSERTION(lang_id == nsIProgrammingLanguage::JAVASCRIPT, "Only JS allowed!"); SetContext(aContext); return NS_OK; } nsIScriptContext * -nsXBLDocGlobalObject::GetScriptContext(PRUint32 language) +nsXBLDocGlobalObject::GetScriptContext() { - // This impl still assumes JS - NS_ENSURE_TRUE(language==nsIProgrammingLanguage::JAVASCRIPT, nsnull); return GetContext(); } nsresult -nsXBLDocGlobalObject::EnsureScriptEnvironment(PRUint32 aLangID) +nsXBLDocGlobalObject::EnsureScriptEnvironment() { - if (aLangID != nsIProgrammingLanguage::JAVASCRIPT) { - NS_WARNING("XBL still JS only"); - return NS_ERROR_INVALID_ARG; - } if (mScriptContext) return NS_OK; // already initialized for this lang nsCOMPtr factory = do_GetService(kDOMScriptObjectFactoryCID); @@ -312,10 +305,11 @@ nsXBLDocGlobalObject::EnsureScriptEnvironment(PRUint32 aLangID) nsresult rv; nsCOMPtr scriptRuntime; - rv = NS_GetScriptRuntimeByID(aLangID, getter_AddRefs(scriptRuntime)); + rv = NS_GetScriptRuntimeByID(nsIProgrammingLanguage::JAVASCRIPT, + getter_AddRefs(scriptRuntime)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr newCtx = scriptRuntime->CreateContext(); - rv = SetScriptContext(aLangID, newCtx); + rv = SetScriptContext(newCtx); JSContext *cx = mScriptContext->GetNativeContext(); JSAutoRequest ar(cx); @@ -347,7 +341,7 @@ nsXBLDocGlobalObject::GetContext() // This whole fragile mess is predicated on the fact that // GetContext() will be called before GetScriptObject() is. if (! mScriptContext) { - nsresult rv = EnsureScriptEnvironment(nsIProgrammingLanguage::JAVASCRIPT); + nsresult rv = EnsureScriptEnvironment(); // JS is builtin so we make noise if it fails to initialize. NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to setup JS!?"); NS_ENSURE_SUCCESS(rv, nsnull); @@ -563,7 +557,7 @@ nsXBLDocumentInfo::~nsXBLDocumentInfo() /* destructor code */ if (mGlobalObject) { // remove circular reference - mGlobalObject->SetScriptContext(nsIProgrammingLanguage::JAVASCRIPT, nsnull); + mGlobalObject->SetScriptContext(nsnull); mGlobalObject->ClearGlobalObjectOwner(); // just in case } if (mBindingTable) { diff --git a/content/xbl/src/nsXBLPrototypeHandler.cpp b/content/xbl/src/nsXBLPrototypeHandler.cpp index 428006398263..1825616b8d46 100644 --- a/content/xbl/src/nsXBLPrototypeHandler.cpp +++ b/content/xbl/src/nsXBLPrototypeHandler.cpp @@ -301,8 +301,7 @@ nsXBLPrototypeHandler::ExecuteHandler(nsIDOMEventTarget* aTarget, if (!boundGlobal) return NS_OK; - nsIScriptContext *boundContext = - boundGlobal->GetScriptContext(nsIProgrammingLanguage::JAVASCRIPT); + nsIScriptContext *boundContext = boundGlobal->GetScriptContext(); if (!boundContext) return NS_OK; diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index b19340950bc2..c89d61cdd266 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -771,7 +771,7 @@ nsScriptEventHandlerOwnerTearoff::CompileEventHandler( nsIScriptGlobalObject* global = globalOwner->GetScriptGlobalObject(); NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED); - context = global->GetScriptContext(aContext->GetScriptTypeID()); + context = global->GetScriptContext(); // It could be possible the language has been setup on aContext but // not on the global - we don't demand-create language contexts on the // nsGlobalWindow @@ -2943,8 +2943,7 @@ nsXULPrototypeScript::Serialize(nsIObjectOutputStream* aStream, nsIScriptGlobalObject* aGlobal, const nsCOMArray *aNodeInfos) { - nsIScriptContext *context = aGlobal->GetScriptContext( - mScriptObject.mLangID); + nsIScriptContext *context = aGlobal->GetScriptContext(); NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nsnull || !mScriptObject.mObject, "script source still loading when serializing?!"); @@ -3022,8 +3021,7 @@ nsXULPrototypeScript::Deserialize(nsIObjectInputStream* aStream, aStream->Read32(&mLineNo); aStream->Read32(&mLangVersion); - nsIScriptContext *context = aGlobal->GetScriptContext( - mScriptObject.mLangID); + nsIScriptContext *context = aGlobal->GetScriptContext(); NS_ASSERTION(context != nsnull, "Have no context for deserialization"); NS_ENSURE_TRUE(context, NS_ERROR_UNEXPECTED); nsScriptObjectHolder newScriptObject(context); @@ -3151,7 +3149,7 @@ nsXULPrototypeScript::Compile(const PRUnichar* aText, if (! global) return NS_ERROR_UNEXPECTED; - context = global->GetScriptContext(mScriptObject.mLangID); + context = global->GetScriptContext(); NS_ASSERTION(context != nsnull, "no context for script global"); if (! context) return NS_ERROR_UNEXPECTED; diff --git a/content/xul/document/src/nsXULDocument.cpp b/content/xul/document/src/nsXULDocument.cpp index 29335725f0dc..e2407e5fd5fc 100644 --- a/content/xul/document/src/nsXULDocument.cpp +++ b/content/xul/document/src/nsXULDocument.cpp @@ -3553,9 +3553,8 @@ nsXULDocument::OnStreamComplete(nsIStreamLoader* aLoader, NS_ASSERTION(global != nsnull, "master prototype w/o global?!"); if (global) { - PRUint32 stid = scriptProto->mScriptObject.mLangID; nsIScriptContext *scriptContext = \ - global->GetScriptContext(stid); + global->GetScriptContext(); NS_ASSERTION(scriptContext != nsnull, "Failed to get script context for language"); if (scriptContext) @@ -3619,14 +3618,13 @@ nsXULDocument::ExecuteScript(nsXULPrototypeScript *aScript) NS_PRECONDITION(aScript != nsnull, "null ptr"); NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(mScriptGlobalObject, NS_ERROR_NOT_INITIALIZED); - PRUint32 stid = aScript->mScriptObject.mLangID; nsresult rv; - rv = mScriptGlobalObject->EnsureScriptEnvironment(stid); + rv = mScriptGlobalObject->EnsureScriptEnvironment(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr context = - mScriptGlobalObject->GetScriptContext(stid); + mScriptGlobalObject->GetScriptContext(); // failure getting a script context is fatal. NS_ENSURE_TRUE(context != nsnull, NS_ERROR_UNEXPECTED); diff --git a/content/xul/document/src/nsXULPrototypeDocument.cpp b/content/xul/document/src/nsXULPrototypeDocument.cpp index 040491b9ed7b..9481ded69063 100644 --- a/content/xul/document/src/nsXULPrototypeDocument.cpp +++ b/content/xul/document/src/nsXULPrototypeDocument.cpp @@ -85,10 +85,10 @@ public: virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts); virtual JSObject* GetGlobalJSObject(); - virtual nsresult EnsureScriptEnvironment(PRUint32 aLangID); + virtual nsresult EnsureScriptEnvironment(); - virtual nsIScriptContext *GetScriptContext(PRUint32 lang); - virtual nsresult SetScriptContext(PRUint32 language, nsIScriptContext *ctx); + virtual nsIScriptContext *GetScriptContext(); + virtual nsresult SetScriptContext(nsIScriptContext *ctx); // nsIScriptObjectPrincipal methods virtual nsIPrincipal* GetPrincipal(); @@ -683,10 +683,8 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULPDGlobalObject) // nsresult -nsXULPDGlobalObject::SetScriptContext(PRUint32 lang_id, nsIScriptContext *aScriptContext) +nsXULPDGlobalObject::SetScriptContext(nsIScriptContext *aScriptContext) { - NS_ABORT_IF_FALSE(lang_id == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); // almost a clone of nsGlobalWindow if (!aScriptContext) { NS_WARNING("Possibly early removal of script object, see bug #41608"); @@ -713,10 +711,8 @@ nsXULPDGlobalObject::SetScriptContext(PRUint32 lang_id, nsIScriptContext *aScrip } nsresult -nsXULPDGlobalObject::EnsureScriptEnvironment(PRUint32 lang_id) +nsXULPDGlobalObject::EnsureScriptEnvironment() { - NS_ABORT_IF_FALSE(lang_id == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); if (mContext) { return NS_OK; } @@ -752,23 +748,21 @@ nsXULPDGlobalObject::EnsureScriptEnvironment(PRUint32 lang_id) } NS_ENSURE_SUCCESS(rv, NS_OK); - rv = SetScriptContext(lang_id, ctxNew); + rv = SetScriptContext(ctxNew); NS_ENSURE_SUCCESS(rv, NS_OK); return NS_OK; } nsIScriptContext* -nsXULPDGlobalObject::GetScriptContext(PRUint32 lang_id) +nsXULPDGlobalObject::GetScriptContext() { - NS_ABORT_IF_FALSE(lang_id == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); // This global object creates a context on demand - do that now. - nsresult rv = EnsureScriptEnvironment(nsIProgrammingLanguage::JAVASCRIPT); + nsresult rv = EnsureScriptEnvironment(); if (NS_FAILED(rv)) { NS_ERROR("Failed to setup script language"); return NULL; } - // Note that EnsureScriptEnvironment has validated lang_id + return mContext; } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 6a7769968c4c..ed3cc52b7d62 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -10850,11 +10850,9 @@ nsDocShell::EnsureScriptEnvironment() nsCOMPtr win(do_QueryInterface(mScriptGlobal)); win->SetDocShell(static_cast(this)); - // Ensure the script object is set to run javascript - other languages - // setup on demand. - // XXXmarkh - should this be setup to run the default language for this doc? + // Ensure the script object is set up to run script. nsresult rv; - rv = mScriptGlobal->EnsureScriptEnvironment(nsIProgrammingLanguage::JAVASCRIPT); + rv = mScriptGlobal->EnsureScriptEnvironment(); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 76c5f80519af..b187d40ab477 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1572,10 +1572,8 @@ nsGlobalWindow::UnmarkGrayTimers() //***************************************************************************** nsresult -nsGlobalWindow::SetScriptContext(PRUint32 lang_id, nsIScriptContext *aScriptContext) +nsGlobalWindow::SetScriptContext(nsIScriptContext *aScriptContext) { - NS_ASSERTION(lang_id == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); NS_ASSERTION(IsOuterWindow(), "Uh, SetScriptContext() called on inner window!"); NS_ASSERTION(!aScriptContext || !mContext, "Bad call to SetContext()!"); @@ -1601,11 +1599,9 @@ nsGlobalWindow::SetScriptContext(PRUint32 lang_id, nsIScriptContext *aScriptCont } nsresult -nsGlobalWindow::EnsureScriptEnvironment(PRUint32 aLangID) +nsGlobalWindow::EnsureScriptEnvironment() { - NS_ASSERTION(aLangID == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); - FORWARD_TO_OUTER(EnsureScriptEnvironment, (aLangID), NS_ERROR_NOT_INITIALIZED); + FORWARD_TO_OUTER(EnsureScriptEnvironment, (), NS_ERROR_NOT_INITIALIZED); if (mJSObject) return NS_OK; @@ -1614,20 +1610,18 @@ nsGlobalWindow::EnsureScriptEnvironment(PRUint32 aLangID) "mJSObject is null, but we have an inner window?"); nsCOMPtr scriptRuntime; - nsresult rv = NS_GetScriptRuntimeByID(aLangID, getter_AddRefs(scriptRuntime)); + nsresult rv = NS_GetScriptRuntimeByID(nsIProgrammingLanguage::JAVASCRIPT, + getter_AddRefs(scriptRuntime)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr context = scriptRuntime->CreateContext(); - return SetScriptContext(aLangID, context); + return SetScriptContext(context); } nsIScriptContext * -nsGlobalWindow::GetScriptContext(PRUint32 lang) +nsGlobalWindow::GetScriptContext() { - NS_ASSERTION(lang == nsIProgrammingLanguage::JAVASCRIPT, - "We don't support this language ID"); - - FORWARD_TO_OUTER(GetScriptContext, (lang), nsnull); + FORWARD_TO_OUTER(GetScriptContext, (), nsnull); return mContext; } @@ -1637,7 +1631,7 @@ nsGlobalWindow::GetContext() FORWARD_TO_OUTER(GetContext, (), nsnull); // check GetContext is indeed identical to GetScriptContext() - NS_ASSERTION(mContext == GetScriptContext(nsIProgrammingLanguage::JAVASCRIPT), + NS_ASSERTION(mContext == GetScriptContext(), "GetContext confused?"); return mContext; } diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 3e1a484cd1a7..c77cf16d4e2f 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -317,13 +317,13 @@ public: return mJSObject; } - virtual nsresult EnsureScriptEnvironment(PRUint32 aLangID); + virtual nsresult EnsureScriptEnvironment(); - virtual nsIScriptContext *GetScriptContext(PRUint32 lang); + virtual nsIScriptContext *GetScriptContext(); // Set a new script language context for this global. The native global // for the context is created by the context's GetNativeGlobal() method. - virtual nsresult SetScriptContext(PRUint32 lang, nsIScriptContext *aContext); + virtual nsresult SetScriptContext(nsIScriptContext *aContext); virtual void OnFinalize(JSObject* aObject); virtual void SetScriptsEnabled(bool aEnabled, bool aFireTimeouts); diff --git a/dom/base/nsIScriptGlobalObject.h b/dom/base/nsIScriptGlobalObject.h index a68b758dda0e..44594e64ac3f 100644 --- a/dom/base/nsIScriptGlobalObject.h +++ b/dom/base/nsIScriptGlobalObject.h @@ -122,16 +122,16 @@ public: * has not been registered, as well as 'normal' errors, such as * out-of-memory */ - virtual nsresult EnsureScriptEnvironment(PRUint32 aLangID) = 0; + virtual nsresult EnsureScriptEnvironment() = 0; /** * Get a script context (WITHOUT added reference) for the specified language. */ - virtual nsIScriptContext *GetScriptContext(PRUint32 lang) = 0; + virtual nsIScriptContext *GetScriptContext() = 0; virtual JSObject* GetGlobalJSObject() = 0; virtual nsIScriptContext *GetContext() { - return GetScriptContext(nsIProgrammingLanguage::JAVASCRIPT); + return GetScriptContext(); } /** @@ -139,7 +139,7 @@ public: * context is created by the context's GetNativeGlobal() method. */ - virtual nsresult SetScriptContext(PRUint32 lang, nsIScriptContext *aContext) = 0; + virtual nsresult SetScriptContext(nsIScriptContext *aContext) = 0; /** * Called when the global script for a language is finalized, typically as diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 3f3d8b1c0a6f..a25f51dfad1c 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -143,7 +143,7 @@ nsJSUtils::GetStaticScriptContext(JSContext* aContext, JSObject* aObj) if (!nativeGlobal) return nsnull; - return nativeGlobal->GetScriptContext(nsIProgrammingLanguage::JAVASCRIPT); + return nativeGlobal->GetScriptContext(); } nsIScriptGlobalObject * From 10ed2c363dcedec0f8714c267811b1e31bdcf76b Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 23 Mar 2012 17:48:11 +0000 Subject: [PATCH 029/109] bug 736210 - fix bug in nsCaseTransformTextRunFactory surrogate handling. r=smontagu --- layout/generic/nsTextRunTransformations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layout/generic/nsTextRunTransformations.cpp b/layout/generic/nsTextRunTransformations.cpp index 74ae0da9302c..07d89fa07b66 100644 --- a/layout/generic/nsTextRunTransformations.cpp +++ b/layout/generic/nsTextRunTransformations.cpp @@ -407,7 +407,7 @@ nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun, convertedString.Append(H_SURROGATE(ch)); convertedString.Append(L_SURROGATE(ch)); i++; - charsToMergeArray.AppendElement(true); + charsToMergeArray.AppendElement(false); styleArray.AppendElement(styles[i]); canBreakBeforeArray.AppendElement(false); } From 1e14bacab7ce2898a80579351f0c696bf190d586 Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Fri, 23 Mar 2012 13:57:23 -0400 Subject: [PATCH 030/109] Bug 734975 - Package a .tar.bz2 file for the XULRunner package on Mac, instead of the .pkg.dmg which installs XULRunner to an obsolete system location. r=Mossop --- xulrunner/README.xulrunner | 13 +++ xulrunner/app/Makefile.in | 2 +- xulrunner/installer/Makefile.in | 11 +- xulrunner/installer/mac/Description.plist.in | 12 -- xulrunner/installer/mac/Info.plist.in | 41 ------- xulrunner/installer/mac/Makefile.in | 109 ------------------- 6 files changed, 17 insertions(+), 171 deletions(-) create mode 100644 xulrunner/README.xulrunner delete mode 100644 xulrunner/installer/mac/Description.plist.in delete mode 100644 xulrunner/installer/mac/Info.plist.in delete mode 100644 xulrunner/installer/mac/Makefile.in diff --git a/xulrunner/README.xulrunner b/xulrunner/README.xulrunner new file mode 100644 index 000000000000..424b7a615077 --- /dev/null +++ b/xulrunner/README.xulrunner @@ -0,0 +1,13 @@ +XULRunner is a package which can be used to run applications written in HTML +or XUL. It can also be used to embed the gecko rendering engine into +binary applications. + +XULRunner is not a product; it is a tool which can be used to create products. +It is a byproduct of Firefox development and the Mozilla community does not +have a strong commitment to support XULRunner development apart from Firefox +development. + +For more information about using XULRunner or how to use this binary package, +see the Mozilla Developer Center article: + +https://developer.mozilla.org/en/XULRunner diff --git a/xulrunner/app/Makefile.in b/xulrunner/app/Makefile.in index d6efcbb38931..b4c259c4fc51 100644 --- a/xulrunner/app/Makefile.in +++ b/xulrunner/app/Makefile.in @@ -228,7 +228,7 @@ clean clobber:: rm -rf $(DIST)/$(FRAMEWORK_NAME).framework endif -README_FILE = $(topsrcdir)/README.txt +README_FILE = $(srcdir)/../README.xulrunner libs:: $(INSTALL) $(IFLAGS1) $(README_FILE) $(DIST)/bin diff --git a/xulrunner/installer/Makefile.in b/xulrunner/installer/Makefile.in index 4ff48803327f..647bf6d80a20 100644 --- a/xulrunner/installer/Makefile.in +++ b/xulrunner/installer/Makefile.in @@ -50,16 +50,11 @@ NO_PKG_FILES = \ regxpcom* \ $(NULL) -# If we're on mac, we want to make the .pkg first, in the mac/ -# directory. Then packager.mk can put it into a DMG +# If we're on mac, we don't want an end-user-facing DMG, we want a .tar.bz2 +# which developers then use to package their application. ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) -DIRS += mac -_APPNAME = $(PKG_BASENAME).pkg -_BINPATH = /XUL.framework/Versions/Current -PKG_SKIP_STRIP = 1 -MOZ_PKG_SPECIAL = pkg -PKG_DMG_SOURCE = $(STAGEPATH)xulrunner-pkg +MOZ_PKG_FORMAT = BZ2 endif include $(topsrcdir)/config/rules.mk diff --git a/xulrunner/installer/mac/Description.plist.in b/xulrunner/installer/mac/Description.plist.in deleted file mode 100644 index 3a82f3f72c23..000000000000 --- a/xulrunner/installer/mac/Description.plist.in +++ /dev/null @@ -1,12 +0,0 @@ - - -#filter substitution - - - - - - IFPkgDescriptionTitle - Mozilla XULRunner @MOZ_APP_VERSION@ - - diff --git a/xulrunner/installer/mac/Info.plist.in b/xulrunner/installer/mac/Info.plist.in deleted file mode 100644 index 7d9bb587e8e0..000000000000 --- a/xulrunner/installer/mac/Info.plist.in +++ /dev/null @@ -1,41 +0,0 @@ - - -#filter substitution - - - - - - CFBundleIdentifier - org.mozilla.xulrunner - - CFBundleName - Mozilla XULRunner - - CFBundleShortVersionString - @MOZ_APP_VERSION@ - - IFPkgFlagAuthorizationAction - RootAuthorization - - IFPkgFlagDefaultLocation - /Library/Frameworks - - IFPkgFlagFollowLinks - - - IFPkgFormatVersion - 0.10000000149011612 - - LSMinimumSystemVersion - 10.5 - - LSMinimumSystemVersionByArchitecture - - i386 - 10.5.0 - x86_64 - 10.6.0 - - - diff --git a/xulrunner/installer/mac/Makefile.in b/xulrunner/installer/mac/Makefile.in deleted file mode 100644 index b9dfe41b0c1c..000000000000 --- a/xulrunner/installer/mac/Makefile.in +++ /dev/null @@ -1,109 +0,0 @@ -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is the Mozilla Browser code. -# -# The Initial Developer of the Original Code is -# IBM Corporation. -# Portions created by the Initial Developer are Copyright (C) 2004 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Brian Ryner -# Benjamin Smedberg -# Darin Fisher -# -# 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 ***** - -DEPTH = ../../.. -topsrcdir = @top_srcdir@ -srcdir = @srcdir@ -VPATH = @srcdir@ - -include $(DEPTH)/config/autoconf.mk - -DEFINES += -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) - -NO_PKG_FILES = \ - xulrunner-config \ - regchrome* \ - regxpcom* \ - xpcshell* \ - xpidl* \ - xpt_dump* \ - xpt_link* \ - $(NULL) - -include $(topsrcdir)/config/rules.mk - -libs:: stage-package - -%.plist: %.plist.in - $(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $< > $@ - -PACKAGER_NO_LIBS=1 -_APPNAME = XUL.framework -_BINPATH = /$(_APPNAME)/Versions/Current - -include $(topsrcdir)/toolkit/mozapps/installer/packager.mk - -_ABS_OBJDIR := $(shell pwd) -_ABS_DIST := $(shell cd $(DIST) && pwd) - -# Syntax "chown_root " -# Should be equivalent to "chown -R root:admin " -CHOWN_ROOT ?= $(error CHOWN_ROOT must be set to a setuid script.) - -# Syntax "chown_revert " -# Should be equivalent to "chown -R " -CHOWN_REVERT ?= $(error CHOWN_REVERT must be set to a setuid script.) - -# Do the real work here: we take $(DIST)/XUL.framework and make it into an -# installer .pkg bundle. The xulrunner/installer makefile will then pack the -# installer into a DMG. -libs:: Info.plist Description.plist - $(RM) -rf resource-stage - mkdir resource-stage - $(RM) -rf $(DIST)/$(STAGEPATH)xulrunner-pkg - mkdir $(DIST)/$(STAGEPATH)xulrunner-pkg - chmod -R a+rX,u+w,go-w,-s,-t $(DIST)/$(STAGEPATH)$(MOZ_PKG_APPNAME) -# For some unknown reason, PackageMaker requires absolute paths to everything. -# For other poor reasons, PackageMaker returns an exit code of "1" if the -# version string has any letters in it (e.g. 1.9a1). - unset NEXT_ROOT; \ - $(CHOWN_ROOT) $(DIST)/$(STAGEPATH)$(MOZ_PKG_APPNAME) && \ - /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker -build -v \ - -p $(_ABS_DIST)/$(STAGEPATH)xulrunner-pkg/$(PKG_BASENAME).pkg \ - -f $(_ABS_DIST)/$(STAGEPATH)$(MOZ_PKG_APPNAME) \ - -r $(_ABS_OBJDIR)/resource-stage \ - -i $(_ABS_OBJDIR)/Info.plist \ - -d $(_ABS_OBJDIR)/Description.plist > packagemaker.log; \ - SAVED=$$?; \ - if [ "$$SAVED" == "1" -a \ - `grep -c 'was completed with the following non-fatal errors' < packagemaker.log` -gt 0 ]; then \ - SAVED=0; \ - fi; \ - $(CHOWN_REVERT) $(DIST)/$(STAGEPATH)$(MOZ_PKG_APPNAME); \ - exit $$SAVED From baf4865f2ff1ede652ea120ce301eac600dd374d Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Fri, 23 Mar 2012 11:03:47 -0700 Subject: [PATCH 031/109] =?UTF-8?q?Bug=20731024=20-=20Part=201:=20Unsuppor?= =?UTF-8?q?ted=20record=20types=20causes=20Android=20Sync=20to=20reorder?= =?UTF-8?q?=20folders=20containing=20livemarks/queries/separators=E2=80=A6?= =?UTF-8?q?.=20r=3Dnalexander?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...roidBrowserBookmarksRepositorySession.java | 2 + .../android/BrowserContractHelpers.java | 56 +++++++- .../sync/repositories/android/RepoUtils.java | 2 +- .../repositories/domain/BookmarkRecord.java | 131 +++++++++++++----- 4 files changed, 154 insertions(+), 37 deletions(-) diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java index 563206495aa2..66dea741c8c1 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java @@ -152,6 +152,7 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo * */ public static final Map SPECIAL_GUID_PARENTS; + static { HashMap m = new HashMap(); m.put("places", null); @@ -163,6 +164,7 @@ public class AndroidBrowserBookmarksRepositorySession extends AndroidBrowserRepo SPECIAL_GUID_PARENTS = Collections.unmodifiableMap(m); } + /** * A map of guids to their localized name strings. */ diff --git a/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java b/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java index 3254735eebce..0ade8fce532f 100644 --- a/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java +++ b/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java @@ -4,12 +4,14 @@ package org.mozilla.gecko.sync.repositories.android; -import android.net.Uri; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; -// This will exist implicitly when we're merged into Fennec, or explicitly -// due to our own imported copy of Fennec's BrowserContract.java.in. import org.mozilla.gecko.db.BrowserContract; +import android.net.Uri; + public class BrowserContractHelpers extends BrowserContract { protected static Uri withSyncAndDeleted(Uri u) { return u.buildUpon() @@ -77,4 +79,52 @@ public class BrowserContractHelpers extends BrowserContract { Bookmarks.DESCRIPTION, Bookmarks.KEYWORD }; + + // Mapping from Sync types to Fennec types. + public static final String[] BOOKMARK_TYPE_CODE_TO_STRING = { + // Observe omissions: "microsummary", "item". + "folder", "bookmark", "separator", "livemark", "query" + }; + private static final int MAX_BOOKMARK_TYPE_CODE = BOOKMARK_TYPE_CODE_TO_STRING.length - 1; + public static final Map BOOKMARK_TYPE_STRING_TO_CODE; + static { + HashMap t = new HashMap(); + t.put("folder", Bookmarks.TYPE_FOLDER); + t.put("bookmark", Bookmarks.TYPE_BOOKMARK); + t.put("separator", Bookmarks.TYPE_SEPARATOR); + t.put("livemark", Bookmarks.TYPE_LIVEMARK); + t.put("query", Bookmarks.TYPE_QUERY); + BOOKMARK_TYPE_STRING_TO_CODE = Collections.unmodifiableMap(t); + } + + /** + * Convert a database bookmark type code into the Sync string equivalent. + * + * @param code one of the Bookmarks.TYPE_* enumerations. + * @return the string equivalent, or null if not found. + */ + public static String typeStringForCode(int code) { + if (0 <= code && code <= MAX_BOOKMARK_TYPE_CODE) { + return BOOKMARK_TYPE_CODE_TO_STRING[code]; + } + return null; + } + + /** + * Convert a Sync type string into a Fennec type code. + * + * @param type a type string, such as "livemark". + * @return the type code, or -1 if not found. + */ + public static int typeCodeForString(String type) { + Integer found = BOOKMARK_TYPE_STRING_TO_CODE.get(type); + if (found == null) { + return -1; + } + return found.intValue(); + } + + public static boolean isSupportedType(String type) { + return BOOKMARK_TYPE_STRING_TO_CODE.containsKey(type); + } } diff --git a/mobile/android/base/sync/repositories/android/RepoUtils.java b/mobile/android/base/sync/repositories/android/RepoUtils.java index 247510601231..b0a1480dabeb 100644 --- a/mobile/android/base/sync/repositories/android/RepoUtils.java +++ b/mobile/android/base/sync/repositories/android/RepoUtils.java @@ -93,7 +93,7 @@ public class RepoUtils { return cur.getLong(cur.getColumnIndex(colId)); } - public static long getIntFromCursor(Cursor cur, String colId) { + public static int getIntFromCursor(Cursor cur, String colId) { return cur.getInt(cur.getColumnIndex(colId)); } diff --git a/mobile/android/base/sync/repositories/domain/BookmarkRecord.java b/mobile/android/base/sync/repositories/domain/BookmarkRecord.java index bc0e0b01fcc2..79ce5c1da066 100644 --- a/mobile/android/base/sync/repositories/domain/BookmarkRecord.java +++ b/mobile/android/base/sync/repositories/domain/BookmarkRecord.java @@ -43,7 +43,6 @@ import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.NonArrayJSONException; import org.mozilla.gecko.sync.Utils; -import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor; import org.mozilla.gecko.sync.repositories.android.RepoUtils; import android.util.Log; @@ -141,6 +140,61 @@ public class BookmarkRecord extends Record { return out; } + public boolean isBookmark() { + if (type == null) { + return false; + } + return type.equals("bookmark"); + } + + public boolean isFolder() { + if (type == null) { + return false; + } + return type.equals("folder"); + } + + public boolean isLivemark() { + if (type == null) { + return false; + } + return type.equals("livemark"); + } + + public boolean isSeparator() { + if (type == null) { + return false; + } + return type.equals("separator"); + } + + public boolean isMicrosummary() { + if (type == null) { + return false; + } + return type.equals("microsummary"); + } + + public boolean isQuery() { + if (type == null) { + return false; + } + return type.equals("query"); + } + + /** + * Return true if this record should have the Sync fields + * of a bookmark, microsummary, or query. + */ + private boolean isBookmarkIsh() { + if (type == null) { + return false; + } + return type.equals("bookmark") || + type.equals("microsummary") || + type.equals("query"); + } + @Override protected void initFromPayload(ExtendedJSONObject payload) { this.type = (String) payload.get("type"); @@ -149,19 +203,24 @@ public class BookmarkRecord extends Record { this.parentID = (String) payload.get("parentid"); this.parentName = (String) payload.get("parentName"); - // Bookmark. - if (isBookmark()) { - this.bookmarkURI = (String) payload.get("bmkUri"); - this.keyword = (String) payload.get("keyword"); - try { - this.tags = payload.getArray("tags"); - } catch (NonArrayJSONException e) { - Log.e(LOG_TAG, "Got non-array tags in bookmark record " + this.guid, e); - this.tags = new JSONArray(); - } + // bookmark, microsummary, query. + if (isBookmarkIsh()) { + this.keyword = (String) payload.get("keyword"); + try { + this.tags = payload.getArray("tags"); + } catch (NonArrayJSONException e) { + Logger.warn(LOG_TAG, "Got non-array tags in bookmark record " + this.guid, e); + this.tags = new JSONArray(); + } } - // Folder. + // bookmark. + if (isBookmark()) { + this.bookmarkURI = (String) payload.get("bmkUri"); + return; + } + + // folder. if (isFolder()) { try { this.children = payload.getArray("children"); @@ -170,19 +229,25 @@ public class BookmarkRecord extends Record { // Let's see if we can recover later by using the parentid pointers. this.children = new JSONArray(); } + return; } - // TODO: predecessor ID? - // TODO: type-specific attributes: - /* - public String generatorURI; - public String staticTitle; - public String folderName; - public String queryID; - public String siteURI; - public String feedURI; - public String pos; - */ + if (isLivemark()) { + // TODO: siteUri, feedUri. + return; + } + if (isQuery()) { + // TODO: queryId (optional), folderName. + return; + } + if (isMicrosummary()) { + // TODO: generatorUri, staticTitle. + return; + } + if (isSeparator()) { + this.pos = payload.getString("pos"); + return; + } } @Override @@ -192,22 +257,19 @@ public class BookmarkRecord extends Record { putPayload(payload, "description", this.description); putPayload(payload, "parentid", this.parentID); putPayload(payload, "parentName", this.parentName); + putPayload(payload, "keyword", this.keyword); + + if (this.tags != null) { + payload.put("tags", this.tags); + } if (isBookmark()) { payload.put("bmkUri", bookmarkURI); - payload.put("keyword", keyword); - payload.put("tags", this.tags); } else if (isFolder()) { payload.put("children", this.children); } - } - public boolean isBookmark() { - return AndroidBrowserBookmarksDataAccessor.TYPE_BOOKMARK.equalsIgnoreCase(this.type); - } - - public boolean isFolder() { - return AndroidBrowserBookmarksDataAccessor.TYPE_FOLDER.equalsIgnoreCase(this.type); + // TODO: fields for other types. } private void trace(String s) { @@ -226,6 +288,10 @@ public class BookmarkRecord extends Record { return false; } + if (!RepoUtils.stringsEqual(this.type, other.type)) { + return false; + } + // Check children. if (isFolder() && (this.children != other.children)) { trace("BookmarkRecord.equals: this folder: " + this.title + ", " + this.guid); @@ -260,7 +326,6 @@ public class BookmarkRecord extends Record { && RepoUtils.stringsEqual(this.bookmarkURI, other.bookmarkURI) && RepoUtils.stringsEqual(this.parentID, other.parentID) && RepoUtils.stringsEqual(this.parentName, other.parentName) - && RepoUtils.stringsEqual(this.type, other.type) && RepoUtils.stringsEqual(this.description, other.description) && RepoUtils.stringsEqual(this.keyword, other.keyword) && jsonArrayStringsEqual(this.tags, other.tags); From 6173ce3d9787a3fbf477088a44b2e835626fa301 Mon Sep 17 00:00:00 2001 From: Marina Samuel Date: Fri, 23 Mar 2012 11:03:48 -0700 Subject: [PATCH 032/109] Bug 715792 - Process client record commands (but don't act on them). r=rnewman --- .../android/base/sync/CommandProcessor.java | 83 +++++++++++++++++++ mobile/android/base/sync/CommandRunner.java | 11 +++ mobile/android/base/sync/GlobalSession.java | 30 ++++++- .../sync/stage/SyncClientsEngineStage.java | 7 ++ .../base/sync/syncadapter/SyncAdapter.java | 18 ++++ mobile/android/sync/java-sources.mn | 2 +- 6 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 mobile/android/base/sync/CommandProcessor.java create mode 100644 mobile/android/base/sync/CommandRunner.java diff --git a/mobile/android/base/sync/CommandProcessor.java b/mobile/android/base/sync/CommandProcessor.java new file mode 100644 index 000000000000..5d79b624eb6f --- /dev/null +++ b/mobile/android/base/sync/CommandProcessor.java @@ -0,0 +1,83 @@ +/* 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/. */ + +package org.mozilla.gecko.sync; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import org.json.simple.JSONArray; + +public class CommandProcessor { + private static final String LOG_TAG = "Command"; + protected ConcurrentHashMap commands = new ConcurrentHashMap(); + + private final static CommandProcessor processor = new CommandProcessor(); + + public static CommandProcessor getProcessor() { + return processor; + } + + public static class Command { + public final String commandType; + public final List args; + + public Command(String commandType, List args) { + this.commandType = commandType; + this.args = args; + } + } + + public void registerCommand(String commandType, CommandRunner command) { + commands.put(commandType, command); + } + + public void processCommand(ExtendedJSONObject unparsedCommand) { + Command command = parseCommand(unparsedCommand); + if (command == null) { + Logger.debug(LOG_TAG, "Invalid command: " + unparsedCommand + " will not be processed."); + return; + } + + CommandRunner executableCommand = commands.get(command.commandType); + if (executableCommand == null) { + Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed."); + return; + } + + executableCommand.executeCommand(command.args); + } + + /** + * Parse a JSON command into a ParsedCommand object for easier handling. + * + * @param unparsedCommand - command as ExtendedJSONObject + * @return - null if command is invalid, else return ParsedCommand with + * no null attributes. + */ + protected Command parseCommand(ExtendedJSONObject unparsedCommand) { + String type = (String) unparsedCommand.get("command"); + if (type == null) { + return null; + } + + try { + JSONArray unparsedArgs = unparsedCommand.getArray("args"); + if (unparsedArgs == null) { + return null; + } + ArrayList args = new ArrayList(unparsedArgs.size()); + + for (int i = 0; i < unparsedArgs.size(); i++) { + args.add(unparsedArgs.get(i).toString()); + } + + return new Command(type, args); + } catch (NonArrayJSONException e) { + Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command"); + return null; + } + } +} diff --git a/mobile/android/base/sync/CommandRunner.java b/mobile/android/base/sync/CommandRunner.java new file mode 100644 index 000000000000..31323a13bd09 --- /dev/null +++ b/mobile/android/base/sync/CommandRunner.java @@ -0,0 +1,11 @@ +/* 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/. */ + +package org.mozilla.gecko.sync; + +import java.util.List; + +public interface CommandRunner { + public void executeCommand(List args); +} diff --git a/mobile/android/base/sync/GlobalSession.java b/mobile/android/base/sync/GlobalSession.java index 7a78ed9de4e7..322e8f15c20b 100644 --- a/mobile/android/base/sync/GlobalSession.java +++ b/mobile/android/base/sync/GlobalSession.java @@ -9,6 +9,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.json.simple.parser.ParseException; @@ -155,9 +156,28 @@ public class GlobalSession implements CredentialsSource, PrefsSource { config.password = password; config.syncKeyBundle = syncKeyBundle; + registerCommands(); prepareStages(); } + protected void registerCommands() { + CommandProcessor processor = CommandProcessor.getProcessor(); + + processor.registerCommand("resetEngine", new CommandRunner() { + @Override + public void executeCommand(List args) { + resetClient(new String[] { args.get(0) }); + } + }); + + processor.registerCommand("resetAll", new CommandRunner() { + @Override + public void executeCommand(List args) { + resetClient(null); + } + }); + } + protected void prepareStages() { stages = new HashMap(); stages.put(Stage.checkPreconditions, new CheckPreconditionsStage()); @@ -415,7 +435,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { String localSyncID = this.getSyncID(); if (!remoteSyncID.equals(localSyncID)) { // Sync ID has changed. Reset timestamps and fetch new keys. - resetClient(); + resetClient(null); if (config.collectionKeys != null) { config.collectionKeys.clear(); } @@ -468,7 +488,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource { @Override public void onWiped(long timestamp) { - session.resetClient(); + session.resetClient(null); session.config.collectionKeys.clear(); // TODO: make sure we clear our keys timestamp. session.config.persistToPrefs(); @@ -643,10 +663,12 @@ public class GlobalSession implements CredentialsSource, PrefsSource { * Reset our state. Clear our sync ID, reset each engine, drop any * cached records. */ - private void resetClient() { + private void resetClient(String[] engines) { + if (engines == null) { + // Set `engines` to be *all* the engines. + } // TODO: futz with config?! // TODO: engines?! - } /** diff --git a/mobile/android/base/sync/stage/SyncClientsEngineStage.java b/mobile/android/base/sync/stage/SyncClientsEngineStage.java index 089857aa3e17..1494d45f0222 100644 --- a/mobile/android/base/sync/stage/SyncClientsEngineStage.java +++ b/mobile/android/base/sync/stage/SyncClientsEngineStage.java @@ -10,7 +10,10 @@ import java.net.URISyntaxException; import java.util.concurrent.atomic.AtomicInteger; import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.mozilla.gecko.sync.CommandProcessor; import org.mozilla.gecko.sync.CryptoRecord; +import org.mozilla.gecko.sync.ExtendedJSONObject; import org.mozilla.gecko.sync.GlobalSession; import org.mozilla.gecko.sync.HTTPFailureException; import org.mozilla.gecko.sync.Logger; @@ -278,8 +281,12 @@ public class SyncClientsEngineStage implements GlobalSyncStage { } commandsProcessedShouldUpload = true; + CommandProcessor processor = CommandProcessor.getProcessor(); // TODO: Bug 715792 - Process commands here. + for (int i = 0; i < commands.size(); i++) { + processor.processCommand(new ExtendedJSONObject((JSONObject)commands.get(i))); + } } protected void checkAndUpload() { diff --git a/mobile/android/base/sync/syncadapter/SyncAdapter.java b/mobile/android/base/sync/syncadapter/SyncAdapter.java index 2cdb8cc3c774..538bfe7f94c2 100644 --- a/mobile/android/base/sync/syncadapter/SyncAdapter.java +++ b/mobile/android/base/sync/syncadapter/SyncAdapter.java @@ -7,12 +7,16 @@ package org.mozilla.gecko.sync.syncadapter; import java.io.IOException; import java.net.URI; import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.concurrent.TimeUnit; import org.json.simple.parser.ParseException; import org.mozilla.gecko.sync.AlreadySyncingException; +import org.mozilla.gecko.sync.CommandRunner; +import org.mozilla.gecko.sync.CommandProcessor; import org.mozilla.gecko.sync.GlobalConstants; import org.mozilla.gecko.sync.GlobalSession; +import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.NonObjectJSONException; import org.mozilla.gecko.sync.SyncConfiguration; import org.mozilla.gecko.sync.SyncConfigurationException; @@ -62,6 +66,15 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe mContext = context; Log.d(LOG_TAG, "AccountManager.get(" + mContext + ")"); mAccountManager = AccountManager.get(context); + + // Register the displayURI command here so our SyncService + // can receive notifications to open a URI. + CommandProcessor.getProcessor().registerCommand("displayURI", new CommandRunner() { + @Override + public void executeCommand(List args) { + displayURI(args.get(0), args.get(1)); + } + }); } private SharedPreferences getGlobalPrefs() { @@ -480,4 +493,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe public void informUnauthorizedResponse(GlobalSession session, URI oldClusterURL) { setClusterURLIsStale(true); } + + public void displayURI(String uri, String clientId) { + Logger.info(LOG_TAG, "Received a URI for display: " + uri + " from " + clientId); + // TODO: Bug 732147 - Send tab to device: receiving pushed tabs + } } diff --git a/mobile/android/sync/java-sources.mn b/mobile/android/sync/java-sources.mn index 873312f19c17..be59c900fc9e 100644 --- a/mobile/android/sync/java-sources.mn +++ b/mobile/android/sync/java-sources.mn @@ -1 +1 @@ -sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java +sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CommandProcessor.java sync/CommandRunner.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java From 00d51298fb86eda72acdd046d27282079eb9ceda Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Fri, 23 Mar 2012 11:03:48 -0700 Subject: [PATCH 033/109] Bug 709402 preparation. r=rnewman --- .../AndroidBrowserBookmarksDataAccessor.java | 2 +- .../AndroidBrowserHistoryDataAccessor.java | 69 +++++----------- .../AndroidBrowserPasswordsDataAccessor.java | 62 ++++---------- .../AndroidBrowserRepositoryDataAccessor.java | 80 ++++++++++--------- .../AndroidBrowserRepositorySession.java | 4 +- .../android/BrowserContractHelpers.java | 27 ++++++- .../sync/repositories/android/RepoUtils.java | 33 +++++--- mobile/android/sync/java-sources.mn | 2 +- 8 files changed, 125 insertions(+), 154 deletions(-) diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java index 341f7a21c665..46e3093dabb1 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java @@ -211,7 +211,7 @@ public class AndroidBrowserBookmarksDataAccessor extends AndroidBrowserRepositor // content provider to do that for us. return cv; } - + /** * Returns a cursor over non-deleted records that list the given androidID as a parent. */ diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java index 5851730711c0..c5b14893b7c2 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserHistoryDataAccessor.java @@ -1,54 +1,22 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Jason Voll - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.repositories.android; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.mozilla.gecko.db.BrowserContract; +import org.mozilla.gecko.sync.Logger; import org.mozilla.gecko.sync.repositories.domain.HistoryRecord; import org.mozilla.gecko.sync.repositories.domain.Record; import android.content.ContentValues; import android.content.Context; import android.net.Uri; -import android.util.Log; -public class AndroidBrowserHistoryDataAccessor extends AndroidBrowserRepositoryDataAccessor { +public class AndroidBrowserHistoryDataAccessor extends + AndroidBrowserRepositoryDataAccessor { private AndroidBrowserHistoryDataExtender dataExtender; @@ -56,7 +24,7 @@ public class AndroidBrowserHistoryDataAccessor extends AndroidBrowserRepositoryD super(context); dataExtender = new AndroidBrowserHistoryDataExtender(context); } - + public AndroidBrowserHistoryDataExtender getHistoryDataExtender() { return dataExtender; } @@ -70,15 +38,16 @@ public class AndroidBrowserHistoryDataAccessor extends AndroidBrowserRepositoryD protected ContentValues getContentValues(Record record) { ContentValues cv = new ContentValues(); HistoryRecord rec = (HistoryRecord) record; - cv.put(BrowserContract.History.GUID, rec.guid); - cv.put(BrowserContract.History.TITLE, rec.title); - cv.put(BrowserContract.History.URL, rec.histURI); + cv.put(BrowserContract.History.GUID, rec.guid); + cv.put(BrowserContract.History.TITLE, rec.title); + cv.put(BrowserContract.History.URL, rec.histURI); if (rec.visits != null) { JSONArray visits = rec.visits; long mostRecent = 0; for (int i = 0; i < visits.size(); i++) { JSONObject visit = (JSONObject) visits.get(i); - long visitDate = (Long) visit.get(AndroidBrowserHistoryRepositorySession.KEY_DATE); + long visitDate = (Long) visit + .get(AndroidBrowserHistoryRepositorySession.KEY_DATE); if (visitDate > mostRecent) { mostRecent = visitDate; } @@ -94,13 +63,13 @@ public class AndroidBrowserHistoryDataAccessor extends AndroidBrowserRepositoryD protected String[] getAllColumns() { return BrowserContractHelpers.HistoryColumns; } - + @Override public Uri insert(Record record) { HistoryRecord rec = (HistoryRecord) record; - Log.d(LOG_TAG, "Storing visits for " + record.guid); + Logger.debug(LOG_TAG, "Storing visits for " + record.guid); dataExtender.store(record.guid, rec.visits); - Log.d(LOG_TAG, "Storing record " + record.guid); + Logger.debug(LOG_TAG, "Storing record " + record.guid); return super.insert(record); } @@ -108,17 +77,17 @@ public class AndroidBrowserHistoryDataAccessor extends AndroidBrowserRepositoryD public void update(String oldGUID, Record newRecord) { HistoryRecord rec = (HistoryRecord) newRecord; String newGUID = newRecord.guid; - Log.d(LOG_TAG, "Storing visits for " + newGUID + ", replacing " + oldGUID); + Logger.debug(LOG_TAG, "Storing visits for " + newGUID + ", replacing " + oldGUID); dataExtender.delete(oldGUID); dataExtender.store(newGUID, rec.visits); super.update(oldGUID, newRecord); } @Override - protected void delete(String guid) { - Log.d(LOG_TAG, "Deleting record " + guid); - super.delete(guid); + public int purgeGuid(String guid) { + Logger.debug(LOG_TAG, "Purging record with " + guid); dataExtender.delete(guid); + return super.purgeGuid(guid); } public void closeExtender() { diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java index d8e2f10c2bb9..442a56344eab 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java @@ -1,42 +1,10 @@ -/* ***** 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 Android Sync Client. - * - * The Initial Developer of the Original Code is - * the Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2011 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Jason Voll - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.sync.repositories.android; +import org.mozilla.gecko.db.BrowserContract; import org.mozilla.gecko.sync.repositories.domain.PasswordRecord; import org.mozilla.gecko.sync.repositories.domain.Record; @@ -55,20 +23,20 @@ public class AndroidBrowserPasswordsDataAccessor extends AndroidBrowserRepositor PasswordRecord rec = (PasswordRecord) record; ContentValues cv = new ContentValues(); - cv.put(PasswordColumns.GUID, rec.guid); - cv.put(PasswordColumns.HOSTNAME, rec.hostname); - cv.put(PasswordColumns.HTTP_REALM, rec.httpRealm); - cv.put(PasswordColumns.FORM_SUBMIT_URL, rec.formSubmitURL); - cv.put(PasswordColumns.USERNAME_FIELD, rec.usernameField); - cv.put(PasswordColumns.PASSWORD_FIELD, rec.passwordField); + cv.put(BrowserContract.Passwords.GUID, rec.guid); + cv.put(BrowserContract.Passwords.HOSTNAME, rec.hostname); + cv.put(BrowserContract.Passwords.HTTP_REALM, rec.httpRealm); + cv.put(BrowserContract.Passwords.FORM_SUBMIT_URL, rec.formSubmitURL); + cv.put(BrowserContract.Passwords.USERNAME_FIELD, rec.usernameField); + cv.put(BrowserContract.Passwords.PASSWORD_FIELD, rec.passwordField); // TODO Do encryption of username/password here. Bug 711636 - cv.put(PasswordColumns.ENC_TYPE, rec.encType); - cv.put(PasswordColumns.ENCRYPTED_USERNAME, rec.username); - cv.put(PasswordColumns.ENCRYPTED_PASSWORD, rec.password); + cv.put(BrowserContract.Passwords.ENC_TYPE, rec.encType); + cv.put(BrowserContract.Passwords.ENCRYPTED_USERNAME, rec.username); + cv.put(BrowserContract.Passwords.ENCRYPTED_PASSWORD, rec.password); - cv.put(PasswordColumns.TIMES_USED, rec.timesUsed); - cv.put(PasswordColumns.TIME_LAST_USED, rec.timeLastUsed); + cv.put(BrowserContract.Passwords.TIMES_USED, rec.timesUsed); + cv.put(BrowserContract.Passwords.TIME_LAST_USED, rec.timeLastUsed); return cv; } diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java index b9439aedaa0b..798bed6c755e 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java @@ -61,9 +61,20 @@ public abstract class AndroidBrowserRepositoryDataAccessor { } protected abstract String[] getAllColumns(); + + /** + * Produce a ContentValues instance that represents the provided Record. + * + * @param record The Record to be converted. + * @return The ContentValues corresponding to record. + */ protected abstract ContentValues getContentValues(Record record); + protected abstract Uri getUri(); + /** + * Dump all the records in raw format. + */ public void dumpDB() { Cursor cur = null; try { @@ -83,36 +94,34 @@ public abstract class AndroidBrowserRepositoryDataAccessor { public void wipe() { Uri uri = getUri(); - Logger.info(LOG_TAG, "wiping: " + uri); + Logger.debug(LOG_TAG, "Wiping: " + uri); context.getContentResolver().delete(uri, null, null); } - + public void purgeDeleted() throws NullCursorException { String where = BrowserContract.SyncColumns.IS_DELETED + "= 1"; - Cursor cur = queryHelper.safeQuery(".purgeDeleted", GUID_COLUMNS, where, null, null); - - try { - if (!cur.moveToFirst()) { - return; - } - while (!cur.isAfterLast()) { - delete(RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID)); - cur.moveToNext(); - } - } finally { - cur.close(); - } + Uri uri = getUri(); + Logger.info(LOG_TAG, "Purging deleted from: " + uri); + context.getContentResolver().delete(uri, where, null); } - - protected void delete(String guid) { + + /** + * Remove matching records from the database entirely, i.e., do not set a + * deleted flag, delete entirely. + * + * @param guid + * The GUID of the record to be deleted. + * @return The number of records deleted. + */ + public int purgeGuid(String guid) { String where = BrowserContract.SyncColumns.GUID + " = ?"; String[] args = new String[] { guid }; int deleted = context.getContentResolver().delete(getUri(), where, args); - if (deleted == 1) { - return; + if (deleted != 1) { + Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " records for guid " + guid); } - Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " rows for guid " + guid); + return deleted; } public void update(String guid, Record newRecord) { @@ -132,21 +141,23 @@ public abstract class AndroidBrowserRepositoryDataAccessor { /** * Fetch all records. + *

                * The caller is responsible for closing the cursor. * - * @return A cursor. You *must* close this when you're done with it. + * @return A cursor. You must close this when you're done with it. * @throws NullCursorException */ public Cursor fetchAll() throws NullCursorException { return queryHelper.safeQuery(".fetchAll", getAllColumns(), null, null, null); } - + /** * Fetch GUIDs for records modified since the provided timestamp. + *

                * The caller is responsible for closing the cursor. * - * @param timestamp - * @return A cursor. You *must* close this when you're done with it. + * @param timestamp A timestamp in milliseconds. + * @return A cursor. You must close this when you're done with it. * @throws NullCursorException */ public Cursor getGUIDsSince(long timestamp) throws NullCursorException { @@ -158,10 +169,11 @@ public abstract class AndroidBrowserRepositoryDataAccessor { /** * Fetch records modified since the provided timestamp. + *

                * The caller is responsible for closing the cursor. * - * @param timestamp - * @return A cursor. You *must* close this when you're done with it. + * @param timestamp A timestamp in milliseconds. + * @return A cursor. You must close this when you're done with it. * @throws NullCursorException */ public Cursor fetchSince(long timestamp) throws NullCursorException { @@ -173,10 +185,11 @@ public abstract class AndroidBrowserRepositoryDataAccessor { /** * Fetch records for the provided GUIDs. + *

                * The caller is responsible for closing the cursor. * - * @param guids - * @return A cursor. You *must* close this when you're done with it. + * @param guids The GUIDs of the records to fetch. + * @return A cursor. You must close this when you're done with it. * @throws NullCursorException */ public Cursor fetch(String guids[]) throws NullCursorException { @@ -198,17 +211,6 @@ public abstract class AndroidBrowserRepositoryDataAccessor { return builder.toString(); } - public void delete(Record record) { - String where = BrowserContract.SyncColumns.GUID + " = ?"; - String[] args = new String[] { record.guid }; - - int deleted = context.getContentResolver().delete(getUri(), where, args); - if (deleted == 1) { - return; - } - Logger.warn(LOG_TAG, "Unexpectedly deleted " + deleted + " rows for guid " + record.guid); - } - public void updateByGuid(String guid, ContentValues cv) { String where = BrowserContract.SyncColumns.GUID + " = ?"; String[] args = new String[] { guid }; diff --git a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java index 505b18532f69..37e1f66ff8ea 100644 --- a/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java +++ b/mobile/android/base/sync/repositories/android/AndroidBrowserRepositorySession.java @@ -489,9 +489,9 @@ public abstract class AndroidBrowserRepositorySession extends StoreTrackingRepos } protected void storeRecordDeletion(final Record record) { - // TODO: we ought to mark the record as deleted rather than deleting it, + // TODO: we ought to mark the record as deleted rather than purging it, // in order to support syncing to multiple destinations. Bug 722607. - dbHelper.delete(record); // TODO: mm? + dbHelper.purgeGuid(record.guid); delegate.onRecordStoreSucceeded(record); } diff --git a/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java b/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java index 0ade8fce532f..d58d4811a351 100644 --- a/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java +++ b/mobile/android/base/sync/repositories/android/BrowserContractHelpers.java @@ -13,6 +13,12 @@ import org.mozilla.gecko.db.BrowserContract; import android.net.Uri; public class BrowserContractHelpers extends BrowserContract { + protected static Uri withSync(Uri u) { + return u.buildUpon() + .appendQueryParameter(PARAM_IS_SYNC, "true") + .build(); + } + protected static Uri withSyncAndDeleted(Uri u) { return u.buildUpon() .appendQueryParameter(PARAM_IS_SYNC, "true") @@ -30,9 +36,9 @@ public class BrowserContractHelpers extends BrowserContract { public static final Uri PASSWORDS_CONTENT_URI = null; /* public static final Uri PASSWORDS_CONTENT_URI = withSyncAndDeleted(Passwords.CONTENT_URI); - public static final Uri FORM_HISTORY_CONTENT_URI = withSyncAndDeleted(FormHistory.CONTENT_URI); - public static final Uri DELETED_FORM_HISTORY_CONTENT_URI = withSyncAndDeleted(DeletedFormHistory.CONTENT_URI); */ + public static final Uri FORM_HISTORY_CONTENT_URI = withSync(FormHistory.CONTENT_URI); + public static final Uri DELETED_FORM_HISTORY_CONTENT_URI = withSync(DeletedFormHistory.CONTENT_URI); public static final String[] PasswordColumns = new String[] { CommonColumns._ID, @@ -80,6 +86,23 @@ public class BrowserContractHelpers extends BrowserContract { Bookmarks.KEYWORD }; + + public static final String[] FormHistoryColumns = new String[] { + FormHistory.ID, + FormHistory.GUID, + FormHistory.FIELD_NAME, + FormHistory.VALUE, + FormHistory.TIMES_USED, + FormHistory.FIRST_USED, + FormHistory.LAST_USED + }; + + public static final String[] DeletedColumns = new String[] { + DeletedFormHistory.ID, + DeletedFormHistory.GUID, + DeletedFormHistory.TIME_DELETED + }; + // Mapping from Sync types to Fennec types. public static final String[] BOOKMARK_TYPE_CODE_TO_STRING = { // Observe omissions: "microsummary", "item". diff --git a/mobile/android/base/sync/repositories/android/RepoUtils.java b/mobile/android/base/sync/repositories/android/RepoUtils.java index b0a1480dabeb..f5cb290ceed0 100644 --- a/mobile/android/base/sync/repositories/android/RepoUtils.java +++ b/mobile/android/base/sync/repositories/android/RepoUtils.java @@ -146,7 +146,7 @@ public class RepoUtils { Logger.pii(LOG_TAG, "> URI: " + rec.histURI); } } catch (Exception e) { - Logger.debug(LOG_TAG, "Exception logging bookmark record " + rec, e); + Logger.debug(LOG_TAG, "Exception logging history record " + rec, e); } return rec; } @@ -154,10 +154,10 @@ public class RepoUtils { public static void logClient(ClientRecord rec) { if (Logger.logVerbose(LOG_TAG)) { Logger.trace(LOG_TAG, "Returning client record " + rec.guid + " (" + rec.androidID + ")"); - Logger.trace(LOG_TAG, "Client Name: " + rec.name); - Logger.trace(LOG_TAG, "Client Type: " + rec.type); + Logger.trace(LOG_TAG, "Client Name: " + rec.name); + Logger.trace(LOG_TAG, "Client Type: " + rec.type); Logger.trace(LOG_TAG, "Last Modified: " + rec.lastModified); - Logger.trace(LOG_TAG, "Deleted: " + rec.deleted); + Logger.trace(LOG_TAG, "Deleted: " + rec.deleted); } } @@ -217,19 +217,26 @@ public class RepoUtils { return " ".substring(0, i); } + private static String dashes(int i) { + return "-------------------------------------".substring(0, i); + } + public static void dumpCursor(Cursor cur) { + dumpCursor(cur, 18, "records"); + } + + public static void dumpCursor(Cursor cur, int columnWidth, String tag) { int originalPosition = cur.getPosition(); try { String[] columnNames = cur.getColumnNames(); int columnCount = cur.getColumnCount(); - // 12 chars each column. for (int i = 0; i < columnCount; ++i) { - System.out.print(fixedWidth(12, columnNames[i]) + " | "); + System.out.print(fixedWidth(columnWidth, columnNames[i]) + " | "); } - System.out.println(""); + System.out.println("(" + cur.getCount() + " " + tag + ")"); for (int i = 0; i < columnCount; ++i) { - System.out.print("------------" + " | "); + System.out.print(dashes(columnWidth) + " | "); } System.out.println(""); if (!cur.moveToFirst()) { @@ -238,15 +245,17 @@ public class RepoUtils { } cur.moveToFirst(); - while (cur.moveToNext()) { + while (!cur.isAfterLast()) { for (int i = 0; i < columnCount; ++i) { - System.out.print(fixedWidth(12, cur.getString(i)) + " | "); + System.out.print(fixedWidth(columnWidth, cur.getString(i)) + " | "); } System.out.println(""); + cur.moveToNext(); } - for (int i = 0; i < columnCount; ++i) { - System.out.print("---------------"); + for (int i = 0; i < columnCount-1; ++i) { + System.out.print(dashes(columnWidth + 3)); } + System.out.print(dashes(columnWidth + 3 - 1)); System.out.println(""); } finally { cur.moveToPosition(originalPosition); diff --git a/mobile/android/sync/java-sources.mn b/mobile/android/sync/java-sources.mn index be59c900fc9e..59a311d316a4 100644 --- a/mobile/android/sync/java-sources.mn +++ b/mobile/android/sync/java-sources.mn @@ -1 +1 @@ -sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CommandProcessor.java sync/CommandRunner.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/PasswordColumns.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java +sync/AlreadySyncingException.java sync/CollectionKeys.java sync/CommandProcessor.java sync/CommandRunner.java sync/CredentialsSource.java sync/crypto/CryptoException.java sync/crypto/CryptoInfo.java sync/crypto/HKDF.java sync/crypto/HMACVerificationException.java sync/crypto/KeyBundle.java sync/crypto/MissingCryptoInputException.java sync/crypto/NoKeyBundleException.java sync/CryptoRecord.java sync/DelayedWorkTracker.java sync/delegates/ClientsDataDelegate.java sync/delegates/FreshStartDelegate.java sync/delegates/GlobalSessionCallback.java sync/delegates/InfoCollectionsDelegate.java sync/delegates/KeyUploadDelegate.java sync/delegates/MetaGlobalDelegate.java sync/delegates/WipeServerDelegate.java sync/ExtendedJSONObject.java sync/GlobalSession.java sync/HTTPFailureException.java sync/InfoCollections.java sync/jpake/BigIntegerHelper.java sync/jpake/Gx3OrGx4IsZeroOrOneException.java sync/jpake/IncorrectZkpException.java sync/jpake/JPakeClient.java sync/jpake/JPakeCrypto.java sync/jpake/JPakeJson.java sync/jpake/JPakeNoActivePairingException.java sync/jpake/JPakeNumGenerator.java sync/jpake/JPakeNumGeneratorRandom.java sync/jpake/JPakeParty.java sync/jpake/JPakeRequest.java sync/jpake/JPakeRequestDelegate.java sync/jpake/JPakeResponse.java sync/jpake/stage/CompleteStage.java sync/jpake/stage/ComputeFinalStage.java sync/jpake/stage/ComputeKeyVerificationStage.java sync/jpake/stage/ComputeStepOneStage.java sync/jpake/stage/ComputeStepTwoStage.java sync/jpake/stage/DecryptDataStage.java sync/jpake/stage/GetChannelStage.java sync/jpake/stage/GetRequestStage.java sync/jpake/stage/JPakeStage.java sync/jpake/stage/PutRequestStage.java sync/jpake/stage/VerifyPairingStage.java sync/jpake/Zkp.java sync/KeyBundleProvider.java sync/Logger.java sync/MetaGlobal.java sync/MetaGlobalException.java sync/MetaGlobalMissingEnginesException.java sync/MetaGlobalNotSetException.java sync/middleware/Crypto5MiddlewareRepository.java sync/middleware/Crypto5MiddlewareRepositorySession.java sync/middleware/MiddlewareRepository.java sync/middleware/MiddlewareRepositorySession.java sync/net/BaseResource.java sync/net/CompletedEntity.java sync/net/ConnectionMonitorThread.java sync/net/HandleProgressException.java sync/net/Resource.java sync/net/ResourceDelegate.java sync/net/SyncResourceDelegate.java sync/net/SyncResponse.java sync/net/SyncStorageCollectionRequest.java sync/net/SyncStorageCollectionRequestDelegate.java sync/net/SyncStorageRecordRequest.java sync/net/SyncStorageRequest.java sync/net/SyncStorageRequestDelegate.java sync/net/SyncStorageRequestIncrementalDelegate.java sync/net/SyncStorageResponse.java sync/net/TLSSocketFactory.java sync/net/WBOCollectionRequestDelegate.java sync/net/WBORequestDelegate.java sync/NoCollectionKeysSetException.java sync/NodeAuthenticationException.java sync/NonArrayJSONException.java sync/NonObjectJSONException.java sync/NullClusterURLException.java sync/PrefsSource.java sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java sync/repositories/android/AndroidBrowserBookmarksRepository.java sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java sync/repositories/android/AndroidBrowserHistoryDataAccessor.java sync/repositories/android/AndroidBrowserHistoryDataExtender.java sync/repositories/android/AndroidBrowserHistoryRepository.java sync/repositories/android/AndroidBrowserHistoryRepositorySession.java sync/repositories/android/AndroidBrowserPasswordsDataAccessor.java sync/repositories/android/AndroidBrowserPasswordsRepository.java sync/repositories/android/AndroidBrowserPasswordsRepositorySession.java sync/repositories/android/AndroidBrowserRepository.java sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java sync/repositories/android/AndroidBrowserRepositorySession.java sync/repositories/android/BrowserContractHelpers.java sync/repositories/android/CachedSQLiteOpenHelper.java sync/repositories/android/ClientsDatabase.java sync/repositories/android/ClientsDatabaseAccessor.java sync/repositories/android/FennecTabsRepository.java sync/repositories/android/RepoUtils.java sync/repositories/BookmarkNeedsReparentingException.java sync/repositories/BookmarksRepository.java sync/repositories/ConstrainedServer11Repository.java sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionBeginDelegate.java sync/repositories/delegates/RepositorySessionCleanDelegate.java sync/repositories/delegates/RepositorySessionCreationDelegate.java sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java sync/repositories/delegates/RepositorySessionFinishDelegate.java sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java sync/repositories/delegates/RepositorySessionStoreDelegate.java sync/repositories/delegates/RepositorySessionWipeDelegate.java sync/repositories/domain/BookmarkRecord.java sync/repositories/domain/BookmarkRecordFactory.java sync/repositories/domain/ClientRecord.java sync/repositories/domain/ClientRecordFactory.java sync/repositories/domain/HistoryRecord.java sync/repositories/domain/HistoryRecordFactory.java sync/repositories/domain/PasswordRecord.java sync/repositories/domain/Record.java sync/repositories/domain/TabsRecord.java sync/repositories/HashSetStoreTracker.java sync/repositories/HistoryRepository.java sync/repositories/IdentityRecordFactory.java sync/repositories/InactiveSessionException.java sync/repositories/InvalidBookmarkTypeException.java sync/repositories/InvalidRequestException.java sync/repositories/InvalidSessionTransitionException.java sync/repositories/MultipleRecordsForGuidException.java sync/repositories/NoContentProviderException.java sync/repositories/NoGuidForIdException.java sync/repositories/NoStoreDelegateException.java sync/repositories/NullCursorException.java sync/repositories/ParentNotFoundException.java sync/repositories/ProfileDatabaseException.java sync/repositories/RecordFactory.java sync/repositories/RecordFilter.java sync/repositories/Repository.java sync/repositories/RepositorySession.java sync/repositories/RepositorySessionBundle.java sync/repositories/Server11Repository.java sync/repositories/Server11RepositorySession.java sync/repositories/StoreTracker.java sync/repositories/StoreTrackingRepositorySession.java sync/setup/activities/AccountActivity.java sync/setup/activities/SetupFailureActivity.java sync/setup/activities/SetupSuccessActivity.java sync/setup/activities/SetupSyncActivity.java sync/setup/Constants.java sync/setup/SyncAccounts.java sync/setup/SyncAuthenticatorService.java sync/stage/AndroidBrowserBookmarksServerSyncStage.java sync/stage/AndroidBrowserHistoryServerSyncStage.java sync/stage/CheckPreconditionsStage.java sync/stage/CompletedStage.java sync/stage/EnsureClusterURLStage.java sync/stage/EnsureKeysStage.java sync/stage/FennecTabsServerSyncStage.java sync/stage/FetchInfoCollectionsStage.java sync/stage/FetchMetaGlobalStage.java sync/stage/GlobalSyncStage.java sync/stage/NoSuchStageException.java sync/stage/NoSyncIDException.java sync/stage/ServerSyncStage.java sync/stage/SyncClientsEngineStage.java sync/StubActivity.java sync/syncadapter/SyncAdapter.java sync/syncadapter/SyncService.java sync/SyncConfiguration.java sync/SyncConfigurationException.java sync/SyncException.java sync/synchronizer/ConcurrentRecordConsumer.java sync/synchronizer/RecordConsumer.java sync/synchronizer/RecordsChannel.java sync/synchronizer/RecordsChannelDelegate.java sync/synchronizer/RecordsConsumerDelegate.java sync/synchronizer/SerialRecordConsumer.java sync/synchronizer/SessionNotBegunException.java sync/synchronizer/Synchronizer.java sync/synchronizer/SynchronizerDelegate.java sync/synchronizer/SynchronizerSession.java sync/synchronizer/SynchronizerSessionDelegate.java sync/synchronizer/UnbundleError.java sync/synchronizer/UnexpectedSessionException.java sync/SynchronizerConfiguration.java sync/SynchronizerConfigurations.java sync/ThreadPool.java sync/UnexpectedJSONException.java sync/UnknownSynchronizerConfigurationVersionException.java sync/Utils.java From 84ad760f65083fabfbecd0b307191cd720126d46 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Fri, 23 Mar 2012 11:03:48 -0700 Subject: [PATCH 034/109] Bug 738516 - Auth cache should be set for all requests, not just authenticated requests. r=msamuel --- mobile/android/base/sync/net/BaseResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/sync/net/BaseResource.java b/mobile/android/base/sync/net/BaseResource.java index b861b9a4d0f8..f2d97edb8893 100644 --- a/mobile/android/base/sync/net/BaseResource.java +++ b/mobile/android/base/sync/net/BaseResource.java @@ -117,8 +117,6 @@ public class BaseResource implements Resource { * @param credentials a string, "user:pass". */ private static void applyCredentials(String credentials, HttpUriRequest request, HttpContext context) { - addAuthCacheToContext(request, context); - Credentials creds = new UsernamePasswordCredentials(credentials); Header header = BasicScheme.authenticate(creds, "US-ASCII", false); request.addHeader(header); @@ -144,6 +142,8 @@ public class BaseResource implements Resource { BaseResource.applyCredentials(credentials, request, context); } + addAuthCacheToContext(request, context); + HttpParams params = client.getParams(); HttpConnectionParams.setConnectionTimeout(params, delegate.connectionTimeout()); HttpConnectionParams.setSoTimeout(params, delegate.socketTimeout()); From 05537a54d8543f16e88fe8c0c1be6d518e329a20 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Fri, 23 Mar 2012 11:11:14 -0700 Subject: [PATCH 035/109] Backed out changeset 29a7f9d3d4c4 (bug 730318) r=bustage --- content/base/src/nsObjectLoadingContent.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 3d5a88a51cf8..0e947ff7e842 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -563,9 +563,8 @@ nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* /*aParent nsIContent* /*aBindingParent*/, bool /*aCompileEventHandlers*/) { - if (aDocument) { + if (aDocument) return aDocument->AddPlugin(this); - } return NS_OK; } From 71163aacd755f41d2b1ce74dccc044b16e6d40db Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Fri, 23 Mar 2012 11:11:22 -0700 Subject: [PATCH 036/109] Backed out changeset c120dd831b3f (bug 734323) r=bustage --- mobile/android/chrome/content/browser.js | 41 ++++++------------------ 1 file changed, 10 insertions(+), 31 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 9fdc6bb10502..517ab7c22fe2 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1456,9 +1456,6 @@ function Tab(aURL, aParams) { this._zoom = 1.0; this.userScrollPos = { x: 0, y: 0 }; this.contentDocumentIsDisplayed = true; - this.clickToPlayPluginDoorhangerShown = false; - this.clickToPlayPluginsActivated = false; - this.loadEventProcessed = false; } Tab.prototype = { @@ -1766,7 +1763,6 @@ Tab.prototype = { } case "load": { - this.loadEventProcessed = true; // Show a plugin doorhanger if there are no clickable overlays showing let contentWindow = this.browser.contentWindow; let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) @@ -1889,20 +1885,14 @@ Tab.prototype = { case "PluginClickToPlay": { let plugin = aEvent.target; - - if (this.clickToPlayPluginsActivated) { - PluginHelper.playPlugin(plugin); + let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); + if (!overlay) return; - } // If the overlay is too small, hide the overlay and act like this // is a hidden plugin object - let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); - if (!overlay || PluginHelper.isTooSmall(plugin, overlay)) { - if (overlay) - overlay.style.visibility = "hidden"; - if (this.loadEventProcessed && !this.clickToPlayPluginDoorhangerShown) - PluginHelper.showDoorHanger(this); + if (PluginHelper.isTooSmall(plugin, overlay)) { + overlay.style.visibility = "hidden"; return; } @@ -1913,10 +1903,7 @@ Tab.prototype = { return; e.preventDefault(); } - let win = e.target.ownerDocument.defaultView.top; - let tab = BrowserApp.getTabForWindow(win); - tab.clickToPlayPluginsActivated = true; - PluginHelper.playAllPlugins(win); + PluginHelper.playAllPlugins(e.target.ownerDocument.defaultView); }, true); break; } @@ -1977,11 +1964,6 @@ Tab.prototype = { contentType = browser.contentDocument.contentType; } - // Reset state of click-to-play plugin notifications. - this.clickToPlayPluginDoorhangerShown = false; - this.clickToPlayPluginsActivated = false; - this.loadEventProcessed = false; - let message = { gecko: { type: "Content:LocationChange", @@ -3881,7 +3863,6 @@ var ClipboardHelper = { var PluginHelper = { showDoorHanger: function(aTab) { - aTab.clickToPlayPluginDoorhangerShown = true; let message = Strings.browser.GetStringFromName("clickToPlayPlugins.message"); let buttons = [ { @@ -3908,13 +3889,11 @@ var PluginHelper = { if (!plugins || !plugins.length) return; - plugins.forEach(this.playPlugin); - }, - - playPlugin: function(plugin) { - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (!objLoadingContent.activated) - objLoadingContent.playPlugin(); + for (let plugin of plugins) { + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (!objLoadingContent.activated) + objLoadingContent.playPlugin(); + } }, getPluginPreference: function getPluginPreference() { From e8d8a40761aed6548c795aea4df80e195f32d66d Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Fri, 23 Mar 2012 11:11:31 -0700 Subject: [PATCH 037/109] Backed out changeset 5f79a3dd45ff (bug 730318) r=bustage --- content/base/public/nsIDocument.h | 5 -- .../base/public/nsIObjectLoadingContent.idl | 8 +-- content/base/src/nsDocument.cpp | 50 +------------------ content/base/src/nsDocument.h | 13 ----- content/base/src/nsObjectLoadingContent.cpp | 46 +---------------- content/base/src/nsObjectLoadingContent.h | 10 ---- .../html/content/src/nsHTMLObjectElement.cpp | 6 --- .../content/src/nsHTMLSharedObjectElement.cpp | 6 --- dom/base/nsDOMWindowUtils.cpp | 25 ---------- dom/interfaces/base/nsIDOMWindowUtils.idl | 13 +---- js/src/jsapi.cpp | 2 +- js/xpconnect/public/Makefile.in | 1 - js/xpconnect/public/nsTArrayHelpers.h | 49 ------------------ 13 files changed, 6 insertions(+), 228 deletions(-) delete mode 100644 js/xpconnect/public/nsTArrayHelpers.h diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 17f9cff61331..d818dc949965 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -111,7 +111,6 @@ class imgIRequest; class nsISHEntry; class nsDOMNavigationTiming; class nsWindowSizes; -class nsIObjectLoadingContent; namespace mozilla { namespace css { @@ -1567,10 +1566,6 @@ public: // state is unlocked/false. virtual nsresult SetImageLockingState(bool aLocked) = 0; - virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin) = 0; - virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin) = 0; - virtual void GetPlugins(nsTArray& aPlugins) = 0; - virtual nsresult GetStateObject(nsIVariant** aResult) = 0; virtual nsDOMNavigationTiming* GetNavigationTiming() const = 0; diff --git a/content/base/public/nsIObjectLoadingContent.idl b/content/base/public/nsIObjectLoadingContent.idl index 1b0a1a7f470e..bf688caa76f9 100644 --- a/content/base/public/nsIObjectLoadingContent.idl +++ b/content/base/public/nsIObjectLoadingContent.idl @@ -52,7 +52,7 @@ interface nsIURI; /** * This interface represents a content node that loads objects. */ -[scriptable, uuid(fd56fda8-d3c3-4368-8cf3-67dbc992aec9)] +[scriptable, uuid(3FF07AB3-5BAC-4D98-9549-5BD15CCEBCD3)] interface nsIObjectLoadingContent : nsISupports { const unsigned long TYPE_LOADING = 0; @@ -125,12 +125,6 @@ interface nsIObjectLoadingContent : nsISupports */ void playPlugin(); - /** - * This attribute will return true if the plugin has been activated - * and false if the plugin is still in the click-to-play state. - */ - readonly attribute boolean activated; - [noscript] void stopPluginInstance(); [noscript] void syncStartPluginInstance(); diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index c530fd7ea978..9e8e28cc9429 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -1673,8 +1673,6 @@ nsDocument::~nsDocument() // unlocked state, and then clear the table. SetImageLockingState(false); mImageTracker.Clear(); - - mPlugins.Clear(); } NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument) @@ -2025,8 +2023,7 @@ nsDocument::Init() mScriptLoader = new nsScriptLoader(this); NS_ENSURE_TRUE(mScriptLoader, NS_ERROR_OUT_OF_MEMORY); - if (!mImageTracker.Init() || - !mPlugins.Init()) { + if (!mImageTracker.Init()) { return NS_ERROR_OUT_OF_MEMORY; } @@ -8357,51 +8354,6 @@ nsDocument::RemoveImage(imgIRequest* aImage) return rv; } -nsresult -nsDocument::AddPlugin(nsIObjectLoadingContent* aPlugin) -{ - MOZ_ASSERT(aPlugin); - if (!mPlugins.PutEntry(aPlugin)) { - return NS_ERROR_OUT_OF_MEMORY; - } - return NS_OK; -} - -void -nsDocument::RemovePlugin(nsIObjectLoadingContent* aPlugin) -{ - MOZ_ASSERT(aPlugin); - mPlugins.RemoveEntry(aPlugin); -} - -static bool -AllSubDocumentPluginEnum(nsIDocument* aDocument, void* userArg) -{ - nsTArray* plugins = - reinterpret_cast< nsTArray* >(userArg); - MOZ_ASSERT(plugins); - aDocument->GetPlugins(*plugins); - return true; -} - -static PLDHashOperator -AllPluginEnum(nsPtrHashKey* aPlugin, void* userArg) -{ - nsTArray* allPlugins = - reinterpret_cast< nsTArray* >(userArg); - MOZ_ASSERT(allPlugins); - allPlugins->AppendElement(aPlugin->GetKey()); - return PL_DHASH_NEXT; -} - -void -nsDocument::GetPlugins(nsTArray& aPlugins) -{ - aPlugins.SetCapacity(aPlugins.Length() + mPlugins.Count()); - mPlugins.EnumerateEntries(AllPluginEnum, &aPlugins); - EnumerateSubDocuments(AllSubDocumentPluginEnum, &aPlugins); -} - PLDHashOperator LockEnumerator(imgIRequest* aKey, PRUint32 aData, void* userArg) diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index e133084baddd..8655935d1193 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -934,16 +934,6 @@ public: virtual NS_HIDDEN_(nsresult) RemoveImage(imgIRequest* aImage); virtual NS_HIDDEN_(nsresult) SetImageLockingState(bool aLocked); - // AddPlugin adds a plugin-related element to mPlugins when the element is - // added to the tree. - virtual nsresult AddPlugin(nsIObjectLoadingContent* aPlugin); - // RemovePlugin removes a plugin-related element to mPlugins when the - // element is removed from the tree. - virtual void RemovePlugin(nsIObjectLoadingContent* aPlugin); - // GetPlugins returns the plugin-related elements from - // the frame and any subframes. - virtual void GetPlugins(nsTArray& aPlugins); - virtual nsresult GetStateObject(nsIVariant** aResult); virtual nsDOMNavigationTiming* GetNavigationTiming() const; @@ -1308,9 +1298,6 @@ private: // Tracking for images in the document. nsDataHashtable< nsPtrHashKey, PRUint32> mImageTracker; - // Tracking for plugins in the document. - nsTHashtable< nsPtrHashKey > mPlugins; - VisibilityState mVisibilityState; #ifdef DEBUG diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 0e947ff7e842..87ef4be73e21 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -115,18 +115,6 @@ static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc"); #include "mozilla/Preferences.h" -static bool gClickToPlayPlugins = false; - -static void -InitPrefCache() -{ - static bool initializedPrefCache = false; - if (!initializedPrefCache) { - mozilla::Preferences::AddBoolVarCache(&gClickToPlayPlugins, "plugins.click_to_play"); - } - initializedPrefCache = true; -} - class nsAsyncInstantiateEvent : public nsRunnable { public: nsObjectLoadingContent *mContent; @@ -558,25 +546,6 @@ bool nsObjectLoadingContent::IsPluginEnabledByExtension(nsIURI* uri, nsCString& return false; } -nsresult -nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* /*aParent*/, - nsIContent* /*aBindingParent*/, - bool /*aCompileEventHandlers*/) -{ - if (aDocument) - return aDocument->AddPlugin(this); - return NS_OK; -} - -void -nsObjectLoadingContent::UnbindFromTree(bool /*aDeep*/, bool /*aNullParent*/) -{ - nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); - MOZ_ASSERT(thisContent); - nsIDocument* ownerDoc = thisContent->OwnerDoc(); - ownerDoc->RemovePlugin(this); -} - nsObjectLoadingContent::nsObjectLoadingContent() : mPendingInstantiateEvent(nsnull) , mChannel(nsnull) @@ -585,14 +554,11 @@ nsObjectLoadingContent::nsObjectLoadingContent() , mUserDisabled(false) , mSuppressed(false) , mNetworkCreated(true) + // If plugins.click_to_play is false, plugins should always play + , mShouldPlay(!mozilla::Preferences::GetBool("plugins.click_to_play", false)) , mSrcStreamLoading(false) , mFallbackReason(ePluginOtherState) { - InitPrefCache(); - // If plugins.click_to_play is false, plugins should always play - mShouldPlay = !gClickToPlayPlugins; - // If plugins.click_to_play is true, track the activated state of plugins. - mActivated = !gClickToPlayPlugins; } nsObjectLoadingContent::~nsObjectLoadingContent() @@ -2240,13 +2206,5 @@ nsObjectLoadingContent::PlayPlugin() return NS_OK; mShouldPlay = true; - mActivated = true; return LoadObject(mURI, true, mContentType, true); } - -NS_IMETHODIMP -nsObjectLoadingContent::GetActivated(bool* aActivated) -{ - *aActivated = mActivated; - return NS_OK; -} diff --git a/content/base/src/nsObjectLoadingContent.h b/content/base/src/nsObjectLoadingContent.h index 8e24376f386d..720e9ca8bb95 100644 --- a/content/base/src/nsObjectLoadingContent.h +++ b/content/base/src/nsObjectLoadingContent.h @@ -244,12 +244,6 @@ class nsObjectLoadingContent : public nsImageLoadingContent static void DoStopPlugin(nsPluginInstanceOwner *aInstanceOwner, bool aDelayedStop); - nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, - nsIContent* aBindingParent, - bool aCompileEventHandler); - void UnbindFromTree(bool aDeep = true, - bool aNullParent = true); - private: void NotifyContentObjectWrapper(); @@ -405,10 +399,6 @@ class nsObjectLoadingContent : public nsImageLoadingContent // This is used for click-to-play plugins. bool mShouldPlay : 1; - // Used to keep track of whether or not a plugin has been played. - // This is used for click-to-play plugins. - bool mActivated : 1; - // Used to track when we might try to instantiate a plugin instance based on // a src data stream being delivered to this object. When this is true we don't // want plugin instance instantiation code to attempt to load src data again or diff --git a/content/html/content/src/nsHTMLObjectElement.cpp b/content/html/content/src/nsHTMLObjectElement.cpp index b9d8a79bfbad..a45e4de30355 100644 --- a/content/html/content/src/nsHTMLObjectElement.cpp +++ b/content/html/content/src/nsHTMLObjectElement.cpp @@ -265,11 +265,6 @@ nsHTMLObjectElement::BindToTree(nsIDocument *aDocument, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); - rv = nsObjectLoadingContent::BindToTree(aDocument, aParent, - aBindingParent, - aCompileEventHandlers); - NS_ENSURE_SUCCESS(rv, rv); - // If we already have all the children, start the load. if (mIsDoneAddingChildren) { void (nsHTMLObjectElement::*start)() = &nsHTMLObjectElement::StartObjectLoad; @@ -284,7 +279,6 @@ nsHTMLObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { RemovedFromDocument(); - nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent); } diff --git a/content/html/content/src/nsHTMLSharedObjectElement.cpp b/content/html/content/src/nsHTMLSharedObjectElement.cpp index 975f78211aab..177518aef2bf 100644 --- a/content/html/content/src/nsHTMLSharedObjectElement.cpp +++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp @@ -283,11 +283,6 @@ nsHTMLSharedObjectElement::BindToTree(nsIDocument *aDocument, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); - rv = nsObjectLoadingContent::BindToTree(aDocument, aParent, - aBindingParent, - aCompileEventHandlers); - NS_ENSURE_SUCCESS(rv, rv); - // If we already have all the children, start the load. if (mIsDoneAddingChildren) { void (nsHTMLSharedObjectElement::*start)() = @@ -303,7 +298,6 @@ nsHTMLSharedObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { RemovedFromDocument(); - nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); } diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 93519679afd3..6d81c6513541 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -51,7 +51,6 @@ #include "nsRefreshDriver.h" #include "nsDOMTouchEvent.h" #include "nsIDOMTouchEvent.h" -#include "nsObjectLoadingContent.h" #include "nsIScrollableFrame.h" @@ -77,7 +76,6 @@ #include "nsCSSProps.h" #include "nsDOMFile.h" #include "BasicLayers.h" -#include "nsTArrayHelpers.h" #if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK2) #include @@ -2232,26 +2230,3 @@ nsDOMWindowUtils::GetPaintingSuppressed(bool *aPaintingSuppressed) return NS_OK; } -NS_IMETHODIMP -nsDOMWindowUtils::GetPlugins(JSContext* cx, jsval* aPlugins) -{ - if (!IsUniversalXPConnectCapable()) { - return NS_ERROR_DOM_SECURITY_ERR; - } - - nsIDOMDocument* ddoc = mWindow->GetExtantDocument(); - - nsresult rv; - nsCOMPtr doc = do_QueryInterface(ddoc, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsTArray plugins; - doc->GetPlugins(plugins); - - JSObject* jsPlugins = nsnull; - rv = nsTArrayToJSArray(cx, plugins, &jsPlugins); - NS_ENSURE_SUCCESS(rv, rv); - - *aPlugins = OBJECT_TO_JSVAL(jsPlugins); - return NS_OK; -} diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index a86f14081ae0..b90c2b57e8b9 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -70,7 +70,7 @@ interface nsIDOMFile; interface nsIFile; interface nsIDOMTouch; -[scriptable, uuid(c7f303a1-4f7b-4d38-a192-c3f0e25dadb1)] +[scriptable, uuid(43feb172-30e1-4ff1-b021-004f973da516)] interface nsIDOMWindowUtils : nsISupports { /** @@ -1099,15 +1099,4 @@ interface nsIDOMWindowUtils : nsISupports { * otherwise. */ readonly attribute boolean paintingSuppressed; - - /** - * Returns an array of plugins on the page for opt-in activation. - * - * Cannot be accessed from unprivileged context (not content-accessible). - * Will throw a DOM security error if called without UniversalXPConnect - * privileges. - * - */ - [implicit_jscontext] - readonly attribute jsval plugins; }; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 93d99cddedc1..788f4261f452 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4104,7 +4104,7 @@ JS_SetElement(JSContext *cx, JSObject *obj, uint32_t index, jsval *vp) { AssertNoGC(cx); CHECK_REQUEST(cx); - assertSameCompartment(cx, obj, *vp); + assertSameCompartment(cx, obj); JSAutoResolveFlags rf(cx, JSRESOLVE_QUALIFIED | JSRESOLVE_ASSIGNING); return obj->setElement(cx, index, vp, false); } diff --git a/js/xpconnect/public/Makefile.in b/js/xpconnect/public/Makefile.in index ded8d99b62cd..ab1afea4b7b9 100644 --- a/js/xpconnect/public/Makefile.in +++ b/js/xpconnect/public/Makefile.in @@ -49,7 +49,6 @@ EXPORTS = \ nsAXPCNativeCallContext.h \ xpc_map_end.h \ nsAutoJSValHolder.h \ - nsTArrayHelpers.h \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/js/xpconnect/public/nsTArrayHelpers.h b/js/xpconnect/public/nsTArrayHelpers.h deleted file mode 100644 index 1a5be9ece7ee..000000000000 --- a/js/xpconnect/public/nsTArrayHelpers.h +++ /dev/null @@ -1,49 +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/. */ - -#ifndef __NSTARRAYHELPERS_H__ -#define __NSTARRAYHELPERS_H__ - -template -inline nsresult -nsTArrayToJSArray(JSContext* aCx, const nsTArray& aSourceArray, - JSObject** aResultArray) -{ - MOZ_ASSERT(aCx); - JSAutoRequest ar(aCx); - - JSObject* arrayObj = JS_NewArrayObject(aCx, aSourceArray.Length(), nsnull); - if (!arrayObj) { - NS_WARNING("JS_NewArrayObject failed!"); - return NS_ERROR_OUT_OF_MEMORY; - } - - JSObject* global = JS_GetGlobalForScopeChain(aCx); - MOZ_ASSERT(global); - - for (PRUint32 index = 0; index < aSourceArray.Length(); index++) { - nsCOMPtr obj; - nsresult rv = CallQueryInterface(aSourceArray[index], getter_AddRefs(obj)); - NS_ENSURE_SUCCESS(rv, rv); - - jsval wrappedVal; - rv = nsContentUtils::WrapNative(aCx, global, obj, &wrappedVal, nsnull, true); - NS_ENSURE_SUCCESS(rv, rv); - - if (!JS_SetElement(aCx, arrayObj, index, &wrappedVal)) { - NS_WARNING("JS_SetElement failed!"); - return NS_ERROR_FAILURE; - } - } - - if (!JS_FreezeObject(aCx, arrayObj)) { - NS_WARNING("JS_FreezeObject failed!"); - return NS_ERROR_FAILURE; - } - - *aResultArray = arrayObj; - return NS_OK; -} - -#endif /* __NSTARRAYHELPERS_H__ */ From 0a1864f287c265f01fa2c5755153ef59e0bf9a90 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Fri, 23 Mar 2012 11:11:40 -0700 Subject: [PATCH 038/109] Backed out changeset 62e3e0fc06c9 (bug 730318) r=bustage --- dom/telephony/Telephony.cpp | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp index 533b199d71c9..e5cf0c7aacbb 100644 --- a/dom/telephony/Telephony.cpp +++ b/dom/telephony/Telephony.cpp @@ -53,7 +53,6 @@ #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "SystemWorkerManager.h" -#include "nsTArrayHelpers.h" #include "CallEvent.h" #include "TelephonyCall.h" @@ -70,6 +69,53 @@ typedef nsAutoTArray TelephonyList; TelephonyList* gTelephonyList; +template +inline nsresult +nsTArrayToJSArray(JSContext* aCx, JSObject* aGlobal, + const nsTArray >& aSourceArray, + JSObject** aResultArray) +{ + NS_ASSERTION(aCx, "Null context!"); + NS_ASSERTION(aGlobal, "Null global!"); + + JSAutoRequest ar(aCx); + JSAutoEnterCompartment ac; + if (!ac.enter(aCx, aGlobal)) { + NS_WARNING("Failed to enter compartment!"); + return NS_ERROR_FAILURE; + } + + JSObject* arrayObj; + + if (aSourceArray.IsEmpty()) { + arrayObj = JS_NewArrayObject(aCx, 0, nsnull); + } else { + nsTArray valArray; + valArray.SetLength(aSourceArray.Length()); + + for (PRUint32 index = 0; index < valArray.Length(); index++) { + nsISupports* obj = aSourceArray[index]->ToISupports(); + nsresult rv = + nsContentUtils::WrapNative(aCx, aGlobal, obj, &valArray[index]); + NS_ENSURE_SUCCESS(rv, rv); + } + + arrayObj = JS_NewArrayObject(aCx, valArray.Length(), valArray.Elements()); + } + + if (!arrayObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // XXX This is not what Jonas wants. He wants it to be live. + if (!JS_FreezeObject(aCx, arrayObj)) { + return NS_ERROR_FAILURE; + } + + *aResultArray = arrayObj; + return NS_OK; +} + } // anonymous namespace Telephony::Telephony() @@ -306,7 +352,8 @@ Telephony::GetCalls(jsval* aCalls) NS_ENSURE_SUCCESS(rv, rv); if (sc) { rv = - nsTArrayToJSArray(mScriptContext->GetNativeContext(), mCalls, &calls); + nsTArrayToJSArray(sc->GetNativeContext(), + sc->GetNativeGlobal(), mCalls, &calls); NS_ENSURE_SUCCESS(rv, rv); if (!mRooted) { From ccadaa974bd8e15c38c1a235df3a462f72800164 Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Fri, 23 Mar 2012 11:11:50 -0700 Subject: [PATCH 039/109] Backed out changeset 1801abb2839a (bug 730318) r=bustage --- mobile/android/chrome/content/browser.js | 105 ++++++++++++++--------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 517ab7c22fe2..cf2f00c06c27 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1455,6 +1455,8 @@ function Tab(aURL, aParams) { this.create(aURL, aParams); this._zoom = 1.0; this.userScrollPos = { x: 0, y: 0 }; + this._pluginCount = 0; + this._pluginOverlayShowing = false; this.contentDocumentIsDisplayed = true; } @@ -1509,7 +1511,6 @@ Tab.prototype = { this.browser.sessionHistory.addSHistoryListener(this); this.browser.addEventListener("DOMContentLoaded", this, true); - this.browser.addEventListener("load", this, true); this.browser.addEventListener("DOMLinkAdded", this, true); this.browser.addEventListener("DOMTitleChanged", this, true); this.browser.addEventListener("DOMWindowClose", this, true); @@ -1517,6 +1518,8 @@ Tab.prototype = { this.browser.addEventListener("scroll", this, true); this.browser.addEventListener("MozScrolledAreaChanged", this, true); this.browser.addEventListener("PluginClickToPlay", this, true); + this.browser.addEventListener("pagehide", this, true); + this.browser.addEventListener("pageshow", this, true); Services.obs.addObserver(this, "before-first-paint", false); @@ -1555,7 +1558,6 @@ Tab.prototype = { this.browser.removeProgressListener(this); this.browser.removeEventListener("DOMContentLoaded", this, true); - this.browser.removeEventListener("load", this, true); this.browser.removeEventListener("DOMLinkAdded", this, true); this.browser.removeEventListener("DOMTitleChanged", this, true); this.browser.removeEventListener("DOMWindowClose", this, true); @@ -1563,6 +1565,8 @@ Tab.prototype = { this.browser.removeEventListener("scroll", this, true); this.browser.removeEventListener("PluginClickToPlay", this, true); this.browser.removeEventListener("MozScrolledAreaChanged", this, true); + this.browser.removeEventListener("pagehide", this, true); + this.browser.removeEventListener("pageshow", this, true); Services.obs.removeObserver(this, "before-first-paint"); @@ -1759,24 +1763,13 @@ Tab.prototype = { this.browser.removeEventListener("pagehide", listener, true); }.bind(this), true); } - break; - } - case "load": { - // Show a plugin doorhanger if there are no clickable overlays showing - let contentWindow = this.browser.contentWindow; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - // XXX not sure if we should enable plugins for the parent documents... - let plugins = cwu.plugins; - let isAnyPluginVisible = false; - for (let plugin of plugins) { - let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); - if (overlay && !PluginHelper.isTooSmall(plugin, overlay)) - isAnyPluginVisible = true; - } - if (plugins && plugins.length && !isAnyPluginVisible) + // Show a plugin doorhanger if there are plugins on the page but no + // clickable overlays showing (this doesn't work on pages loaded after + // back/forward navigation - see bug 719875) + if (this._pluginCount && !this._pluginOverlayShowing) PluginHelper.showDoorHanger(this); + break; } @@ -1884,6 +1877,10 @@ Tab.prototype = { } case "PluginClickToPlay": { + // Keep track of the number of plugins to know whether or not to show + // the hidden plugins doorhanger + this._pluginCount++; + let plugin = aEvent.target; let overlay = plugin.ownerDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); if (!overlay) @@ -1897,14 +1894,22 @@ Tab.prototype = { } // Add click to play listener to the overlay - overlay.addEventListener("click", function(e) { - if (e) { - if (!e.isTrusted) - return; - e.preventDefault(); - } - PluginHelper.playAllPlugins(e.target.ownerDocument.defaultView); - }, true); + overlay.addEventListener("click", (function(event) { + // Play all the plugin objects when the user clicks on one + PluginHelper.playAllPlugins(this, event); + }).bind(this), true); + + this._pluginOverlayShowing = true; + break; + } + + case "pagehide": { + // Check to make sure it's top-level pagehide + if (aEvent.target.defaultView == this.browser.contentWindow) { + // Reset plugin state when we leave the page + this._pluginCount = 0; + this._pluginOverlayShowing = false; + } break; } } @@ -3868,7 +3873,7 @@ var PluginHelper = { { label: Strings.browser.GetStringFromName("clickToPlayPlugins.yes"), callback: function() { - PluginHelper.playAllPlugins(aTab.browser.contentWindow); + PluginHelper.playAllPlugins(aTab); } }, { @@ -3881,19 +3886,41 @@ var PluginHelper = { NativeWindow.doorhanger.show(message, "ask-to-play-plugins", buttons, aTab.id); }, - playAllPlugins: function(aContentWindow) { - let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - // XXX not sure if we should enable plugins for the parent documents... - let plugins = cwu.plugins; - if (!plugins || !plugins.length) - return; - - for (let plugin of plugins) { - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (!objLoadingContent.activated) - objLoadingContent.playPlugin(); + playAllPlugins: function(aTab, aEvent) { + if (aEvent) { + if (!aEvent.isTrusted) + return; + aEvent.preventDefault(); } + + this._findAndPlayAllPlugins(aTab.browser.contentWindow); + }, + + // Helper function that recurses through sub-frames to find all plugin objects + _findAndPlayAllPlugins: function _findAndPlayAllPlugins(aWindow) { + let embeds = aWindow.document.getElementsByTagName("embed"); + for (let i = 0; i < embeds.length; i++) { + if (!embeds[i].hasAttribute("played")) + this._playPlugin(embeds[i]); + } + + let objects = aWindow.document.getElementsByTagName("object"); + for (let i = 0; i < objects.length; i++) { + if (!objects[i].hasAttribute("played")) + this._playPlugin(objects[i]); + } + + for (let i = 0; i < aWindow.frames.length; i++) { + this._findAndPlayAllPlugins(aWindow.frames[i]); + } + }, + + _playPlugin: function _playPlugin(aPlugin) { + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); + objLoadingContent.playPlugin(); + + // Set an attribute on the plugin object to avoid re-loading it + aPlugin.setAttribute("played", true); }, getPluginPreference: function getPluginPreference() { From a2a9c23ea0e8b2dc11780d37eda21632e6dfaecb Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 16 Mar 2012 14:08:03 -0400 Subject: [PATCH 040/109] Bug 734302 - Part 3: Add a menu item for toggling the profiler on mobile; r=BenWa,dougt --HG-- rename : mobile/android/base/resources/menu-v11/gecko_menu.xml => mobile/android/base/resources/menu-v11/gecko_menu.xml.in rename : mobile/android/base/resources/menu/gecko_menu.xml => mobile/android/base/resources/menu/gecko_menu.xml.in --- b2g/installer/package-manifest.in | 1 + mobile/android/base/App.java.in | 11 +++++++++++ mobile/android/base/Makefile.in | 10 ++++------ .../menu-v11/{gecko_menu.xml => gecko_menu.xml.in} | 5 +++++ .../menu/{gecko_menu.xml => gecko_menu.xml.in} | 5 +++++ mobile/android/base/strings.xml.in | 3 +++ mobile/android/chrome/content/browser.js | 9 +++++++++ mobile/android/installer/package-manifest.in | 1 + tools/profiler/TableTicker.cpp | 6 ------ 9 files changed, 39 insertions(+), 12 deletions(-) rename mobile/android/base/resources/menu-v11/{gecko_menu.xml => gecko_menu.xml.in} (91%) rename mobile/android/base/resources/menu/{gecko_menu.xml => gecko_menu.xml.in} (91%) diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index ee1a2ee0e458..e9a546352fe9 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -236,6 +236,7 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt +@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/mobile/android/base/App.java.in b/mobile/android/base/App.java.in index c4fdfa33452e..82ee5631714c 100644 --- a/mobile/android/base/App.java.in +++ b/mobile/android/base/App.java.in @@ -65,5 +65,16 @@ public class App extends GeckoApp { return "Redirector/@MOZ_APP_VERSION@ (Android; rv:@MOZ_APP_VERSION@)"; return getDefaultUAString(); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { +#ifdef MOZ_PROFILING + if (item.getItemId() == R.id.toggle_profiling) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ToggleProfiling", null)); + return true; + } +#endif + return super.onOptionsItemSelected(item); + } }; diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 95c49d3cda4e..2905ebe13455 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -174,6 +174,8 @@ FENNEC_PP_JAVA_FILES = \ FENNEC_PP_XML_FILES = \ res/layout/abouthome_content.xml \ + res/menu/gecko_menu.xml \ + res/menu-v11/gecko_menu.xml \ $(NULL) @@ -530,11 +532,6 @@ RES_COLOR = \ RES_MENU = \ res/menu/awesomebar_contextmenu.xml \ - res/menu/gecko_menu.xml \ - $(NULL) - -RES_MENU_V11 = \ - res/menu-v11/gecko_menu.xml \ $(NULL) JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar @@ -597,7 +594,7 @@ MOZ_ANDROID_DRAWABLES += \ MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' '; fi) -RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) $(RES_MENU_V11) +RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) RES_DIRS= \ res/layout \ @@ -623,6 +620,7 @@ RES_DIRS= \ res/drawable-land-xhdpi-v14 \ res/color \ res/menu \ + res/menu-v11 \ $(NULL) diff --git a/mobile/android/base/resources/menu-v11/gecko_menu.xml b/mobile/android/base/resources/menu-v11/gecko_menu.xml.in similarity index 91% rename from mobile/android/base/resources/menu-v11/gecko_menu.xml rename to mobile/android/base/resources/menu-v11/gecko_menu.xml.in index 68aed8284292..da6e285ecc81 100644 --- a/mobile/android/base/resources/menu-v11/gecko_menu.xml +++ b/mobile/android/base/resources/menu-v11/gecko_menu.xml.in @@ -37,6 +37,11 @@ +#ifdef MOZ_PROFILING + +#endif + diff --git a/mobile/android/base/resources/menu/gecko_menu.xml b/mobile/android/base/resources/menu/gecko_menu.xml.in similarity index 91% rename from mobile/android/base/resources/menu/gecko_menu.xml rename to mobile/android/base/resources/menu/gecko_menu.xml.in index e009c24c84f5..6efa3b697a58 100644 --- a/mobile/android/base/resources/menu/gecko_menu.xml +++ b/mobile/android/base/resources/menu/gecko_menu.xml.in @@ -38,6 +38,11 @@ +#ifdef MOZ_PROFILING + +#endif + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 59436f06da6a..eb364ffc021d 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -88,6 +88,9 @@ &addons; &downloads; &char_encoding; + + Toggle Profiling &site_settings_title; &site_settings_cancel; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index cf2f00c06c27..b287e8232f00 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -193,6 +193,7 @@ var BrowserApp = { Services.obs.addObserver(this, "Viewport:Change", false); Services.obs.addObserver(this, "Passwords:Init", false); Services.obs.addObserver(this, "FormHistory:Init", false); + Services.obs.addObserver(this, "ToggleProfiling", false); Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); @@ -952,6 +953,14 @@ var BrowserApp = { Services.obs.removeObserver(this, "FormHistory:Init", false); } else if (aTopic == "sessionstore-state-purge-complete") { sendMessageToJava({ gecko: { type: "Session:StatePurged" }}); + } else if (aTopic == "ToggleProfiling") { + let profiler = Cc["@mozilla.org/tools/profiler;1"]. + getService(Ci.nsIProfiler); + if (profiler.IsActive()) { + profiler.StopProfiler(); + } else { + profiler.StartProfiler(100000, 25, ["stackwalk"], 1); + } } }, diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 83ecf4d465bb..14ce81e45d8a 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -231,6 +231,7 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt +@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index ed07d33d8c06..a205f611539e 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -353,13 +353,7 @@ class TableTicker: public Sampler { , mPrimaryThreadProfile(aEntrySize, aStack) , mSaveRequested(false) { -#if defined(USE_LIBUNWIND) && defined(ANDROID) - // We don't have the Gecko Profiler add-on on Android, but we know that - // libunwind is available, so we can always walk the stacks. - mUseStackWalk = true; -#else mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); -#endif //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); From ce74f02c9f1d6f97bb33f35da9fb24bd51f348e1 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 16 Mar 2012 14:08:03 -0400 Subject: [PATCH 041/109] Bug 734302 - Part 3: Add a menu item for toggling the profiler on mobile; r=BenWa,dougt --HG-- rename : mobile/android/base/resources/menu-v11/gecko_menu.xml => mobile/android/base/resources/menu-v11/gecko_menu.xml.in rename : mobile/android/base/resources/menu/gecko_menu.xml => mobile/android/base/resources/menu/gecko_menu.xml.in --- b2g/installer/package-manifest.in | 1 + mobile/android/base/App.java.in | 14 ++++++++++++++ mobile/android/base/Makefile.in | 10 ++++------ .../menu-v11/{gecko_menu.xml => gecko_menu.xml.in} | 5 +++++ .../menu/{gecko_menu.xml => gecko_menu.xml.in} | 5 +++++ mobile/android/base/strings.xml.in | 3 +++ mobile/android/chrome/content/browser.js | 9 +++++++++ mobile/android/installer/package-manifest.in | 1 + tools/profiler/TableTicker.cpp | 6 ------ 9 files changed, 42 insertions(+), 12 deletions(-) rename mobile/android/base/resources/menu-v11/{gecko_menu.xml => gecko_menu.xml.in} (91%) rename mobile/android/base/resources/menu/{gecko_menu.xml => gecko_menu.xml.in} (91%) diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index ee1a2ee0e458..e9a546352fe9 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -236,6 +236,7 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt +@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/mobile/android/base/App.java.in b/mobile/android/base/App.java.in index c4fdfa33452e..3a96303f5455 100644 --- a/mobile/android/base/App.java.in +++ b/mobile/android/base/App.java.in @@ -40,6 +40,9 @@ package @ANDROID_PACKAGE_NAME@; import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.R; +import android.view.MenuItem; public class App extends GeckoApp { public String getPackageName() { @@ -65,5 +68,16 @@ public class App extends GeckoApp { return "Redirector/@MOZ_APP_VERSION@ (Android; rv:@MOZ_APP_VERSION@)"; return getDefaultUAString(); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { +#ifdef MOZ_PROFILING + if (item.getItemId() == R.id.toggle_profiling) { + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ToggleProfiling", null)); + return true; + } +#endif + return super.onOptionsItemSelected(item); + } }; diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index f5f3b42dd927..61aeb786d98b 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -175,6 +175,8 @@ FENNEC_PP_JAVA_FILES = \ FENNEC_PP_XML_FILES = \ res/layout/abouthome_content.xml \ + res/menu/gecko_menu.xml \ + res/menu-v11/gecko_menu.xml \ $(NULL) @@ -531,11 +533,6 @@ RES_COLOR = \ RES_MENU = \ res/menu/awesomebar_contextmenu.xml \ - res/menu/gecko_menu.xml \ - $(NULL) - -RES_MENU_V11 = \ - res/menu-v11/gecko_menu.xml \ $(NULL) JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar @@ -598,7 +595,7 @@ MOZ_ANDROID_DRAWABLES += \ MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' '; fi) -RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) $(RES_MENU_V11) +RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) RES_DIRS= \ res/layout \ @@ -624,6 +621,7 @@ RES_DIRS= \ res/drawable-land-xhdpi-v14 \ res/color \ res/menu \ + res/menu-v11 \ $(NULL) diff --git a/mobile/android/base/resources/menu-v11/gecko_menu.xml b/mobile/android/base/resources/menu-v11/gecko_menu.xml.in similarity index 91% rename from mobile/android/base/resources/menu-v11/gecko_menu.xml rename to mobile/android/base/resources/menu-v11/gecko_menu.xml.in index 68aed8284292..da6e285ecc81 100644 --- a/mobile/android/base/resources/menu-v11/gecko_menu.xml +++ b/mobile/android/base/resources/menu-v11/gecko_menu.xml.in @@ -37,6 +37,11 @@ +#ifdef MOZ_PROFILING + +#endif + diff --git a/mobile/android/base/resources/menu/gecko_menu.xml b/mobile/android/base/resources/menu/gecko_menu.xml.in similarity index 91% rename from mobile/android/base/resources/menu/gecko_menu.xml rename to mobile/android/base/resources/menu/gecko_menu.xml.in index e009c24c84f5..6efa3b697a58 100644 --- a/mobile/android/base/resources/menu/gecko_menu.xml +++ b/mobile/android/base/resources/menu/gecko_menu.xml.in @@ -38,6 +38,11 @@ +#ifdef MOZ_PROFILING + +#endif + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 59436f06da6a..eb364ffc021d 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -88,6 +88,9 @@ &addons; &downloads; &char_encoding; + + Toggle Profiling &site_settings_title; &site_settings_cancel; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index ed469856610f..d8595527b97b 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -193,6 +193,7 @@ var BrowserApp = { Services.obs.addObserver(this, "Viewport:Change", false); Services.obs.addObserver(this, "Passwords:Init", false); Services.obs.addObserver(this, "FormHistory:Init", false); + Services.obs.addObserver(this, "ToggleProfiling", false); Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); @@ -952,6 +953,14 @@ var BrowserApp = { Services.obs.removeObserver(this, "FormHistory:Init", false); } else if (aTopic == "sessionstore-state-purge-complete") { sendMessageToJava({ gecko: { type: "Session:StatePurged" }}); + } else if (aTopic == "ToggleProfiling") { + let profiler = Cc["@mozilla.org/tools/profiler;1"]. + getService(Ci.nsIProfiler); + if (profiler.IsActive()) { + profiler.StopProfiler(); + } else { + profiler.StartProfiler(100000, 25, ["stackwalk"], 1); + } } }, diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 83ecf4d465bb..14ce81e45d8a 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -231,6 +231,7 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt +@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 973a2793b834..2fe06cc7d099 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -338,13 +338,7 @@ class TableTicker: public Sampler { , mPrimaryThreadProfile(aEntrySize, aStack) , mSaveRequested(false) { -#if defined(USE_LIBUNWIND) && defined(ANDROID) - // We don't have the Gecko Profiler add-on on Android, but we know that - // libunwind is available, so we can always walk the stacks. - mUseStackWalk = true; -#else mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); -#endif //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); From bed9c464833e21aefbf30fac90cd6cc64a84c3bb Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Fri, 23 Mar 2012 11:17:33 -0700 Subject: [PATCH 042/109] Bug 738705: Remove dead helper-class CompareByContentOrderComparator. r=bz --- layout/generic/nsFrameList.cpp | 39 ---------------------------------- 1 file changed, 39 deletions(-) diff --git a/layout/generic/nsFrameList.cpp b/layout/generic/nsFrameList.cpp index d24933c38cb6..5d23cf62982b 100644 --- a/layout/generic/nsFrameList.cpp +++ b/layout/generic/nsFrameList.cpp @@ -374,45 +374,6 @@ nsFrameList::GetLength() const return count; } -static int CompareByContentOrder(const nsIFrame* aF1, const nsIFrame* aF2) -{ - if (aF1->GetContent() != aF2->GetContent()) { - return nsLayoutUtils::CompareTreePosition(aF1->GetContent(), aF2->GetContent()); - } - - if (aF1 == aF2) { - return 0; - } - - const nsIFrame* f; - for (f = aF2; f; f = f->GetPrevInFlow()) { - if (f == aF1) { - // f1 comes before f2 in the flow - return -1; - } - } - for (f = aF1; f; f = f->GetPrevInFlow()) { - if (f == aF2) { - // f1 comes after f2 in the flow - return 1; - } - } - - NS_ASSERTION(false, "Frames for same content but not in relative flow order"); - return 0; -} - -class CompareByContentOrderComparator -{ - public: - bool Equals(const nsIFrame* aA, const nsIFrame* aB) const { - return aA == aB; - } - bool LessThan(const nsIFrame* aA, const nsIFrame* aB) const { - return CompareByContentOrder(aA, aB) < 0; - } -}; - void nsFrameList::ApplySetParent(nsIFrame* aParent) const { From 444323eb38a681fb60511cd925ac136992aa62fe Mon Sep 17 00:00:00 2001 From: Edwin Flores Date: Fri, 23 Mar 2012 11:21:57 -0700 Subject: [PATCH 043/109] Bug 738392 - Fix plugins not rendering inside CSS transform on Mac OSX r=roc --- layout/base/nsPresContext.cpp | 2 +- layout/generic/nsObjectFrame.cpp | 10 ++++++++++ layout/generic/nsObjectFrame.h | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 17071217919d..85ef9140dc1b 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -2487,7 +2487,7 @@ RecoverPluginGeometry(nsDisplayListBuilder* aBuilder, aClosure->mAffectedPlugins.GetEntry(f); // Windowed plugins in transforms are always ignored, we don't // create configurations for them - if (entry && (!aInTransform || !f->GetWidget())) { + if (entry && (!aInTransform || f->PaintedByGecko())) { displayPlugin->GetWidgetConfiguration(aBuilder, aClosure->mOutputConfigurations); // we've dealt with this plugin now diff --git a/layout/generic/nsObjectFrame.cpp b/layout/generic/nsObjectFrame.cpp index b39602ea3bbe..5d6aede91109 100644 --- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -2165,4 +2165,14 @@ NS_NewObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) return new (aPresShell) nsObjectFrame(aContext); } +bool +nsObjectFrame::PaintedByGecko() +{ +#ifdef XP_MACOSX + return true; +#else + return !mWidget; +#endif +} + NS_IMPL_FRAMEARENA_HELPERS(nsObjectFrame) diff --git a/layout/generic/nsObjectFrame.h b/layout/generic/nsObjectFrame.h index 29a153b83835..c7f1342a2ded 100644 --- a/layout/generic/nsObjectFrame.h +++ b/layout/generic/nsObjectFrame.h @@ -192,6 +192,8 @@ public: */ static void EndSwapDocShells(nsIContent* aContent, void*); + bool PaintedByGecko(); + nsIWidget* GetWidget() { return mWidget; } /** From abf3010b1feca1a43586ed308f004f3204f0f003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hub=20Figui=C3=A8re?= Date: Fri, 23 Mar 2012 11:54:55 -0700 Subject: [PATCH 044/109] Bug 718627 - Part 1: Autocomplete is to be ignored. Use TextField directly. r=surkov --- accessible/src/mac/mozTextAccessible.h | 10 --- accessible/src/mac/mozTextAccessible.mm | 99 +------------------------ accessible/src/mac/nsAccessibleWrap.mm | 3 - accessible/src/mac/nsRoleMap.h | 2 +- 4 files changed, 3 insertions(+), 111 deletions(-) diff --git a/accessible/src/mac/mozTextAccessible.h b/accessible/src/mac/mozTextAccessible.h index 65c662cbd6a3..bc66b059b550 100644 --- a/accessible/src/mac/mozTextAccessible.h +++ b/accessible/src/mac/mozTextAccessible.h @@ -10,13 +10,3 @@ nsIAccessibleEditableText *mGeckoEditableTextAccessible; // strong } @end - -/* A combobox (in the mac world) is a textfield with an associated menu, for example - the location bar. */ -@interface mozComboboxAccessible : mozTextAccessible -// equivalent to pressing return key in this textfield. -- (void)confirm; - -// shows the menu for this combobox. -- (void)showMenu; -@end diff --git a/accessible/src/mac/mozTextAccessible.mm b/accessible/src/mac/mozTextAccessible.mm index e236f8e0bcfc..c4649a1c9fde 100644 --- a/accessible/src/mac/mozTextAccessible.mm +++ b/accessible/src/mac/mozTextAccessible.mm @@ -1,3 +1,5 @@ +/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + #include "nsAccessibleWrap.h" #include "nsCocoaUtils.h" @@ -250,100 +252,3 @@ using namespace mozilla::a11y; } @end - -@implementation mozComboboxAccessible - -- (NSArray*)accessibilityAttributeNames -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - static NSArray *supportedAttributes = nil; - if (!supportedAttributes) { - // standard attributes that are shared and supported by all generic elements. - supportedAttributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required - NSAccessibilityRoleAttribute, // required - NSAccessibilityTitleAttribute, - NSAccessibilityValueAttribute, // required - NSAccessibilityHelpAttribute, - NSAccessibilityRoleDescriptionAttribute, - NSAccessibilityPositionAttribute, // required - NSAccessibilitySizeAttribute, // required - NSAccessibilityWindowAttribute, // required - NSAccessibilityFocusedAttribute, // required - NSAccessibilityEnabledAttribute, // required - NSAccessibilityChildrenAttribute, // required - NSAccessibilityHelpAttribute, - // NSAccessibilityExpandedAttribute, // required - NSAccessibilityTopLevelUIElementAttribute, // required - NSAccessibilityDescriptionAttribute, // required - /* text-specific attributes */ - NSAccessibilitySelectedTextAttribute, // required - NSAccessibilitySelectedTextRangeAttribute, // required - NSAccessibilityNumberOfCharactersAttribute, // required - // TODO: NSAccessibilityVisibleCharacterRangeAttribute, // required - // TODO: NSAccessibilityInsertionPointLineNumberAttribute -#if DEBUG - @"AXMozDescription", -#endif - nil]; - } - return supportedAttributes; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (NSArray *)accessibilityActionNames -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([self isEnabled]) { - return [NSArray arrayWithObjects:NSAccessibilityConfirmAction, - NSAccessibilityShowMenuAction, - nil]; - } - return nil; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (NSString *)accessibilityActionDescription:(NSString *)action -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([action isEqualToString:NSAccessibilityShowMenuAction]) - return @"show menu"; - if ([action isEqualToString:NSAccessibilityConfirmAction]) - return @"confirm"; - - return [super accessibilityActionDescription:action]; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (void)accessibilityPerformAction:(NSString *)action -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - // both the ShowMenu and Click action do the same thing. - if ([self isEnabled]) { - if ([action isEqualToString:NSAccessibilityShowMenuAction]) - [self showMenu]; - if ([action isEqualToString:NSAccessibilityConfirmAction]) - [self confirm]; - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -- (void)showMenu -{ - // currently unimplemented. waiting for support in bug 363697 -} - -- (void)confirm -{ - // should be the same as pressing enter/return in this textfield. - // not yet implemented -} - -@end diff --git a/accessible/src/mac/nsAccessibleWrap.mm b/accessible/src/mac/nsAccessibleWrap.mm index ad45f36cbdc5..08c94b2cf177 100644 --- a/accessible/src/mac/nsAccessibleWrap.mm +++ b/accessible/src/mac/nsAccessibleWrap.mm @@ -110,9 +110,6 @@ nsAccessibleWrap::GetNativeType () case roles::CHECKBUTTON: return [mozCheckboxAccessible class]; - case roles::AUTOCOMPLETE: - return [mozComboboxAccessible class]; - case roles::HEADING: return [mozHeadingAccessible class]; diff --git a/accessible/src/mac/nsRoleMap.h b/accessible/src/mac/nsRoleMap.h index 77a445f4f96f..a8102384fe6b 100644 --- a/accessible/src/mac/nsRoleMap.h +++ b/accessible/src/mac/nsRoleMap.h @@ -142,7 +142,7 @@ static const NSString* AXRoles [] = { NSAccessibilityGroupRole, // ROLE_FOOTER NSAccessibilityGroupRole, // ROLE_PARAGRAPH @"AXRuler", // ROLE_RULER. 10.4+ only, so we re-define the constant. - NSAccessibilityComboBoxRole, // ROLE_AUTOCOMPLETE + NSAccessibilityUnknownRole, // ROLE_AUTOCOMPLETE NSAccessibilityTextFieldRole, // ROLE_EDITBAR NSAccessibilityTextFieldRole, // ROLE_ENTRY NSAccessibilityStaticTextRole, // ROLE_CAPTION From 5b25a7d5f08d3dc8af830c0fad019152ff43b70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hub=20Figui=C3=A8re?= Date: Fri, 23 Mar 2012 11:54:55 -0700 Subject: [PATCH 045/109] Bug 718627 - Part 2: Expose CaretLineNumber() and GetTextBounds() from nsHyperTextAccessible. r=surkov --- accessible/src/html/nsHyperTextAccessible.cpp | 4 ++-- accessible/src/html/nsHyperTextAccessible.h | 23 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/accessible/src/html/nsHyperTextAccessible.cpp b/accessible/src/html/nsHyperTextAccessible.cpp index 6870e56a1206..2f91741be344 100644 --- a/accessible/src/html/nsHyperTextAccessible.cpp +++ b/accessible/src/html/nsHyperTextAccessible.cpp @@ -1224,7 +1224,7 @@ nsHyperTextAccessible::GetAttributesInternal(nsIPersistentProperties *aAttribute } if (FocusMgr()->IsFocused(this)) { - PRInt32 lineNumber = GetCaretLineNumber(); + PRInt32 lineNumber = CaretLineNumber(); if (lineNumber >= 1) { nsAutoString strLineNumber; strLineNumber.AppendInt(lineNumber); @@ -1680,7 +1680,7 @@ nsHyperTextAccessible::GetCaretOffset(PRInt32 *aCaretOffset) } PRInt32 -nsHyperTextAccessible::GetCaretLineNumber() +nsHyperTextAccessible::CaretLineNumber() { // Provide the line number for the caret, relative to the // currently focused node. Use a 1-based index diff --git a/accessible/src/html/nsHyperTextAccessible.h b/accessible/src/html/nsHyperTextAccessible.h index 8c42d12d7a60..c64e7af05417 100644 --- a/accessible/src/html/nsHyperTextAccessible.h +++ b/accessible/src/html/nsHyperTextAccessible.h @@ -264,6 +264,22 @@ public: return GetChildAt(GetChildIndexAtOffset(aOffset)); } + /** + * Return the bounds of the text between given start and end offset. + */ + nsIntRect GetTextBounds(PRInt32 aStartOffset, PRInt32 aEndOffset) + { + nsIntRect bounds; + GetPosAndText(aStartOffset, aEndOffset, nsnull, nsnull, &bounds); + return bounds; + } + + /** + * Provide the line number for the caret. + * @return 1-based index for the line number with the caret + */ + PRInt32 CaretLineNumber(); + ////////////////////////////////////////////////////////////////////////////// // EditableTextAccessible @@ -373,13 +389,6 @@ protected: nsresult SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos); - /** - * Provide the line number for the caret, relative to the - * current DOM node. - * @return 1-based index for the line number with the caret - */ - PRInt32 GetCaretLineNumber(); - // Helpers nsresult GetDOMPointByFrameOffset(nsIFrame *aFrame, PRInt32 aOffset, nsIAccessible *aAccessible, From a1e57fd3cb06482992a14b7c65b8be35193f3fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hub=20Figui=C3=A8re?= Date: Fri, 23 Mar 2012 11:54:55 -0700 Subject: [PATCH 046/109] Bug 718627 - Part 3: Make helper GetObjectOrRepresentedView() public in mozAccessible.h. r=surkov --- accessible/src/mac/mozAccessible.h | 12 ++++++++++++ accessible/src/mac/mozAccessible.mm | 15 --------------- accessible/src/mac/mozTextAccessible.mm | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/accessible/src/mac/mozAccessible.h b/accessible/src/mac/mozAccessible.h index e803fd33bf0d..ae5e0392e39c 100644 --- a/accessible/src/mac/mozAccessible.h +++ b/accessible/src/mac/mozAccessible.h @@ -44,6 +44,18 @@ @class mozRootAccessible; +/** + * All mozAccessibles are either abstract objects (that correspond to XUL + * widgets, HTML frames, etc) or are attached to a certain view; for example + * a document view. When we hand an object off to an AT, we always want + * to give it the represented view, in the latter case. + */ +inline id +GetObjectOrRepresentedView(id aObject) +{ + return [aObject hasRepresentedView] ? [aObject representedView] : aObject; +} + @interface mozAccessible : NSObject { /** diff --git a/accessible/src/mac/mozAccessible.mm b/accessible/src/mac/mozAccessible.mm index cfdee860e5e9..d7f4d2579136 100644 --- a/accessible/src/mac/mozAccessible.mm +++ b/accessible/src/mac/mozAccessible.mm @@ -71,21 +71,6 @@ ConvertCocoaToGeckoPoint(NSPoint &aInPoint, nsPoint &aOutPoint) aOutPoint.MoveTo ((nscoord)aInPoint.x, (nscoord)(mainScreenHeight - aInPoint.y)); } -// all mozAccessibles are either abstract objects (that correspond to XUL widgets, HTML frames, etc) or are -// attached to a certain view; for example a document view. when we hand an object off to an AT, we always want -// to give it the represented view, in the latter case. -static inline id -GetObjectOrRepresentedView(id anObject) -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([anObject hasRepresentedView]) - return [anObject representedView]; - return anObject; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - // returns the passed in object if it is not ignored. if it's ignored, will return // the first unignored ancestor. static inline id diff --git a/accessible/src/mac/mozTextAccessible.mm b/accessible/src/mac/mozTextAccessible.mm index c4649a1c9fde..959aac715254 100644 --- a/accessible/src/mac/mozTextAccessible.mm +++ b/accessible/src/mac/mozTextAccessible.mm @@ -245,7 +245,7 @@ using namespace mozilla::a11y; { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - NSAccessibilityPostNotification([self hasRepresentedView] ? [self representedView] : self, + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), NSAccessibilityValueChangedNotification); NS_OBJC_END_TRY_ABORT_BLOCK; From 196b2bd0643bc1fc0ba2ab50c68060c4d9941c5a Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Fri, 23 Mar 2012 15:01:14 -0400 Subject: [PATCH 047/109] Backout changeset 11b6f37603ce (bug 734302) for Android bustage --- b2g/installer/package-manifest.in | 1 - mobile/android/base/App.java.in | 11 ----------- mobile/android/base/Makefile.in | 10 ++++++---- .../menu-v11/{gecko_menu.xml.in => gecko_menu.xml} | 5 ----- .../menu/{gecko_menu.xml.in => gecko_menu.xml} | 5 ----- mobile/android/base/strings.xml.in | 3 --- mobile/android/chrome/content/browser.js | 9 --------- mobile/android/installer/package-manifest.in | 1 - tools/profiler/TableTicker.cpp | 6 ++++++ 9 files changed, 12 insertions(+), 39 deletions(-) rename mobile/android/base/resources/menu-v11/{gecko_menu.xml.in => gecko_menu.xml} (91%) rename mobile/android/base/resources/menu/{gecko_menu.xml.in => gecko_menu.xml} (91%) diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index e9a546352fe9..ee1a2ee0e458 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -236,7 +236,6 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt -@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/mobile/android/base/App.java.in b/mobile/android/base/App.java.in index 82ee5631714c..c4fdfa33452e 100644 --- a/mobile/android/base/App.java.in +++ b/mobile/android/base/App.java.in @@ -65,16 +65,5 @@ public class App extends GeckoApp { return "Redirector/@MOZ_APP_VERSION@ (Android; rv:@MOZ_APP_VERSION@)"; return getDefaultUAString(); } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { -#ifdef MOZ_PROFILING - if (item.getItemId() == R.id.toggle_profiling) { - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ToggleProfiling", null)); - return true; - } -#endif - return super.onOptionsItemSelected(item); - } }; diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 2905ebe13455..95c49d3cda4e 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -174,8 +174,6 @@ FENNEC_PP_JAVA_FILES = \ FENNEC_PP_XML_FILES = \ res/layout/abouthome_content.xml \ - res/menu/gecko_menu.xml \ - res/menu-v11/gecko_menu.xml \ $(NULL) @@ -532,6 +530,11 @@ RES_COLOR = \ RES_MENU = \ res/menu/awesomebar_contextmenu.xml \ + res/menu/gecko_menu.xml \ + $(NULL) + +RES_MENU_V11 = \ + res/menu-v11/gecko_menu.xml \ $(NULL) JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar @@ -594,7 +597,7 @@ MOZ_ANDROID_DRAWABLES += \ MOZ_ANDROID_DRAWABLES += $(shell if test -e $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn; then cat $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/android-resources.mn | tr '\n' ' '; fi) -RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) +RESOURCES=$(RES_LAYOUT) $(RES_LAYOUT_V11) $(RES_LAYOUT_LAND_V14) $(RES_VALUES) $(RES_VALUES_V11) $(RES_XML) $(RES_ANIM) $(RES_DRAWABLE_NODPI) $(RES_DRAWABLE_BASE) $(RES_DRAWABLE_LDPI) $(RES_DRAWABLE_HDPI) $(RES_DRAWABLE_MDPI_V11) $(RES_DRAWABLE_HDPI_V11) $(RES_DRAWABLE_XHDPI_V11) $(RES_DRAWABLE_LAND_V14) $(RES_DRAWABLE_LAND_MDPI_V14) $(RES_DRAWABLE_LAND_HDPI_V14) $(RES_DRAWABLE_LAND_XHDPI_V14) $(RES_COLOR) $(RES_MENU) $(RES_MENU_V11) RES_DIRS= \ res/layout \ @@ -620,7 +623,6 @@ RES_DIRS= \ res/drawable-land-xhdpi-v14 \ res/color \ res/menu \ - res/menu-v11 \ $(NULL) diff --git a/mobile/android/base/resources/menu-v11/gecko_menu.xml.in b/mobile/android/base/resources/menu-v11/gecko_menu.xml similarity index 91% rename from mobile/android/base/resources/menu-v11/gecko_menu.xml.in rename to mobile/android/base/resources/menu-v11/gecko_menu.xml index da6e285ecc81..68aed8284292 100644 --- a/mobile/android/base/resources/menu-v11/gecko_menu.xml.in +++ b/mobile/android/base/resources/menu-v11/gecko_menu.xml @@ -37,11 +37,6 @@ -#ifdef MOZ_PROFILING - -#endif - diff --git a/mobile/android/base/resources/menu/gecko_menu.xml.in b/mobile/android/base/resources/menu/gecko_menu.xml similarity index 91% rename from mobile/android/base/resources/menu/gecko_menu.xml.in rename to mobile/android/base/resources/menu/gecko_menu.xml index 6efa3b697a58..e009c24c84f5 100644 --- a/mobile/android/base/resources/menu/gecko_menu.xml.in +++ b/mobile/android/base/resources/menu/gecko_menu.xml @@ -38,11 +38,6 @@ -#ifdef MOZ_PROFILING - -#endif - diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index eb364ffc021d..59436f06da6a 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -88,9 +88,6 @@ &addons; &downloads; &char_encoding; - - Toggle Profiling &site_settings_title; &site_settings_cancel; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index b287e8232f00..cf2f00c06c27 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -193,7 +193,6 @@ var BrowserApp = { Services.obs.addObserver(this, "Viewport:Change", false); Services.obs.addObserver(this, "Passwords:Init", false); Services.obs.addObserver(this, "FormHistory:Init", false); - Services.obs.addObserver(this, "ToggleProfiling", false); Services.obs.addObserver(this, "sessionstore-state-purge-complete", false); @@ -953,14 +952,6 @@ var BrowserApp = { Services.obs.removeObserver(this, "FormHistory:Init", false); } else if (aTopic == "sessionstore-state-purge-complete") { sendMessageToJava({ gecko: { type: "Session:StatePurged" }}); - } else if (aTopic == "ToggleProfiling") { - let profiler = Cc["@mozilla.org/tools/profiler;1"]. - getService(Ci.nsIProfiler); - if (profiler.IsActive()) { - profiler.StopProfiler(); - } else { - profiler.StartProfiler(100000, 25, ["stackwalk"], 1); - } } }, diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 14ce81e45d8a..83ecf4d465bb 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -231,7 +231,6 @@ @BINPATH@/components/pref.xpt @BINPATH@/components/prefetch.xpt @BINPATH@/components/profile.xpt -@BINPATH@/components/profiler.xpt @BINPATH@/components/proxyObject.xpt @BINPATH@/components/rdf.xpt @BINPATH@/components/satchel.xpt diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index a205f611539e..ed07d33d8c06 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -353,7 +353,13 @@ class TableTicker: public Sampler { , mPrimaryThreadProfile(aEntrySize, aStack) , mSaveRequested(false) { +#if defined(USE_LIBUNWIND) && defined(ANDROID) + // We don't have the Gecko Profiler add-on on Android, but we know that + // libunwind is available, so we can always walk the stacks. + mUseStackWalk = true; +#else mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); +#endif //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); From dd0518f1ec1e3bb8fb9be2b5b93a0b9a914e61ab Mon Sep 17 00:00:00 2001 From: Sriram Ramasubramanian Date: Fri, 23 Mar 2012 12:00:17 -0700 Subject: [PATCH 048/109] Bug 738049: Using LayoutInflater.Factory improves performance. [r=mfinkle] --- mobile/android/base/AwesomeBar.java | 2 + mobile/android/base/GeckoApp.java | 2 + mobile/android/base/GeckoViewsFactory.java | 59 ++++++++++++++++++++++ mobile/android/base/Makefile.in | 1 + mobile/android/base/TabsTray.java | 2 + 5 files changed, 66 insertions(+) create mode 100644 mobile/android/base/GeckoViewsFactory.java diff --git a/mobile/android/base/AwesomeBar.java b/mobile/android/base/AwesomeBar.java index d35fd8716398..ca3f3f3ef41a 100644 --- a/mobile/android/base/AwesomeBar.java +++ b/mobile/android/base/AwesomeBar.java @@ -61,6 +61,7 @@ import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; @@ -109,6 +110,7 @@ public class AwesomeBar extends Activity implements GeckoEventListener { Log.d(LOGTAG, "creating awesomebar"); mResolver = Tabs.getInstance().getContentResolver(); + LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance()); setContentView(R.layout.awesomebar); diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 5368066c7178..dd4c8aa6ef27 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1623,6 +1623,8 @@ abstract public class GeckoApp mRestoreSession = savedInstanceState.getBoolean(SAVED_STATE_SESSION); } + LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance()); + super.onCreate(savedInstanceState); mOrientation = getResources().getConfiguration().orientation; diff --git a/mobile/android/base/GeckoViewsFactory.java b/mobile/android/base/GeckoViewsFactory.java new file mode 100644 index 000000000000..9350c347509b --- /dev/null +++ b/mobile/android/base/GeckoViewsFactory.java @@ -0,0 +1,59 @@ +/* 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/. */ + +package org.mozilla.gecko; + +import java.util.HashMap; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; + +public final class GeckoViewsFactory implements LayoutInflater.Factory { + private static final String LOGTAG = "GeckoViewsFactory"; + + private static final String GECKO_VIEW_IDENTIFIER = "org.mozilla.gecko."; + private static final int GECKO_VIEW_IDENTIFIER_LENGTH = GECKO_VIEW_IDENTIFIER.length(); + + // List of custom views used + private static final int ABOUT_HOME_SECTION = 1; + private static final int AWESOME_BAR_TABS = 2; + private static final int FORM_ASSIST_POPUP = 3; + private static final int LINK_TEXT_VIEW = 4; + + private GeckoViewsFactory() { } + + // Making this a singleton class. + private static final GeckoViewsFactory INSTANCE = new GeckoViewsFactory(); + + public static GeckoViewsFactory getInstance() { + return INSTANCE; + } + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + if (!TextUtils.isEmpty(name) && name.startsWith(GECKO_VIEW_IDENTIFIER)) { + String viewName = name.substring(GECKO_VIEW_IDENTIFIER_LENGTH); + + if (TextUtils.isEmpty(viewName)) + return null; + + Log.i(LOGTAG, "Creating custom Gecko view: " + viewName); + + if (TextUtils.equals(viewName, "AboutHomeSection")) + return new AboutHomeSection(context, attrs); + else if (TextUtils.equals(viewName, "AwesomeBarTabs")) + return new AwesomeBarTabs(context, attrs); + else if (TextUtils.equals(viewName, "FormAssistPopup")) + return new FormAssistPopup(context, attrs); + else if (TextUtils.equals(viewName, "LinkTextView")) + return new LinkTextView(context, attrs); + } + + return null; + } +} diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 95c49d3cda4e..f5f3b42dd927 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -97,6 +97,7 @@ FENNEC_JAVA_FILES = \ GeckoStateListDrawable.java \ GeckoThread.java \ GlobalHistory.java \ + GeckoViewsFactory.java \ LinkPreference.java \ LinkTextView.java \ NSSBridge.java \ diff --git a/mobile/android/base/TabsTray.java b/mobile/android/base/TabsTray.java index b4a106dd8eee..9c08d54db9d2 100644 --- a/mobile/android/base/TabsTray.java +++ b/mobile/android/base/TabsTray.java @@ -53,6 +53,8 @@ public class TabsTray extends Activity implements Tabs.OnTabsChangedListener { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance()); + setContentView(R.layout.tabs_tray); mWaitingForClose = false; From 38ac2ed1e9f639145339f06620b7d94c1a488e27 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Fri, 23 Mar 2012 20:09:27 +0100 Subject: [PATCH 049/109] Bug 733861 - Use std::ostreams for profile stringification. r=BenWa --- build/stdc++compat.cpp | 2 - tools/profiler/TableTicker.cpp | 80 +++++++++++++++------------------- 2 files changed, 34 insertions(+), 48 deletions(-) diff --git a/build/stdc++compat.cpp b/build/stdc++compat.cpp index 7cf6f09c38c7..b97ac09be5ec 100644 --- a/build/stdc++compat.cpp +++ b/build/stdc++compat.cpp @@ -59,9 +59,7 @@ namespace std { template ostream& ostream::_M_insert(unsigned long); template ostream& ostream::_M_insert(long long); template ostream& ostream::_M_insert(unsigned long long); -#ifdef DEBUG template ostream& ostream::_M_insert(const void*); -#endif template ostream& __ostream_insert(ostream&, const char*, streamsize); template istream& istream::_M_extract(double&); #endif diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index ed07d33d8c06..973a2793b834 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -38,13 +38,15 @@ #include #include +#include +#include +#include #include "sps_sampler.h" #include "platform.h" #include "nsXULAppAPI.h" #include "nsThreadUtils.h" #include "prenv.h" #include "shared-libraries.h" -#include "mozilla/StringBuilder.h" #include "mozilla/StackWalk.h" #include "JSObjectBuilder.h" @@ -147,7 +149,7 @@ public: , mTagName(aTagName) { } - string TagToString(ThreadProfile *profile); + friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry); private: friend class ThreadProfile; @@ -258,14 +260,7 @@ public: mWritePos = mLastFlushPos; } - void ToString(StringBuilder &profile) - { - int readPos = mReadPos; - while (readPos != mLastFlushPos) { - profile.Append(mEntries[readPos].TagToString(this).c_str()); - readPos = (readPos + 1) % mEntrySize; - } - } + friend std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile); JSObject *ToJSObject(JSContext *aCx) { @@ -309,16 +304,6 @@ public: return profile; } - void WriteProfile(FILE* stream) - { - int readPos = mReadPos; - while (readPos != mLastFlushPos) { - string tag = mEntries[readPos].TagToString(this); - fwrite(tag.data(), 1, tag.length(), stream); - readPos = (readPos + 1) % mEntrySize; - } - } - ProfileStack* GetStack() { return mStack; @@ -431,10 +416,11 @@ public: } #endif - FILE* stream = ::fopen(buff, "w"); - if (stream) { - t->GetPrimaryThreadProfile()->WriteProfile(stream); - ::fclose(stream); + std::ofstream stream; + stream.open(buff); + if (stream.is_open()) { + stream << *(t->GetPrimaryThreadProfile()); + stream.close(); LOG("Saved to " FOLDER "profile_TYPE_PID.txt"); } else { LOG("Fail to open profile log file."); @@ -675,31 +661,32 @@ void TableTicker::Tick(TickSample* sample) } } -string ProfileEntry::TagToString(ThreadProfile *profile) +std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile) { - string tag = ""; - if (mTagName == 'r') { - char buff[50]; - snprintf(buff, 50, "%-40f", mTagFloat); - tag += string(1, mTagName) + string("-") + string(buff) + string("\n"); - } else if (mTagName == 'l') { - char tagBuff[1024]; - Address pc = mTagAddress; - snprintf(tagBuff, 1024, "l-%p\n", pc); - tag += string(tagBuff); + int readPos = profile.mReadPos; + while (readPos != profile.mLastFlushPos) { + stream << profile.mEntries[readPos]; + readPos = (readPos + 1) % profile.mEntrySize; + } + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry) +{ + if (entry.mTagName == 'r') { + stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n"; + } else if (entry.mTagName == 'l') { + stream << entry.mTagName << "-" << static_cast(entry.mTagData) << "\n"; } else { - tag += string(1, mTagName) + string("-") + string(mTagData) + string("\n"); + stream << entry.mTagName << "-" << entry.mTagData << "\n"; } #ifdef ENABLE_SPS_LEAF_DATA - if (mLeafAddress) { - char tagBuff[1024]; - unsigned long pc = (unsigned long)mLeafAddress; - snprintf(tagBuff, 1024, "l-%llu\n", pc); - tag += string(tagBuff); + if (entry.mLeafAddress) { + stream << entry.mTagName << "-" << entry.mLeafAddress << "\n"; } #endif - return tag; + return stream; } void mozilla_sampler_init() @@ -767,11 +754,12 @@ char* mozilla_sampler_get_profile() return NULL; } - StringBuilder profile; - t->GetPrimaryThreadProfile()->ToString(profile); + std::stringstream profile; + profile << *(t->GetPrimaryThreadProfile()); - char *rtn = (char*)malloc( (profile.Length()+1) * sizeof(char) ); - strcpy(rtn, profile.Buffer()); + std::string profileString = profile.str(); + char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) ); + strcpy(rtn, profileString.c_str()); return rtn; } From d14fc6c435ffdee089b05d9ea9d8ddf486e8b63e Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Fri, 23 Mar 2012 14:45:41 -0400 Subject: [PATCH 050/109] Bug 719154, remove the implementation of chained and filtered event queues now that they are no longer needed, r=jlebar --HG-- extra : rebase_source : 54e3f130dfaf59e67974019e39bf85471b22b4bf --- xpcom/threads/nsIThreadInternal.idl | 40 +-------------------- xpcom/threads/nsThread.cpp | 54 +++-------------------------- xpcom/threads/nsThread.h | 28 ++------------- 3 files changed, 8 insertions(+), 114 deletions(-) diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl index 0747d2770dbe..fe8b7ca18371 100644 --- a/xpcom/threads/nsIThreadInternal.idl +++ b/xpcom/threads/nsIThreadInternal.idl @@ -45,7 +45,7 @@ interface nsIThreadEventFilter; * The XPCOM thread object implements this interface, which allows a consumer * to observe dispatch activity on the thread. */ -[scriptable, uuid(e0d35c22-53d5-4b48-8627-93e05b94cf2c)] +[scriptable, uuid(504e9e1f-70e1-4f33-a785-5840a4680414)] interface nsIThreadInternal : nsIThread { /** @@ -57,26 +57,6 @@ interface nsIThreadInternal : nsIThread */ attribute nsIThreadObserver observer; - /** - * This method causes any events currently enqueued on the thread to be - * suppressed until PopEventQueue is called. Additionally, any new events - * dispatched to the thread will only be processed if they are accepted by - * the given filter. If the filter is null, then new events are accepted. - * Calls to PushEventQueue may be nested and must each be paired with a call - * to PopEventQueue in order to restore the original state of the thread. - * - * @param filter - * The thread event filter to apply to dispatched events, or null to accept - * all dispatched events. - */ - void pushEventQueue(in nsIThreadEventFilter filter); - - /** - * Revert a call to PushEventQueue. When an event queue is popped, any - * events remaining in the queue are appended to the elder queue. - */ - void popEventQueue(); - /** * The current recursion depth, 0 when no events are running, 1 when a single * event is running, and higher when nested events are running. Must only be @@ -170,21 +150,3 @@ interface nsIThreadObserver : nsISupports void afterProcessNextEvent(in nsIThreadInternal thread, in unsigned long recursionDepth); }; - -/** - * Interface passed to the nsIThreadInternal::PushEventQueue method. - */ -[scriptable, uuid(a0605c0b-17f5-4681-b8cd-a1cd75d42559)] -interface nsIThreadEventFilter : nsISupports -{ - /** - * This method is called to determine whether or not an event may be accepted - * by a "nested" event queue (see nsIThreadInternal::PushEventQueue). - * - * @param event - * The event being dispatched. - * - * WARNING: This method must not make any calls on the thread object. - */ - [notxpcom] boolean acceptEvent(in nsIRunnable event); -}; diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index 8da91cfab0d8..dfeb7f4afa14 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -296,7 +296,7 @@ nsThread::ThreadFunc(void *arg) while (true) { { MutexAutoLock lock(self->mLock); - if (!self->mEvents->HasPendingEvent()) { + if (!self->mEvents.HasPendingEvent()) { // No events in the queue, so we will stop now. Don't let any more // events be added, since they won't be processed. It is critical // that no PutEvent can occur between testing that the event queue is @@ -325,7 +325,6 @@ nsThread::ThreadFunc(void *arg) nsThread::nsThread(MainThreadFlag aMainThread, PRUint32 aStackSize) : mLock("nsThread.mLock") - , mEvents(&mEventsRoot) , mPriority(PRIORITY_NORMAL) , mThread(nsnull) , mRunningEvent(0) @@ -366,7 +365,7 @@ nsThread::Init() // that mThread is set properly. { MutexAutoLock lock(mLock); - mEvents->PutEvent(startup); + mEvents.PutEvent(startup); } // Wait for thread to call ThreadManager::SetupCurrentThread, which completes @@ -393,7 +392,7 @@ nsThread::PutEvent(nsIRunnable *event) NS_WARNING("An event was posted to a thread that will never run it (rejected)"); return NS_ERROR_UNEXPECTED; } - if (!mEvents->PutEvent(event)) + if (!mEvents.PutEvent(event)) return NS_ERROR_OUT_OF_MEMORY; } @@ -523,7 +522,7 @@ nsThread::HasPendingEvents(bool *result) { NS_ENSURE_STATE(PR_GetCurrentThread() == mThread); - *result = mEvents->GetEvent(false, nsnull); + *result = mEvents.GetEvent(false, nsnull); return NS_OK; } @@ -635,7 +634,7 @@ nsThread::ProcessNextEvent(bool mayWait, bool *result) // If we are shutting down, then do not wait for new events. nsCOMPtr event; - mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event)); + mEvents.GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event)); #ifdef NS_FUNCTION_TIMER char message[1024] = {'\0'}; @@ -738,49 +737,6 @@ nsThread::SetObserver(nsIThreadObserver *obs) return NS_OK; } -NS_IMETHODIMP -nsThread::PushEventQueue(nsIThreadEventFilter *filter) -{ - nsChainedEventQueue *queue = new nsChainedEventQueue(filter); - - MutexAutoLock lock(mLock); - queue->mNext = mEvents; - mEvents = queue; - return NS_OK; -} - -NS_IMETHODIMP -nsThread::PopEventQueue() -{ - MutexAutoLock lock(mLock); - - // Make sure we do not pop too many! - NS_ENSURE_STATE(mEvents != &mEventsRoot); - - nsChainedEventQueue *queue = mEvents; - mEvents = mEvents->mNext; - - nsCOMPtr event; - while (queue->GetEvent(false, getter_AddRefs(event))) - mEvents->PutEvent(event); - - delete queue; - - return NS_OK; -} - -bool -nsThread::nsChainedEventQueue::PutEvent(nsIRunnable *event) -{ - bool val; - if (!mFilter || mFilter->AcceptEvent(event)) { - val = mQueue.PutEvent(event); - } else { - val = mNext->PutEvent(event); - } - return val; -} - NS_IMETHODIMP nsThread::GetRecursionDepth(PRUint32 *depth) { diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index 995ed77d9807..d1ad57edd0de 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -98,33 +98,10 @@ private: // Wrappers for event queue methods: bool GetEvent(bool mayWait, nsIRunnable **event) { - return mEvents->GetEvent(mayWait, event); + return mEvents.GetEvent(mayWait, event); } nsresult PutEvent(nsIRunnable *event); - // Wrapper for nsEventQueue that supports chaining. - class nsChainedEventQueue { - public: - nsChainedEventQueue(nsIThreadEventFilter *filter = nsnull) - : mNext(nsnull), mFilter(filter) { - } - - bool GetEvent(bool mayWait, nsIRunnable **event) { - return mQueue.GetEvent(mayWait, event); - } - - bool PutEvent(nsIRunnable *event); - - bool HasPendingEvent() { - return mQueue.HasPendingEvent(); - } - - class nsChainedEventQueue *mNext; - private: - nsCOMPtr mFilter; - nsEventQueue mQueue; - }; - // This lock protects access to mObserver, mEvents and mEventsAreDoomed. // All of those fields are only modified on the thread itself (never from // another thread). This means that we can avoid holding the lock while @@ -137,8 +114,7 @@ private: // Only accessed on the target thread. nsAutoTObserverArray, 2> mEventObservers; - nsChainedEventQueue *mEvents; // never null - nsChainedEventQueue mEventsRoot; + nsEventQueue mEvents; PRInt32 mPriority; PRThread *mThread; From aa2bb6dae0799b14b3b9bc30d9496133a9d708a5 Mon Sep 17 00:00:00 2001 From: Chris Lord Date: Fri, 23 Mar 2012 19:27:25 +0000 Subject: [PATCH 051/109] Bug 738740 - Fix handling of parameters in nsTArray::ReplaceElementAt. r=bz Handle the parameters given to ReplaceElementAt the same way as those given to other methods in the class. This allows removal of the & in GLContext.cpp that was taking the address of a temporary, and causing a build error when using clang, or when using GCC with particular parameters. --- gfx/gl/GLContext.cpp | 2 +- xpcom/glue/nsTArray.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 5eec14d25a79..fced458f79b1 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -1185,7 +1185,7 @@ void TiledTextureImage::Resize(const nsIntSize& aSize) nsRefPtr teximg = mGL->TileGenFunc(size, mContentType, mUseNearestFilter); if (replace) - mImages.ReplaceElementAt(i, &teximg.forget()); + mImages.ReplaceElementAt(i, teximg.forget()); else mImages.InsertElementAt(i, teximg.forget()); i++; diff --git a/xpcom/glue/nsTArray.h b/xpcom/glue/nsTArray.h index 7fb2ca11e1bb..6833964001b4 100644 --- a/xpcom/glue/nsTArray.h +++ b/xpcom/glue/nsTArray.h @@ -754,7 +754,7 @@ public: // A variation on the ReplaceElementsAt method defined above. template elem_type *ReplaceElementAt(index_type index, const Item& item) { - return ReplaceElementsAt(index, 1, item, 1); + return ReplaceElementsAt(index, 1, &item, 1); } // A variation on the ReplaceElementsAt method defined above. From a75f61a4075af035f064f490ebd954bd9168a85e Mon Sep 17 00:00:00 2001 From: James Willcox Date: Fri, 23 Mar 2012 16:19:15 -0400 Subject: [PATCH 052/109] Bug 725167 - Don't reposition Android plugin views that aren't in view hierarchy r=blassey --- mobile/android/base/GeckoApp.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index dd4c8aa6ef27..4ae99aeece7a 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1570,7 +1570,8 @@ abstract public class GeckoApp view.setVisibility(View.VISIBLE); } - mPluginContainer.updateViewLayout(view, lp); + if (mPluginContainer.indexOfChild(view) >= 0) + mPluginContainer.updateViewLayout(view, lp); } } From 078715dcff8ce6195bd8cb81452c8c50213baca7 Mon Sep 17 00:00:00 2001 From: Kan-Ru Chen Date: Fri, 23 Mar 2012 21:50:29 +0100 Subject: [PATCH 053/109] Bug 738529 - Check "screen" wake lock before turning off screen. r=cjones --- b2g/chrome/content/shell.js | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index dbe1c9075a0b..a4f601e5effc 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -327,17 +327,36 @@ var shell = { }; (function PowerManager() { - let idleHandler = { - observe: function(subject, topic, time) { - if (topic === "idle") { - // TODO: Check wakelock status. See bug 697132. + // This will eventually be moved to content, so use content API as + // much as possible here. TODO: Bug 738530 + let power = navigator.mozPower; + let idleHandler = function idleHandler(subject, topic, time) { + if (topic === "idle") { + if (power.getWakeLockState("screen") != "locked-foreground") { screen.mozEnabled = false; } - }, + } + } + let wakeLockHandler = function wakeLockHandler(topic, state) { + // Turn off the screen when no one needs the it or all of them are + // invisible, otherwise turn the screen on. Note that the CPU + // might go to sleep as soon as the screen is turned off and + // acquiring wake lock will not bring it back (actually the code + // is not executed at all). + if (topic == "screen") { + if (state != "locked-foreground") { + if (Services.idle.idleTime > idleTimeout*1000) { + screen.mozEnabled = false; + } + } else { + screen.mozEnabled = true; + } + } } let idleTimeout = Services.prefs.getIntPref("power.screen.timeout"); if (idleTimeout) { Services.idle.addIdleObserver(idleHandler, idleTimeout); + power.addWakeLockListener(wakeLockHandler); } })(); From db8ba9f4b9ec18c990331f56258cd1bff812169b Mon Sep 17 00:00:00 2001 From: Ian Moody Date: Thu, 22 Mar 2012 13:06:20 +0000 Subject: [PATCH 054/109] Bug 738233 - Fix misplaced brackets in browser_bug329212.js. r=dao --- .../base/content/test/browser_bug329212.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/browser/base/content/test/browser_bug329212.js b/browser/base/content/test/browser_bug329212.js index 8c418efceba9..e08e845af585 100644 --- a/browser/base/content/test/browser_bug329212.js +++ b/browser/base/content/test/browser_bug329212.js @@ -7,30 +7,30 @@ function test () { let doc = gBrowser.contentDocument; let tooltip = document.getElementById("aHTMLTooltip"); - ok(FillInHTMLTooltip(doc.getElementById("svg1"), "should get title")); + ok(FillInHTMLTooltip(doc.getElementById("svg1")), "should get title"); is(tooltip.getAttribute("label"), "This is a non-root SVG element title"); - ok(FillInHTMLTooltip(doc.getElementById("text1"), "should get title")); + ok(FillInHTMLTooltip(doc.getElementById("text1")), "should get title"); is(tooltip.getAttribute("label"), "\n\n\n This is a title\n\n "); - ok(!FillInHTMLTooltip(doc.getElementById("text2"), "should not get title")); + ok(!FillInHTMLTooltip(doc.getElementById("text2")), "should not get title"); - ok(!FillInHTMLTooltip(doc.getElementById("text3"), "should not get title")); + ok(!FillInHTMLTooltip(doc.getElementById("text3")), "should not get title"); - ok(FillInHTMLTooltip(doc.getElementById("link1"), "should get title")); + ok(FillInHTMLTooltip(doc.getElementById("link1")), "should get title"); is(tooltip.getAttribute("label"), "\n This is a title\n "); - ok(FillInHTMLTooltip(doc.getElementById("text4"), "should get title")); + ok(FillInHTMLTooltip(doc.getElementById("text4")), "should get title"); is(tooltip.getAttribute("label"), "\n This is a title\n "); - ok(!FillInHTMLTooltip(doc.getElementById("link2"), "should not get title")); + ok(!FillInHTMLTooltip(doc.getElementById("link2")), "should not get title"); - ok(FillInHTMLTooltip(doc.getElementById("link3"), "should get title")); - ok(tooltip.getAttribute("label") != ""); + ok(FillInHTMLTooltip(doc.getElementById("link3")), "should get title"); + isnot(tooltip.getAttribute("label"), ""); - ok(FillInHTMLTooltip(doc.getElementById("link4"), "should get title")); + ok(FillInHTMLTooltip(doc.getElementById("link4")), "should get title"); is(tooltip.getAttribute("label"), "This is an xlink:title attribute"); - ok(!FillInHTMLTooltip(doc.getElementById("text5"), "should not get title")); + ok(!FillInHTMLTooltip(doc.getElementById("text5")), "should not get title"); gBrowser.removeCurrentTab(); finish(); From 3853831c88beea723e4a18b2b2462dd1cdc96a5a Mon Sep 17 00:00:00 2001 From: Pallani Kumaran Date: Thu, 22 Mar 2012 17:17:19 +0800 Subject: [PATCH 055/109] Bug 738160 - Fix "beause" spelling in /browser/locales/en-US/chrome/browser/browser.dtd. r=gavin --- browser/locales/en-US/chrome/browser/browser.dtd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 036c26dfcb19..8ddb3c529c3f 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -280,7 +280,7 @@ can reach it easily. --> - + From be41190a07c52627fc7608c0e213c219b8c4b976 Mon Sep 17 00:00:00 2001 From: Joshua M Date: Fri, 23 Mar 2012 21:53:11 +0100 Subject: [PATCH 056/109] Bug 679801 - "New Tab", "Panorama" and "List All Tabs" buttons do not get inverted on Aero with tabs on bottom. r=dao --- browser/themes/winstripe/browser-aero.css | 4 ++-- browser/themes/winstripe/browser.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/browser/themes/winstripe/browser-aero.css b/browser/themes/winstripe/browser-aero.css index 86e45924c27a..c11f41b9a627 100644 --- a/browser/themes/winstripe/browser-aero.css +++ b/browser/themes/winstripe/browser-aero.css @@ -158,9 +158,9 @@ border-right-style: none !important; } - #toolbar-menubar :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme), + #toolbar-menubar :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme), #TabsToolbar[tabsontop=true] :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme), - #navigator-toolbox[tabsontop=false] > #nav-bar :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme), + #navigator-toolbox[tabsontop=false] > #nav-bar :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme), #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#tabview-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme) { list-style-image: url("chrome://browser/skin/Toolbar-inverted.png"); } diff --git a/browser/themes/winstripe/browser.css b/browser/themes/winstripe/browser.css index 45ed5ecbda86..5f2d224f419a 100644 --- a/browser/themes/winstripe/browser.css +++ b/browser/themes/winstripe/browser.css @@ -1003,6 +1003,8 @@ toolbar[mode=full] .toolbarbutton-1 > .toolbarbutton-menubutton-button { %ifdef WINSTRIPE_AERO #TabsToolbar > #tabview-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), #TabsToolbar > toolbarpaletteitem > #tabview-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), +#navigator-toolbox[tabsontop=false] > #nav-bar #tabview-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), +#toolbar-menubar #tabview-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), %endif #tabview-button:-moz-lwtheme-brighttext { list-style-image: url(chrome://browser/skin/tabview/tabview-inverted.png); @@ -2051,6 +2053,8 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action- %ifdef WINSTRIPE_AERO #TabsToolbar > #alltabs-button[type="menu"]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), #TabsToolbar > toolbarpaletteitem > #alltabs-button[type="menu"]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), +#navigator-toolbox[tabsontop=false] > #nav-bar #alltabs-button[type="menu"]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), +#toolbar-menubar #alltabs-button[type="menu"]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme), %endif #alltabs-button[type="menu"]:-moz-lwtheme-brighttext { list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png"); From 502585bb835efb49824a23466695d251de64080f Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Fri, 23 Mar 2012 16:53:40 -0400 Subject: [PATCH 057/109] Bug 738526 - SessionStore TabValue APIs are busted r=bnicholson --- mobile/android/components/SessionStore.idl | 6 +++--- mobile/android/components/SessionStore.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/android/components/SessionStore.idl b/mobile/android/components/SessionStore.idl index c08242aff847..6486a44d9ddf 100644 --- a/mobile/android/components/SessionStore.idl +++ b/mobile/android/components/SessionStore.idl @@ -88,20 +88,20 @@ interface nsISessionStore : nsISupports * * @returns A string value or an empty string if none is set. */ - AString getTabValue(in nsIDOMNode aTab, in AString aKey); + AString getTabValue(in jsval aTab, in AString aKey); /** * @param aTab is the browser tab to set the value for. * @param aKey is the value's name. * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects). */ - void setTabValue(in nsIDOMNode aTab, in AString aKey, in AString aStringValue); + void setTabValue(in jsval aTab, in AString aKey, in AString aStringValue); /** * @param aTab is the browser tab to get the value for. * @param aKey is the value's name. */ - void deleteTabValue(in nsIDOMNode aTab, in AString aKey); + void deleteTabValue(in jsval aTab, in AString aKey); /** * @returns A boolean indicating we should restore previous browser session diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index eda654848511..4d0a5c0ae4aa 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -872,13 +872,13 @@ SessionStore.prototype = { }, getTabValue: function ss_getTabValue(aTab, aKey) { - let browser = aTab.linkedBrowser; + let browser = aTab.browser; let data = browser.__SS_extdata || {}; return data[aKey] || ""; }, setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) { - let browser = aTab.linkedBrowser; + let browser = aTab.browser; // Thumbnails are actually stored in the cache, so do the save and update the URI if (aKey == "thumbnail") { @@ -903,7 +903,7 @@ SessionStore.prototype = { }, deleteTabValue: function ss_deleteTabValue(aTab, aKey) { - let browser = aTab.linkedBrowser; + let browser = aTab.browser; if (browser.__SS_extdata && browser.__SS_extdata[aKey]) delete browser.__SS_extdata[aKey]; else From 27d14f9642cb4ce664693f31366db962c534af9e Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Fri, 23 Mar 2012 16:53:43 -0400 Subject: [PATCH 058/109] Bug 738527 - Syntax error breaks removing closed tabs r=bnicholson --- mobile/android/chrome/content/browser.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index cf2f00c06c27..ed469856610f 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1501,8 +1501,7 @@ Tab.prototype = { sendMessageToJava(message); this.overscrollController = new OverscrollController(this); - this.browser.contentWindow.controllers - .insertControllerAt(0, this.overscrollController); + this.browser.contentWindow.controllers.insertControllerAt(0, this.overscrollController); let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL | Ci.nsIWebProgress.NOTIFY_LOCATION | @@ -1553,8 +1552,7 @@ Tab.prototype = { if (!this.browser) return; - this.browser.controllers.contentWindow - .removeController(this.overscrollController); + this.browser.contentWindow.controllers.removeController(this.overscrollController); this.browser.removeProgressListener(this); this.browser.removeEventListener("DOMContentLoaded", this, true); From 58fc9c47978e91280b049bbf994ff59f37313973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20=C3=81vila=20de=20Esp=C3=ADndola?= Date: Fri, 23 Mar 2012 16:56:36 -0400 Subject: [PATCH 059/109] Bug 683975 - Disable lto. r=rail. --- build/unix/build-toolchain/build-gcc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build/unix/build-toolchain/build-gcc.py b/build/unix/build-toolchain/build-gcc.py index 23fa23a3452d..fda17dfee572 100755 --- a/build/unix/build-toolchain/build-gcc.py +++ b/build/unix/build-toolchain/build-gcc.py @@ -109,6 +109,7 @@ def build_gcc(stage_dir, is_stage_one): "--with-mpfr=%s" % lib_inst_dir, "--with-mpc=%s" % lib_inst_dir, "--enable-languages=c,c++", + "--disable-lto", "--disable-multilib", "--disable-bootstrap"] if is_stage_one: From 13edde4d5e1ef03654f0bb4340d82de1e05be80a Mon Sep 17 00:00:00 2001 From: William Lachance Date: Fri, 23 Mar 2012 14:00:04 -0700 Subject: [PATCH 060/109] Bug 722403 - DeviceManager: killProcess(): Add forceKill parameter and kill all matching processes, r=gbrown Earlier patch by Christian Holler. --HG-- extra : rebase_source : 295a10d592827f4633951eda196505529560f8f0 --- build/mobile/devicemanager.py | 6 +++--- build/mobile/devicemanagerADB.py | 17 +++++++++++------ build/mobile/devicemanagerSUT.py | 12 +++++++----- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/build/mobile/devicemanager.py b/build/mobile/devicemanager.py index 14067993215f..4a6ef7378526 100755 --- a/build/mobile/devicemanager.py +++ b/build/mobile/devicemanager.py @@ -235,12 +235,12 @@ class DeviceManager: @abstractmethod - def killProcess(self, appname): + def killProcess(self, appname, forceKill=False): """ external function returns: - success: output from testagent - failure: None + success: True + failure: False """ @abstractmethod diff --git a/build/mobile/devicemanagerADB.py b/build/mobile/devicemanagerADB.py index 7e9cef4fb255..2d4fb0a14c3b 100644 --- a/build/mobile/devicemanagerADB.py +++ b/build/mobile/devicemanagerADB.py @@ -376,16 +376,21 @@ class DeviceManagerADB(DeviceManager): # external function # returns: - # success: output from testagent - # failure: None - def killProcess(self, appname): + # success: True + # failure: False + def killProcess(self, appname, forceKill=False): procs = self.getProcessList() + didKillProcess = False for (pid, name, user) in procs: if name == appname: - p = self.runCmdAs(["shell", "kill", pid]) - return p.stdout.read() + args = ["shell", "kill"] + if forceKill: + args.append("-9") + args.append(pid) + p = self.runCmdAs(args) + didKillProcess = True - return None + return didKillProcess # external function # returns: diff --git a/build/mobile/devicemanagerSUT.py b/build/mobile/devicemanagerSUT.py index 6d5ffc7afb92..d957d7a8e9e6 100644 --- a/build/mobile/devicemanagerSUT.py +++ b/build/mobile/devicemanagerSUT.py @@ -582,15 +582,17 @@ class DeviceManagerSUT(DeviceManager): # external function # returns: - # success: output from testagent - # failure: None - def killProcess(self, appname): + # success: True + # failure: False + def killProcess(self, appname, forceKill=False): + if forceKill: + print "WARNING: killProcess(): forceKill parameter unsupported on SUT" try: data = self.runCmds(['kill ' + appname]) except AgentError: - return None + return False - return data + return True # external function # returns: From 554919925bef31a92e2b7c36d1030d6f248113df Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 23 Mar 2012 17:04:28 -0400 Subject: [PATCH 061/109] Backout 1152d14294df, 18c70ab50559, dbd51616925a, 730c2e84247f, and 7beb8fd46629 due to perma-hangs on WinXP debug moth. (test_memoryReporters.xul | application timed out after 330 seconds with no output) --- gfx/thebes/gfxDWriteFontList.cpp | 75 +--------- gfx/thebes/gfxDWriteFontList.h | 17 +-- gfx/thebes/gfxDWriteFonts.cpp | 17 --- gfx/thebes/gfxDWriteFonts.h | 5 - gfx/thebes/gfxFT2FontList.cpp | 17 --- gfx/thebes/gfxFT2FontList.h | 5 - gfx/thebes/gfxFT2Fonts.cpp | 17 --- gfx/thebes/gfxFT2Fonts.h | 5 - gfx/thebes/gfxFont.cpp | 216 ++------------------------- gfx/thebes/gfxFont.h | 65 +------- gfx/thebes/gfxFontUtils.h | 16 +- gfx/thebes/gfxGDIFont.cpp | 17 --- gfx/thebes/gfxGDIFont.h | 5 - gfx/thebes/gfxGDIFontList.cpp | 46 +----- gfx/thebes/gfxGDIFontList.h | 10 +- gfx/thebes/gfxMacFont.cpp | 16 -- gfx/thebes/gfxMacFont.h | 5 - gfx/thebes/gfxMacPlatformFontList.h | 6 - gfx/thebes/gfxMacPlatformFontList.mm | 25 +--- gfx/thebes/gfxPlatformFontList.cpp | 185 ++--------------------- gfx/thebes/gfxPlatformFontList.h | 33 ---- xpcom/glue/nsBaseHashtable.h | 15 +- xpcom/glue/nsTHashtable.h | 7 +- 23 files changed, 66 insertions(+), 759 deletions(-) diff --git a/gfx/thebes/gfxDWriteFontList.cpp b/gfx/thebes/gfxDWriteFontList.cpp index 61077420fa2f..0cdf0b1bc0a6 100644 --- a/gfx/thebes/gfxDWriteFontList.cpp +++ b/gfx/thebes/gfxDWriteFontList.cpp @@ -249,23 +249,6 @@ gfxDWriteFontFamily::LocalizedName(nsAString &aLocalizedName) aLocalizedName = nsDependentString(famName.Elements()); } -void -gfxDWriteFontFamily::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - gfxFontFamily::SizeOfExcludingThis(aMallocSizeOf, aSizes); - // TODO: - // This doesn't currently account for |mDWFamily| -} - -void -gfxDWriteFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - //////////////////////////////////////////////////////////////////////////////// // gfxDWriteFontEntry @@ -394,8 +377,7 @@ gfxDWriteFontEntry::ReadCMAP() unicodeFont, symbolFont); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), - mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); + NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -442,8 +424,7 @@ gfxDWriteFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), - mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); + NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -563,23 +544,6 @@ gfxDWriteFontEntry::IsCJKFont() return mIsCJK; } -void -gfxDWriteFontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - gfxFontEntry::SizeOfExcludingThis(aMallocSizeOf, aSizes); - // TODO: - // This doesn't currently account for the |mFont| and |mFontFile| members -} - -void -gfxDWriteFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - //////////////////////////////////////////////////////////////////////////////// // gfxDWriteFontList @@ -623,6 +587,7 @@ gfxFontEntry * gfxDWriteFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, const nsAString& aFullname) { + bool found; gfxFontEntry *lookup; // initialize name lookup tables if needed @@ -631,8 +596,8 @@ gfxDWriteFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, } // lookup in name lookup tables, return null if not found - if (!(lookup = mPostscriptNames.GetWeak(aFullname)) && - !(lookup = mFullnames.GetWeak(aFullname))) + if (!(lookup = mPostscriptNames.GetWeak(aFullname, &found)) && + !(lookup = mFullnames.GetWeak(aFullname, &found))) { return nsnull; } @@ -1217,8 +1182,8 @@ gfxDWriteFontList::ResolveFontName(const nsAString& aFontName, nsAutoString keyName(aFontName); BuildKeyNameFromFontName(keyName); - gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName); - if (ff) { + nsRefPtr ff; + if (mFontSubstitutes.Get(keyName, &ff)) { aResolvedFontName = ff->Name(); return true; } @@ -1230,32 +1195,6 @@ gfxDWriteFontList::ResolveFontName(const nsAString& aFontName, return gfxPlatformFontList::ResolveFontName(aFontName, aResolvedFontName); } -void -gfxDWriteFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - gfxPlatformFontList::SizeOfExcludingThis(aMallocSizeOf, aSizes); - - aSizes->mFontListSize += - mFontSubstitutes.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, - aMallocSizeOf); - - aSizes->mFontListSize += - mNonExistingFonts.SizeOfExcludingThis(aMallocSizeOf); - for (PRUint32 i = 0; i < mNonExistingFonts.Length(); ++i) { - aSizes->mFontListSize += - mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); - } -} - -void -gfxDWriteFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - static nsresult GetFamilyName(IDWriteFont *aFont, nsString& aFamilyName) { HRESULT hr; diff --git a/gfx/thebes/gfxDWriteFontList.h b/gfx/thebes/gfxDWriteFontList.h index 5287402cd24a..c1db3f0456f5 100644 --- a/gfx/thebes/gfxDWriteFontList.h +++ b/gfx/thebes/gfxDWriteFontList.h @@ -79,11 +79,6 @@ public: void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: /** This font family's directwrite fontfamily object */ nsRefPtr mDWFamily; @@ -184,11 +179,6 @@ public: void SetForceGDIClassic(bool aForce) { mForceGDIClassic = aForce; } bool GetForceGDIClassic() { return mForceGDIClassic; } - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: friend class gfxDWriteFont; friend class gfxDWriteFontList; @@ -390,11 +380,6 @@ public: gfxFloat GetForceGDIClassicMaxFontSize() { return mForceGDIClassicMaxFontSize; } - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - private: friend class gfxDWriteFontFamily; @@ -416,7 +401,7 @@ private: */ nsTArray mNonExistingFonts; - typedef nsRefPtrHashtable FontTable; + typedef nsDataHashtable > FontTable; /** * Table of font substitutes, we grab this from the registry to get diff --git a/gfx/thebes/gfxDWriteFonts.cpp b/gfx/thebes/gfxDWriteFonts.cpp index 48d35cce0016..b5e9beb8231c 100644 --- a/gfx/thebes/gfxDWriteFonts.cpp +++ b/gfx/thebes/gfxDWriteFonts.cpp @@ -762,20 +762,3 @@ gfxDWriteFont::MeasureGlyphWidth(PRUint16 aGlyph) } return 0; } - -void -gfxDWriteFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); - aSizes->mFontInstances += aMallocSizeOf(mMetrics) + - mGlyphWidths.SizeOfExcludingThis(nsnull, aMallocSizeOf); -} - -void -gfxDWriteFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxDWriteFonts.h b/gfx/thebes/gfxDWriteFonts.h index 135903ae91d6..0338776f8dcb 100644 --- a/gfx/thebes/gfxDWriteFonts.h +++ b/gfx/thebes/gfxDWriteFonts.h @@ -92,11 +92,6 @@ public: virtual mozilla::TemporaryRef GetGlyphRenderingOptions(); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: friend class gfxDWriteShaper; diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp index 82c1819f57d6..9165207a106b 100644 --- a/gfx/thebes/gfxFT2FontList.cpp +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -389,23 +389,6 @@ FT2FontEntry::GetFontTable(PRUint32 aTableTag, return NS_OK; } -void -FT2FontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - gfxFontEntry::SizeOfExcludingThis(aMallocSizeOf, aSizes); - aSizes->mFontListSize += - mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf); -} - -void -FT2FontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - /* * FT2FontFamily * A standard gfxFontFamily; just adds a method used to support sending diff --git a/gfx/thebes/gfxFT2FontList.h b/gfx/thebes/gfxFT2FontList.h index 9299b9418ee6..9d430c452c96 100644 --- a/gfx/thebes/gfxFT2FontList.h +++ b/gfx/thebes/gfxFT2FontList.h @@ -102,11 +102,6 @@ public: nsresult ReadCMAP(); nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - FT_Face mFTFace; cairo_font_face_t *mFontFace; diff --git a/gfx/thebes/gfxFT2Fonts.cpp b/gfx/thebes/gfxFT2Fonts.cpp index f4e1d6cf27f1..a2cd06e40803 100644 --- a/gfx/thebes/gfxFT2Fonts.cpp +++ b/gfx/thebes/gfxFT2Fonts.cpp @@ -661,20 +661,3 @@ gfxFT2Font::FillGlyphDataForChar(PRUint32 ch, CachedGlyphData *gd) gd->rsbDelta = face->glyph->rsb_delta; gd->xAdvance = face->glyph->advance.x; } - -void -gfxFT2Font::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); - aSizes->mFontInstances += - mCharGlyphCache.SizeOfExcludingThis(nsnull, aMallocSizeOf); -} - -void -gfxFT2Font::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxFT2Fonts.h b/gfx/thebes/gfxFT2Fonts.h index 7abf39f4416c..61c8b51093ed 100644 --- a/gfx/thebes/gfxFT2Fonts.h +++ b/gfx/thebes/gfxFT2Fonts.h @@ -96,11 +96,6 @@ public: // new functions return &entry->mData; } - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: virtual bool ShapeWord(gfxContext *aContext, gfxShapedWord *aShapedWord, diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index a72edd8cb411..733d7e721507 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -47,6 +47,7 @@ #include "nsReadableUtils.h" #include "nsExpirationTracker.h" #include "nsILanguageAtomService.h" +#include "nsIMemoryReporter.h" #include "nsITimer.h" #include "gfxFont.h" @@ -401,44 +402,6 @@ gfxFontEntry::CheckForGraphiteTables() } #endif -/* static */ size_t -gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis - (FontTableHashEntry *aEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - if (aEntry->mBlob) { - FontListSizes *sizes = static_cast(aUserArg); - sizes->mFontTableCacheSize += aMallocSizeOf(aEntry->mBlob); - sizes->mFontTableCacheSize += - aMallocSizeOf(hb_blob_get_data(aEntry->mBlob, NULL)); - } - - // the size of the blob is recorded in the FontListSizes record, - // so we return 0 here for the function result - return 0; -} - -void -gfxFontEntry::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - aSizes->mCharMapsSize += mCharacterMap.SizeOfExcludingThis(aMallocSizeOf); - aSizes->mFontTableCacheSize += - mFontTableCache.SizeOfExcludingThis( - FontTableHashEntry::SizeOfEntryExcludingThis, - aMallocSizeOf, aSizes); -} - -void -gfxFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - ////////////////////////////////////////////////////////////////////////////// // // class gfxFontFamily @@ -1013,32 +976,6 @@ gfxFontFamily::FindFont(const nsAString& aPostscriptName) return nsnull; } -void -gfxFontFamily::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += - mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - aSizes->mCharMapsSize += mCharacterMap.SizeOfExcludingThis(aMallocSizeOf); - - aSizes->mFontListSize += - mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); - for (PRUint32 i = 0; i < mAvailableFonts.Length(); ++i) { - gfxFontEntry *fe = mAvailableFonts[i]; - if (fe) { - fe->SizeOfIncludingThis(aMallocSizeOf, aSizes); - } - } -} - -void -gfxFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - /* * gfxFontCache - global cache of gfxFont instances. * Expires unused fonts after a short interval; @@ -1047,52 +984,6 @@ gfxFontFamily::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, * shaped-word caches to free up memory. */ -NS_IMPL_ISUPPORTS1(gfxFontCache::MemoryReporter, nsIMemoryMultiReporter) - -NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontCacheMallocSizeOf, "font-cache") - -NS_IMETHODIMP -gfxFontCache::MemoryReporter::GetName(nsACString &aName) -{ - aName.AssignLiteral("font-cache"); - return NS_OK; -} - -NS_IMETHODIMP -gfxFontCache::MemoryReporter::CollectReports - (nsIMemoryMultiReporterCallback* aCb, - nsISupports* aClosure) -{ - FontCacheSizes sizes; - - gfxFontCache::GetCache()->SizeOfIncludingThis(&FontCacheMallocSizeOf, - &sizes); - - aCb->Callback(EmptyCString(), - NS_LITERAL_CSTRING("explicit/gfx/font-cache"), - nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, - sizes.mFontInstances, - NS_LITERAL_CSTRING("Memory used for active font instances."), - aClosure); - - aCb->Callback(EmptyCString(), - NS_LITERAL_CSTRING("explicit/gfx/font-shaped-words"), - nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, - sizes.mShapedWords, - NS_LITERAL_CSTRING("Memory used to cache shaped glyph data."), - aClosure); - - return NS_OK; -} - -NS_IMETHODIMP -gfxFontCache::MemoryReporter::GetExplicitNonHeap(PRInt64* aAmount) -{ - // This reporter only measures heap memory. - *aAmount = 0; - return NS_OK; -} - // Observer for the memory-pressure notification, to trigger // flushing of the shaped-word caches class MemoryPressureObserver : public nsIObserver, @@ -1124,11 +1015,7 @@ gfxFontCache::Init() { NS_ASSERTION(!gGlobalCache, "Where did this come from?"); gGlobalCache = new gfxFontCache(); - if (!gGlobalCache) { - return NS_ERROR_OUT_OF_MEMORY; - } - NS_RegisterMemoryMultiReporter(new MemoryReporter); - return NS_OK; + return gGlobalCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } void @@ -1289,39 +1176,6 @@ gfxFontCache::ClearCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) return PL_DHASH_NEXT; } -/*static*/ -size_t -gfxFontCache::SizeOfFontEntryExcludingThis(HashEntry* aHashEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - HashEntry *entry = static_cast(aHashEntry); - FontCacheSizes *sizes = static_cast(aUserArg); - entry->mFont->SizeOfExcludingThis(aMallocSizeOf, sizes); - - // The font records its size in the |sizes| parameter, so we return zero - // here to the hashtable enumerator. - return 0; -} - -void -gfxFontCache::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - // TODO: add the overhead of the expiration tracker (generation arrays) - - mFonts.SizeOfExcludingThis(SizeOfFontEntryExcludingThis, - aMallocSizeOf, aSizes); -} - -void -gfxFontCache::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) { @@ -2081,10 +1935,6 @@ gfxFont::GetShapedWord(gfxContext *aContext, aFlags); CacheHashEntry *entry = mWordCache.PutEntry(key); - if (!entry) { - NS_WARNING("failed to create word cache entry - expect missing text"); - return nsnull; - } gfxShapedWord *sw = entry->mShapedWord; Telemetry::Accumulate(Telemetry::WORD_CACHE_LOOKUP_LEN, aLength); Telemetry::Accumulate(Telemetry::WORD_CACHE_LOOKUP_SCRIPT, aRunScript); @@ -2100,21 +1950,20 @@ gfxFont::GetShapedWord(gfxContext *aContext, aRunScript, aAppUnitsPerDevUnit, aFlags); + NS_ASSERTION(sw != nsnull, + "failed to create gfxShapedWord - expect missing text"); if (!sw) { - NS_WARNING("failed to create gfxShapedWord - expect missing text"); return nsnull; } - bool ok = false; + bool ok; if (sizeof(T) == sizeof(PRUnichar)) { ok = ShapeWord(aContext, sw, (const PRUnichar*)aText); } else { nsAutoString utf16; AppendASCIItoUTF16(nsDependentCSubstring((const char*)aText, aLength), utf16); - if (utf16.Length() == aLength) { - ok = ShapeWord(aContext, sw, utf16.BeginReading()); - } + ok = ShapeWord(aContext, sw, utf16.BeginReading()); } NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); @@ -2671,40 +2520,10 @@ gfxFont::SynthesizeSpaceWidth(PRUint32 aCh) } } -/*static*/ size_t -gfxFont::WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - return aMallocSizeOf(aHashEntry->mShapedWord.get()); -} - -void -gfxFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - for (PRUint32 i = 0; i < mGlyphExtentsArray.Length(); ++i) { - aSizes->mFontInstances += - mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); - } - aSizes->mShapedWords += - mWordCache.SizeOfExcludingThis(WordCacheEntrySizeOfExcludingThis, - aMallocSizeOf); -} - -void -gfxFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - gfxGlyphExtents::~gfxGlyphExtents() { #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS - gGlyphExtentsWidthsTotalSize += - mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); + gGlyphExtentsWidthsTotalSize += mContainedGlyphWidths.ComputeSize(); gGlyphExtentsCount++; #endif MOZ_COUNT_DTOR(gfxGlyphExtents); @@ -2748,19 +2567,21 @@ gfxGlyphExtents::GlyphWidths::~GlyphWidths() } } +#ifdef DEBUG PRUint32 -gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const +gfxGlyphExtents::GlyphWidths::ComputeSize() { PRUint32 i; - PRUint32 size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); + PRUint32 size = mBlocks.Capacity()*sizeof(PtrBits); for (i = 0; i < mBlocks.Length(); ++i) { PtrBits bits = mBlocks[i]; if (bits && !(bits & 0x1)) { - size += aMallocSizeOf(reinterpret_cast(bits)); + size += BLOCK_SIZE*sizeof(PRUint16); } } return size; } +#endif void gfxGlyphExtents::GlyphWidths::Set(PRUint32 aGlyphID, PRUint16 aWidth) @@ -2812,19 +2633,6 @@ gfxGlyphExtents::SetTightGlyphExtents(PRUint32 aGlyphID, const gfxRect& aExtents entry->height = aExtentsAppUnits.Height(); } -size_t -gfxGlyphExtents::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const -{ - return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + - mTightGlyphExtents.SizeOfExcludingThis(nsnull, aMallocSizeOf); -} - -size_t -gfxGlyphExtents::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const -{ - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); -} - gfxFontGroup::gfxFontGroup(const nsAString& aFamilies, const gfxFontStyle *aStyle, gfxUserFontSet *aUserFontSet) : mFamilies(aFamilies), mStyle(*aStyle), mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) { diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h index dd44ddd91d97..1a246a51c239 100644 --- a/gfx/thebes/gfxFont.h +++ b/gfx/thebes/gfxFont.h @@ -59,7 +59,6 @@ #include "nsISupportsImpl.h" #include "gfxPattern.h" #include "mozilla/HashFunctions.h" -#include "nsIMemoryReporter.h" typedef struct _cairo_scaled_font cairo_scaled_font_t; @@ -91,8 +90,6 @@ struct THEBES_API gfxFontFeature { // to features that select among multiple alternatives }; -struct FontListSizes; - inline bool operator<(const gfxFontFeature& a, const gfxFontFeature& b) { @@ -316,12 +313,6 @@ public: hb_blob_t *ShareFontTableAndGetBlob(PRUint32 aTag, FallibleTArray* aTable); - // For memory reporting - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - nsString mName; bool mItalic : 1; @@ -476,11 +467,6 @@ private: void Clear(); - static size_t - SizeOfEntryExcludingThis(FontTableHashEntry *aEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg); - private: static void DeleteFontTableBlobData(void *aBlobData); // not implemented @@ -647,12 +633,6 @@ public: // if so set the mIsSimpleFamily flag (defaults to False before we've checked) void CheckForSimpleFamily(); - // For memory reporter - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: // fills in an array with weights of faces that match style, // returns whether any matching entries found @@ -743,15 +723,6 @@ struct gfxTextRange { * completely, with all its words, and avoid the cost of aging the words * individually. That only happens with longer-lived fonts. */ -struct FontCacheSizes { - FontCacheSizes() - : mFontInstances(0), mShapedWords(0) - { } - - size_t mFontInstances; // memory used by instances of gfxFont subclasses - size_t mShapedWords; // memory used by the per-font shapedWord caches -}; - class THEBES_API gfxFontCache MOZ_FINAL : public nsExpirationTracker { public: enum { @@ -805,20 +776,7 @@ public: mFonts.EnumerateEntries(ClearCachedWordsForFont, nsnull); } - void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: - class MemoryReporter - : public nsIMemoryMultiReporter - { - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIMEMORYMULTIREPORTER - }; - void DestroyFont(gfxFont *aFont); static gfxFontCache *gGlobalCache; @@ -851,10 +809,6 @@ protected: gfxFont* mFont; }; - static size_t SizeOfFontEntryExcludingThis(HashEntry* aHashEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg); - nsTHashtable mFonts; static PLDHashOperator ClearCachedWordsForFont(HashEntry* aHashEntry, void*); @@ -1026,9 +980,6 @@ public: PRUint32 GetAppUnitsPerDevUnit() { return mAppUnitsPerDevUnit; } - size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; - size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const; - private: class HashEntry : public nsUint32HashKey { public: @@ -1065,7 +1016,9 @@ private: return widths[indexInBlock]; } - PRUint32 SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const; +#ifdef DEBUG + PRUint32 ComputeSize(); +#endif ~GlyphWidths(); @@ -1084,7 +1037,7 @@ private: nsTArray mBlocks; }; - + GlyphWidths mContainedGlyphWidths; nsTHashtable mTightGlyphExtents; PRUint32 mAppUnitsPerDevUnit; @@ -1521,11 +1474,6 @@ public: } } - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: // Call the appropriate shaper to generate glyphs for aText and store // them into aShapedWord. @@ -1610,11 +1558,6 @@ protected: nsAutoPtr mShapedWord; }; - static size_t - WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg); - nsTHashtable mWordCache; static PLDHashOperator AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData); diff --git a/gfx/thebes/gfxFontUtils.h b/gfx/thebes/gfxFontUtils.h index 3d6ba81f7d93..e7f853f4893a 100644 --- a/gfx/thebes/gfxFontUtils.h +++ b/gfx/thebes/gfxFontUtils.h @@ -264,18 +264,14 @@ public: } } - size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const { - size_t total = mBlocks.SizeOfExcludingThis(aMallocSizeOf); + PRUint32 GetSize() { + PRUint32 size = 0; for (PRUint32 i = 0; i < mBlocks.Length(); i++) { - if (mBlocks[i]) { - total += aMallocSizeOf(mBlocks[i]); - } + if (mBlocks[i]) + size += sizeof(Block); + size += sizeof(nsAutoPtr); } - return total; - } - - size_t SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const { - return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + return size; } // clear out all blocks in the array diff --git a/gfx/thebes/gfxGDIFont.cpp b/gfx/thebes/gfxGDIFont.cpp index 73c690209aed..b9b15b9b048b 100644 --- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -560,20 +560,3 @@ gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID) return -1; } - -void -gfxGDIFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); - aSizes->mFontInstances += aMallocSizeOf(mMetrics) + - mGlyphWidths.SizeOfExcludingThis(nsnull, aMallocSizeOf); -} - -void -gfxGDIFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxGDIFont.h b/gfx/thebes/gfxGDIFont.h index bdaf34b452ad..99dba1355928 100644 --- a/gfx/thebes/gfxGDIFont.h +++ b/gfx/thebes/gfxGDIFont.h @@ -88,11 +88,6 @@ public: // get hinted glyph width in pixels as 16.16 fixed-point value virtual PRInt32 GetGlyphWidth(gfxContext *aCtx, PRUint16 aGID); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: virtual void CreatePlatformShaper(); diff --git a/gfx/thebes/gfxGDIFontList.cpp b/gfx/thebes/gfxGDIFontList.cpp index c40304bf73f0..3230fa287fcb 100644 --- a/gfx/thebes/gfxGDIFontList.cpp +++ b/gfx/thebes/gfxGDIFontList.cpp @@ -246,8 +246,7 @@ GDIFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", - NS_ConvertUTF16toUTF8(mName).get(), - mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); + NS_ConvertUTF16toUTF8(mName).get(), mCharacterMap.GetSize())); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -460,14 +459,6 @@ GDIFontEntry::CreateFontEntry(const nsAString& aName, return fe; } -void -GDIFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - /*************************************************************** * * GDIFontFamily @@ -667,7 +658,7 @@ gfxGDIFontList::GetFontSubstitutes() nsAutoString substituteName; substituteName.AssignLiteral("Courier"); BuildKeyNameFromFontName(substituteName); - if (!mFontSubstitutes.GetWeak(substituteName)) { + if (!mFontSubstitutes.Get(substituteName)) { gfxFontFamily *ff; nsAutoString actualFontName; actualFontName.AssignLiteral("Courier New"); @@ -752,6 +743,7 @@ gfxFontEntry* gfxGDIFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, const nsAString& aFullname) { + bool found; gfxFontEntry *lookup; // initialize name lookup tables if needed @@ -760,8 +752,8 @@ gfxGDIFontList::LookupLocalFont(const gfxProxyFontEntry *aProxyEntry, } // lookup in name lookup tables, return null if not found - if (!(lookup = mPostscriptNames.GetWeak(aFullname)) && - !(lookup = mFullnames.GetWeak(aFullname))) + if (!(lookup = mPostscriptNames.GetWeak(aFullname, &found)) && + !(lookup = mFullnames.GetWeak(aFullname, &found))) { return nsnull; } @@ -1046,8 +1038,8 @@ gfxGDIFontList::ResolveFontName(const nsAString& aFontName, nsAString& aResolved nsAutoString keyName(aFontName); BuildKeyNameFromFontName(keyName); - gfxFontFamily *ff = mFontSubstitutes.GetWeak(keyName); - if (ff) { + nsRefPtr ff; + if (mFontSubstitutes.Get(keyName, &ff)) { aResolvedFontName = ff->Name(); return true; } @@ -1060,27 +1052,3 @@ gfxGDIFontList::ResolveFontName(const nsAString& aFontName, nsAString& aResolved return false; } - -void -gfxGDIFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - gfxPlatformFontList::SizeOfExcludingThis(aMallocSizeOf, aSizes); - aSizes->mFontListSize += - mFontSubstitutes.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, - aMallocSizeOf); - aSizes->mFontListSize += - mNonExistingFonts.SizeOfExcludingThis(aMallocSizeOf); - for (PRUint32 i = 0; i < mNonExistingFonts.Length(); ++i) { - aSizes->mFontListSize += - mNonExistingFonts[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); - } -} - -void -gfxGDIFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxGDIFontList.h b/gfx/thebes/gfxGDIFontList.h index 96834e023b53..320879b3d73b 100644 --- a/gfx/thebes/gfxGDIFontList.h +++ b/gfx/thebes/gfxGDIFontList.h @@ -274,9 +274,6 @@ public: virtual bool TestCharacterMap(PRUint32 aCh); - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - // create a font entry for a font with a given name static GDIFontEntry* CreateFontEntry(const nsAString& aName, gfxWindowsFontType aFontType, @@ -350,11 +347,6 @@ public: virtual bool ResolveFontName(const nsAString& aFontName, nsAString& aResolvedFontName); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - private: friend class gfxWindowsPlatform; @@ -369,7 +361,7 @@ private: DWORD fontType, LPARAM lParam); - typedef nsRefPtrHashtable FontTable; + typedef nsDataHashtable > FontTable; FontTable mFontSubstitutes; nsTArray mNonExistingFonts; diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp index b7744825a7bf..4407c04f9002 100644 --- a/gfx/thebes/gfxMacFont.cpp +++ b/gfx/thebes/gfxMacFont.cpp @@ -509,19 +509,3 @@ gfxMacFont::GetScaledFont() return mAzureFont; } -void -gfxMacFont::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - gfxFont::SizeOfExcludingThis(aMallocSizeOf, aSizes); - // mCGFont is shared with the font entry, so not counted here; - // and we don't have APIs to measure the cairo mFontFace object -} - -void -gfxMacFont::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const -{ - aSizes->mFontInstances += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h index 2f0ce551e178..6806cc582c92 100644 --- a/gfx/thebes/gfxMacFont.h +++ b/gfx/thebes/gfxMacFont.h @@ -81,11 +81,6 @@ public: mozilla::RefPtr GetScaledFont(); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontCacheSizes* aSizes) const; - protected: virtual void CreatePlatformShaper(); diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h index 91ec2c73080f..30d527b240e4 100644 --- a/gfx/thebes/gfxMacPlatformFontList.h +++ b/gfx/thebes/gfxMacPlatformFontList.h @@ -111,9 +111,6 @@ public: virtual nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: virtual bool HasFontTable(PRUint32 aTableTag); @@ -137,9 +134,6 @@ public: virtual nsresult GetFontTable(PRUint32 aTableTag, FallibleTArray& aBuffer); - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: virtual bool HasFontTable(PRUint32 aTableTag); }; diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm index 01a75ec7c0c5..65e78092118a 100644 --- a/gfx/thebes/gfxMacPlatformFontList.mm +++ b/gfx/thebes/gfxMacPlatformFontList.mm @@ -270,7 +270,7 @@ MacOSFontEntry::ReadCMAP() #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d\n", NS_ConvertUTF16toUTF8(mName).get(), - mCharacterMap.SizeOfExcludingThis(moz_malloc_size_of))); + mCharacterMap.GetSize())); if (LOG_CMAPDATA_ENABLED()) { char prefix[256]; sprintf(prefix, "(cmapdata) name: %.220s", @@ -398,14 +398,6 @@ ATSFontEntry::HasFontTable(PRUint32 aTableTag) (::ATSFontGetTable(fontRef, aTableTag, 0, 0, 0, &size) == noErr); } -void -ATSFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - /* CGFontEntry - used on Mac OS X 10.6+ */ #pragma mark- @@ -494,14 +486,6 @@ CGFontEntry::HasFontTable(PRUint32 aTableTag) return true; } -void -CGFontEntry::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} - /* gfxMacFontFamily */ #pragma mark- @@ -827,9 +811,10 @@ gfxMacPlatformFontList::InitSingleFaceList() #endif // add only if doesn't exist already - if (!mFontFamilies.GetWeak(key)) { - gfxFontFamily *familyEntry = - new gfxSingleFaceMacFontFamily(familyName); + bool found; + gfxFontFamily *familyEntry; + if (!(familyEntry = mFontFamilies.GetWeak(key, &found))) { + familyEntry = new gfxSingleFaceMacFontFamily(familyName); familyEntry->AddFontEntry(fontEntry); familyEntry->SetHasStyles(true); mFontFamilies.Put(key, familyEntry); diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp index 5c467b767681..b65675f8ad93 100644 --- a/gfx/thebes/gfxPlatformFontList.cpp +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -130,60 +130,6 @@ gfxFontListPrefObserver::Observe(nsISupports *aSubject, return NS_OK; } -NS_IMPL_ISUPPORTS1(gfxPlatformFontList::MemoryReporter, nsIMemoryMultiReporter) - -NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontListMallocSizeOf, "font-list") - -NS_IMETHODIMP -gfxPlatformFontList::MemoryReporter::GetName(nsACString &aName) -{ - aName.AssignLiteral("font-list"); - return NS_OK; -} - -NS_IMETHODIMP -gfxPlatformFontList::MemoryReporter::CollectReports - (nsIMemoryMultiReporterCallback* aCb, - nsISupports* aClosure) -{ - FontListSizes sizes; - - gfxPlatformFontList::PlatformFontList()->SizeOfIncludingThis(&FontListMallocSizeOf, - &sizes); - - aCb->Callback(EmptyCString(), - NS_LITERAL_CSTRING("explicit/gfx/font-list"), - nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, - sizes.mFontListSize, - NS_LITERAL_CSTRING("Memory used to manage the list of font families and faces."), - aClosure); - - aCb->Callback(EmptyCString(), - NS_LITERAL_CSTRING("explicit/gfx/font-charmaps"), - nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, - sizes.mCharMapsSize, - NS_LITERAL_CSTRING("Memory used to record the character coverage of individual fonts."), - aClosure); - - if (sizes.mFontTableCacheSize) { - aCb->Callback(EmptyCString(), - NS_LITERAL_CSTRING("explicit/gfx/font-tables"), - nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES, - sizes.mFontTableCacheSize, - NS_LITERAL_CSTRING("Memory used for cached font metrics and layout tables."), - aClosure); - } - - return NS_OK; -} - -NS_IMETHODIMP -gfxPlatformFontList::MemoryReporter::GetExplicitNonHeap(PRInt64* aAmount) -{ - // This reporter only measures heap memory. - *aAmount = 0; - return NS_OK; -} gfxPlatformFontList::gfxPlatformFontList(bool aNeedFullnamePostscriptNames) : mNeedFullnamePostscriptNames(aNeedFullnamePostscriptNames), @@ -238,8 +184,6 @@ gfxPlatformFontList::InitFontList() mCodepointsWithNoFonts.SetRange(0,0x1f); // C0 controls mCodepointsWithNoFonts.SetRange(0x7f,0x9f); // C1 controls - NS_RegisterMemoryMultiReporter(new MemoryReporter); - sPlatformFontList = this; return NS_OK; @@ -300,11 +244,12 @@ gfxPlatformFontList::PreloadNamesList() PRUint32 numFonts = preloadFonts.Length(); for (PRUint32 i = 0; i < numFonts; i++) { + bool found; nsAutoString key; GenerateFontListKey(preloadFonts[i], key); // only search canonical names! - gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key); + gfxFontFamily *familyEntry = mFontFamilies.GetWeak(key, &found); if (familyEntry) { familyEntry->ReadOtherFamilyNames(this); } @@ -604,17 +549,18 @@ gfxPlatformFontList::FindFamily(const nsAString& aFamily) { nsAutoString key; gfxFontFamily *familyEntry; + bool found; GenerateFontListKey(aFamily, key); NS_ASSERTION(mFontFamilies.Count() != 0, "system font list was not initialized correctly"); // lookup in canonical (i.e. English) family name list - if ((familyEntry = mFontFamilies.GetWeak(key))) { + if ((familyEntry = mFontFamilies.GetWeak(key, &found))) { return familyEntry; } // lookup in other family names list (mostly localized names) - if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nsnull) { + if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found)) != nsnull) { return familyEntry; } @@ -625,7 +571,7 @@ gfxPlatformFontList::FindFamily(const nsAString& aFamily) // in practice so avoid pulling in names at startup if (!mOtherFamilyNamesInitialized && !IsASCII(aFamily)) { InitOtherFamilyNames(); - if ((familyEntry = mOtherFamilyNames.GetWeak(key)) != nsnull) { + if ((familyEntry = mOtherFamilyNames.GetWeak(key, &found)) != nsnull) { return familyEntry; } } @@ -662,9 +608,10 @@ void gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& aOtherFamilyName) { nsAutoString key; + bool found; GenerateFontListKey(aOtherFamilyName, key); - if (!mOtherFamilyNames.GetWeak(key)) { + if (!mOtherFamilyNames.GetWeak(key, &found)) { mOtherFamilyNames.Put(key, aFamilyEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-otherfamily) canonical family: %s, " @@ -680,7 +627,9 @@ gfxPlatformFontList::AddOtherFamilyName(gfxFontFamily *aFamilyEntry, nsAString& void gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname) { - if (!mFullnames.GetWeak(aFullname)) { + bool found; + + if (!mFullnames.GetWeak(aFullname, &found)) { mFullnames.Put(aFullname, aFontEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-fullname) name: %s, fullname: %s\n", @@ -693,7 +642,9 @@ gfxPlatformFontList::AddFullname(gfxFontEntry *aFontEntry, nsAString& aFullname) void gfxPlatformFontList::AddPostscriptName(gfxFontEntry *aFontEntry, nsAString& aPostscriptName) { - if (!mPostscriptNames.GetWeak(aPostscriptName)) { + bool found; + + if (!mPostscriptNames.GetWeak(aPostscriptName, &found)) { mPostscriptNames.Put(aPostscriptName, aFontEntry); #ifdef PR_LOGGING LOG_FONTLIST(("(fontlist-postscript) name: %s, psname: %s\n", @@ -763,111 +714,3 @@ gfxPlatformFontList::FinishLoader() mFontFamiliesToLoad.Clear(); mNumFamilies = 0; } - -// Support for memory reporting - -static size_t -SizeOfFamilyEntryExcludingThis(const nsAString& aKey, - const nsRefPtr& aFamily, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - FontListSizes *sizes = static_cast(aUserArg); - aFamily->SizeOfExcludingThis(aMallocSizeOf, sizes); - - sizes->mFontListSize += aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - - // we return zero here because the measurements have been added directly - // to the relevant fields of the FontListSizes record - return 0; -} - -// this is also used by subclasses that hold additional hashes of family names -/*static*/ size_t -gfxPlatformFontList::SizeOfFamilyNameEntryExcludingThis - (const nsAString& aKey, - const nsRefPtr& aFamily, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - // we don't count the size of the family here, because this is an *extra* - // reference to a family that will have already been counted in the main list - return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); -} - -static size_t -SizeOfFontNameEntryExcludingThis(const nsAString& aKey, - const nsRefPtr& aFont, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - // the font itself is counted by its owning family; here we only care about - // the name stored in the hashtable key - return aKey.SizeOfExcludingThisIfUnshared(aMallocSizeOf); -} - -static size_t -SizeOfPrefFontEntryExcludingThis - (const PRUint32& aKey, - const nsTArray >& aList, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - // again, we only care about the size of the array itself; we don't follow - // the refPtrs stored in it, because they point to entries already owned - // and accounted-for by the main font list - return aList.SizeOfExcludingThis(aMallocSizeOf); -} - -static size_t -SizeOfStringEntryExcludingThis(nsStringHashKey* aHashEntry, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg) -{ - return aHashEntry->GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); -} - -void -gfxPlatformFontList::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += - mFontFamilies.SizeOfExcludingThis(SizeOfFamilyEntryExcludingThis, - aMallocSizeOf, aSizes); - - aSizes->mFontListSize += - mOtherFamilyNames.SizeOfExcludingThis(SizeOfFamilyNameEntryExcludingThis, - aMallocSizeOf); - - if (mNeedFullnamePostscriptNames) { - aSizes->mFontListSize += - mFullnames.SizeOfExcludingThis(SizeOfFontNameEntryExcludingThis, - aMallocSizeOf); - aSizes->mFontListSize += - mPostscriptNames.SizeOfExcludingThis(SizeOfFontNameEntryExcludingThis, - aMallocSizeOf); - } - - aSizes->mFontListSize += - mCodepointsWithNoFonts.SizeOfExcludingThis(aMallocSizeOf); - aSizes->mFontListSize += - mReplacementCharFallbackFamily.SizeOfExcludingThisIfUnshared(aMallocSizeOf); - aSizes->mFontListSize += - mFontFamiliesToLoad.SizeOfExcludingThis(aMallocSizeOf); - - aSizes->mFontListSize += - mPrefFonts.SizeOfExcludingThis(SizeOfPrefFontEntryExcludingThis, - aMallocSizeOf); - - aSizes->mFontListSize += - mBadUnderlineFamilyNames.SizeOfExcludingThis(SizeOfStringEntryExcludingThis, - aMallocSizeOf); -} - -void -gfxPlatformFontList::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const -{ - aSizes->mFontListSize += aMallocSizeOf(this); - SizeOfExcludingThis(aMallocSizeOf, aSizes); -} diff --git a/gfx/thebes/gfxPlatformFontList.h b/gfx/thebes/gfxPlatformFontList.h index e1cdc36c9025..59509552660d 100644 --- a/gfx/thebes/gfxPlatformFontList.h +++ b/gfx/thebes/gfxPlatformFontList.h @@ -47,7 +47,6 @@ #include "gfxFont.h" #include "gfxPlatform.h" -#include "nsIMemoryReporter.h" #include "mozilla/FunctionTimer.h" // gfxPlatformFontList is an abstract class for the global font list on the system; @@ -58,18 +57,6 @@ // Much of this is based on the old gfxQuartzFontCache, but adapted for use on all platforms. -struct FontListSizes { - FontListSizes() - : mFontListSize(0), mFontTableCacheSize(0), mCharMapsSize(0) - { } - - size_t mFontListSize; // size of the font list and dependent objects - // (font family and face names, etc), but NOT - // including the font table cache and the cmaps - size_t mFontTableCacheSize; // memory used for the gfxFontEntry table caches - size_t mCharMapsSize; // memory used for cmap coverage info -}; - class gfxPlatformFontList : protected gfxFontInfoLoader { public: @@ -154,20 +141,7 @@ public: // (platforms may override, eg Mac) virtual bool GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName); - virtual void SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - virtual void SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf, - FontListSizes* aSizes) const; - protected: - class MemoryReporter - : public nsIMemoryMultiReporter - { - public: - NS_DECL_ISUPPORTS - NS_DECL_NSIMEMORYMULTIREPORTER - }; - gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true); static gfxPlatformFontList *sPlatformFontList; @@ -226,13 +200,6 @@ protected: virtual bool RunLoader(); virtual void FinishLoader(); - // used by memory reporter to accumulate sizes of family names in the hash - static size_t - SizeOfFamilyNameEntryExcludingThis(const nsAString& aKey, - const nsRefPtr& aFamily, - nsMallocSizeOfFun aMallocSizeOf, - void* aUserArg); - // canonical family name ==> family entry (unique, one name per family entry) nsRefPtrHashtable mFontFamilies; diff --git a/xpcom/glue/nsBaseHashtable.h b/xpcom/glue/nsBaseHashtable.h index 4d0ec87ee37d..6ab8590865a9 100644 --- a/xpcom/glue/nsBaseHashtable.h +++ b/xpcom/glue/nsBaseHashtable.h @@ -272,17 +272,16 @@ public: * @return the summed size of all the entries */ size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, - nsMallocSizeOfFun mallocSizeOf, void *userArg = nsnull) const + nsMallocSizeOfFun mallocSizeOf, void *userArg = nsnull) { - if (!IsInitialized()) { - return 0; - } - if (sizeOfEntryExcludingThis) { + if (IsInitialized()) { s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; - return PL_DHashTableSizeOfExcludingThis(&this->mTable, s_SizeOfStub, - mallocSizeOf, &args); + return PL_DHashTableSizeOfExcludingThis(&this->mTable, + s_SizeOfStub, + mallocSizeOf, + &args); } - return PL_DHashTableSizeOfExcludingThis(&this->mTable, NULL, mallocSizeOf); + return 0; } protected: diff --git a/xpcom/glue/nsTHashtable.h b/xpcom/glue/nsTHashtable.h index e2a001358586..762517fb0aa3 100644 --- a/xpcom/glue/nsTHashtable.h +++ b/xpcom/glue/nsTHashtable.h @@ -288,14 +288,11 @@ public: size_t SizeOfExcludingThis(SizeOfEntryExcludingThisFun sizeOfEntryExcludingThis, nsMallocSizeOfFun mallocSizeOf, void *userArg = NULL) const { - if (!IsInitialized()) { - return 0; - } - if (sizeOfEntryExcludingThis) { + if (IsInitialized()) { s_SizeOfArgs args = { sizeOfEntryExcludingThis, userArg }; return PL_DHashTableSizeOfExcludingThis(&mTable, s_SizeOfStub, mallocSizeOf, &args); } - return PL_DHashTableSizeOfExcludingThis(&mTable, NULL, mallocSizeOf); + return 0; } #ifdef DEBUG From bab53c603a9326160353035fdea4584dfc8965c6 Mon Sep 17 00:00:00 2001 From: Eric Chou Date: Fri, 23 Mar 2012 14:18:03 -0700 Subject: [PATCH 062/109] Bug 730990 - Device object boilerplate for DOM Bluetooth - r=qDot --- dom/base/nsDOMClassInfo.cpp | 7 +++++++ dom/base/nsDOMClassInfoClasses.h | 1 + dom/bluetooth/Makefile.in | 2 ++ 3 files changed, 10 insertions(+) diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index de9847d95333..d8787f7532c1 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -526,6 +526,7 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #ifdef MOZ_B2G_BT #include "BluetoothAdapter.h" +#include "BluetoothDevice.h" #endif #include "DOMError.h" @@ -1621,6 +1622,8 @@ static nsDOMClassInfoData sClassInfoData[] = { #ifdef MOZ_B2G_BT NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH, EVENTTARGET_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH, + EVENTTARGET_SCRIPTABLE_FLAGS) #endif NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, @@ -4374,6 +4377,10 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_BEGIN(BluetoothAdapter, nsIDOMBluetoothAdapter) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice) DOM_CLASSINFO_MAP_END #endif diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index da29bb7aca9b..e63173b57302 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -541,6 +541,7 @@ DOMCI_CLASS(CallEvent) #ifdef MOZ_B2G_BT DOMCI_CLASS(BluetoothAdapter) +DOMCI_CLASS(BluetoothDevice) #endif DOMCI_CLASS(DOMError) diff --git a/dom/bluetooth/Makefile.in b/dom/bluetooth/Makefile.in index 7d782e5356c9..46860f2af568 100644 --- a/dom/bluetooth/Makefile.in +++ b/dom/bluetooth/Makefile.in @@ -19,11 +19,13 @@ include $(topsrcdir)/dom/dom-config.mk CPPSRCS = \ BluetoothAdapter.cpp \ + BluetoothDevice.cpp \ $(NULL) XPIDLSRCS = \ nsIDOMNavigatorBluetooth.idl \ nsIDOMBluetoothAdapter.idl \ + nsIDOMBluetoothDevice.idl \ $(NULL) include $(topsrcdir)/config/rules.mk From 9736c0ff22178cb79fabe51be2ae1c6f0cf7a742 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 23 Mar 2012 14:45:29 -0700 Subject: [PATCH 063/109] Backed out changeset a44ac6895a2f --- dom/base/nsDOMClassInfo.cpp | 7 ------- dom/base/nsDOMClassInfoClasses.h | 1 - dom/bluetooth/Makefile.in | 2 -- 3 files changed, 10 deletions(-) diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index d8787f7532c1..de9847d95333 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -526,7 +526,6 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #ifdef MOZ_B2G_BT #include "BluetoothAdapter.h" -#include "BluetoothDevice.h" #endif #include "DOMError.h" @@ -1622,8 +1621,6 @@ static nsDOMClassInfoData sClassInfoData[] = { #ifdef MOZ_B2G_BT NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH, EVENTTARGET_SCRIPTABLE_FLAGS) - NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH, - EVENTTARGET_SCRIPTABLE_FLAGS) #endif NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, @@ -4377,10 +4374,6 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_BEGIN(BluetoothAdapter, nsIDOMBluetoothAdapter) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter) - DOM_CLASSINFO_MAP_END - - DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice) DOM_CLASSINFO_MAP_END #endif diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index e63173b57302..da29bb7aca9b 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -541,7 +541,6 @@ DOMCI_CLASS(CallEvent) #ifdef MOZ_B2G_BT DOMCI_CLASS(BluetoothAdapter) -DOMCI_CLASS(BluetoothDevice) #endif DOMCI_CLASS(DOMError) diff --git a/dom/bluetooth/Makefile.in b/dom/bluetooth/Makefile.in index 46860f2af568..7d782e5356c9 100644 --- a/dom/bluetooth/Makefile.in +++ b/dom/bluetooth/Makefile.in @@ -19,13 +19,11 @@ include $(topsrcdir)/dom/dom-config.mk CPPSRCS = \ BluetoothAdapter.cpp \ - BluetoothDevice.cpp \ $(NULL) XPIDLSRCS = \ nsIDOMNavigatorBluetooth.idl \ nsIDOMBluetoothAdapter.idl \ - nsIDOMBluetoothDevice.idl \ $(NULL) include $(topsrcdir)/config/rules.mk From 8713b9faa2878a0f803a6633ee9f7354384053be Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 23 Mar 2012 14:50:47 -0700 Subject: [PATCH 064/109] Backing out a44ac6895a2f --- dom/base/nsDOMClassInfo.cpp | 7 ------- dom/base/nsDOMClassInfoClasses.h | 1 - dom/bluetooth/Makefile.in | 2 -- 3 files changed, 10 deletions(-) diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index d8787f7532c1..de9847d95333 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -526,7 +526,6 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #ifdef MOZ_B2G_BT #include "BluetoothAdapter.h" -#include "BluetoothDevice.h" #endif #include "DOMError.h" @@ -1622,8 +1621,6 @@ static nsDOMClassInfoData sClassInfoData[] = { #ifdef MOZ_B2G_BT NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH, EVENTTARGET_SCRIPTABLE_FLAGS) - NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH, - EVENTTARGET_SCRIPTABLE_FLAGS) #endif NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, @@ -4377,10 +4374,6 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_BEGIN(BluetoothAdapter, nsIDOMBluetoothAdapter) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter) - DOM_CLASSINFO_MAP_END - - DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice) DOM_CLASSINFO_MAP_END #endif diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index e63173b57302..da29bb7aca9b 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -541,7 +541,6 @@ DOMCI_CLASS(CallEvent) #ifdef MOZ_B2G_BT DOMCI_CLASS(BluetoothAdapter) -DOMCI_CLASS(BluetoothDevice) #endif DOMCI_CLASS(DOMError) diff --git a/dom/bluetooth/Makefile.in b/dom/bluetooth/Makefile.in index 46860f2af568..7d782e5356c9 100644 --- a/dom/bluetooth/Makefile.in +++ b/dom/bluetooth/Makefile.in @@ -19,13 +19,11 @@ include $(topsrcdir)/dom/dom-config.mk CPPSRCS = \ BluetoothAdapter.cpp \ - BluetoothDevice.cpp \ $(NULL) XPIDLSRCS = \ nsIDOMNavigatorBluetooth.idl \ nsIDOMBluetoothAdapter.idl \ - nsIDOMBluetoothDevice.idl \ $(NULL) include $(topsrcdir)/config/rules.mk From 2da532b4e88f16a9ffe7c463df4146a65fd5fdee Mon Sep 17 00:00:00 2001 From: Eric Chou Date: Fri, 23 Mar 2012 14:58:12 -0700 Subject: [PATCH 065/109] Bug 730990 - Device object boilerplate for DOM Bluetooth - r=qDot --- dom/base/nsDOMClassInfo.cpp | 7 ++ dom/base/nsDOMClassInfoClasses.h | 1 + dom/bluetooth/BluetoothDevice.cpp | 154 ++++++++++++++++++++++++ dom/bluetooth/BluetoothDevice.h | 48 ++++++++ dom/bluetooth/Makefile.in | 2 + dom/bluetooth/nsIDOMBluetoothDevice.idl | 30 +++++ 6 files changed, 242 insertions(+) create mode 100644 dom/bluetooth/BluetoothDevice.cpp create mode 100644 dom/bluetooth/BluetoothDevice.h create mode 100644 dom/bluetooth/nsIDOMBluetoothDevice.idl diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index de9847d95333..d8787f7532c1 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -526,6 +526,7 @@ using mozilla::dom::indexedDB::IDBWrapperCache; #ifdef MOZ_B2G_BT #include "BluetoothAdapter.h" +#include "BluetoothDevice.h" #endif #include "DOMError.h" @@ -1621,6 +1622,8 @@ static nsDOMClassInfoData sClassInfoData[] = { #ifdef MOZ_B2G_BT NS_DEFINE_CLASSINFO_DATA(BluetoothAdapter, nsEventTargetSH, EVENTTARGET_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH, + EVENTTARGET_SCRIPTABLE_FLAGS) #endif NS_DEFINE_CLASSINFO_DATA(DOMError, nsDOMGenericSH, @@ -4374,6 +4377,10 @@ nsDOMClassInfo::Init() #ifdef MOZ_B2G_BT DOM_CLASSINFO_MAP_BEGIN(BluetoothAdapter, nsIDOMBluetoothAdapter) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothAdapter) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice) DOM_CLASSINFO_MAP_END #endif diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index da29bb7aca9b..e63173b57302 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -541,6 +541,7 @@ DOMCI_CLASS(CallEvent) #ifdef MOZ_B2G_BT DOMCI_CLASS(BluetoothAdapter) +DOMCI_CLASS(BluetoothDevice) #endif DOMCI_CLASS(DOMError) diff --git a/dom/bluetooth/BluetoothDevice.cpp b/dom/bluetooth/BluetoothDevice.cpp new file mode 100644 index 000000000000..8cd741e9408c --- /dev/null +++ b/dom/bluetooth/BluetoothDevice.cpp @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BluetoothDevice.h" +#include "nsDOMClassInfo.h" + +USING_BLUETOOTH_NAMESPACE + +DOMCI_DATA(BluetoothDevice, BluetoothDevice) + +NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothDevice) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothDevice, + nsDOMEventTargetHelper) + NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(propertychanged) + NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(disconnectrequested) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothDevice, + nsDOMEventTargetHelper) + NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(propertychanged) + NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(disconnectrequested) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDevice) + NS_INTERFACE_MAP_ENTRY(nsIDOMBluetoothDevice) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMBluetoothDevice) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(BluetoothDevice) +NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(BluetoothDevice, nsDOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(BluetoothDevice, nsDOMEventTargetHelper) + +BluetoothDevice::BluetoothDevice() +{ +} + +nsresult +BluetoothDevice::GetAdapter(nsAString& aAdapter) +{ + aAdapter = mAdapter; + return NS_OK; +} + +nsresult +BluetoothDevice::GetAddress(nsAString& aAddress) +{ + aAddress = mAddress; + return NS_OK; +} + +nsresult +BluetoothDevice::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +nsresult +BluetoothDevice::GetClass(PRUint32* aClass) +{ + *aClass = mClass; + return NS_OK; +} + +nsresult +BluetoothDevice::GetConnected(bool* aConnected) +{ + *aConnected = mConnected; + return NS_OK; +} + +nsresult +BluetoothDevice::GetPaired(bool* aPaired) +{ + *aPaired = mPaired; + return NS_OK; +} + +nsresult +BluetoothDevice::GetLegacyPairing(bool* aLegacyPairing) +{ + *aLegacyPairing = mLegacyPairing; + return NS_OK; +} + +nsresult +BluetoothDevice::GetTrusted(bool* aTrusted) +{ + *aTrusted = mTrusted; + return NS_OK; +} + +nsresult +BluetoothDevice::SetTrusted(bool aTrusted) +{ + mTrusted = aTrusted; + return NS_OK; +} + +nsresult +BluetoothDevice::GetAlias(nsAString& aAlias) +{ + aAlias = mAlias; + return NS_OK; +} + +nsresult +BluetoothDevice::SetAlias(const nsAString& aAlias) +{ + mAlias = aAlias; + return NS_OK; +} + +nsresult +BluetoothDevice::GetUuids(jsval* aUuids) +{ + //TODO: convert mUuids to jsval and assign to aUuids; + return NS_OK; +} + +nsresult +BluetoothDevice::Disconnect() +{ + return NS_OK; +} + +nsresult +BluetoothDevice::GetProperties(jsval* aProperties) +{ + return NS_OK; +} + +nsresult +BluetoothDevice::SetProperty(const nsAString& aName, const nsAString& aValue) +{ + return NS_OK; +} + +nsresult +BluetoothDevice::DiscoverServices(const nsAString& aPattern, jsval* aServices) +{ + return NS_OK; +} + +nsresult +BluetoothDevice::CancelDiscovery() +{ + return NS_OK; +} + +NS_IMPL_EVENT_HANDLER(BluetoothDevice, propertychanged) +NS_IMPL_EVENT_HANDLER(BluetoothDevice, disconnectrequested) diff --git a/dom/bluetooth/BluetoothDevice.h b/dom/bluetooth/BluetoothDevice.h new file mode 100644 index 000000000000..d0d45430576c --- /dev/null +++ b/dom/bluetooth/BluetoothDevice.h @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_bluetooth_bluetoothdevice_h__ +#define mozilla_dom_bluetooth_bluetoothdevice_h__ + +#include "BluetoothCommon.h" +#include "nsDOMEventTargetHelper.h" +#include "nsIDOMBluetoothDevice.h" +#include "nsString.h" + +BEGIN_BLUETOOTH_NAMESPACE + +class BluetoothDevice : public nsIDOMBluetoothDevice + , public nsDOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMBLUETOOTHDEVICE + + NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::) + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDevice, + nsDOMEventTargetHelper) + + BluetoothDevice(); + +protected: + nsString mAdapter; + nsString mAddress; + PRUint32 mClass; + bool mConnected; + bool mLegacyPairing; + nsString mName; + bool mPaired; + nsTArray mUuids; + + bool mTrusted; + nsString mAlias; + + NS_DECL_EVENT_HANDLER(propertychanged) + NS_DECL_EVENT_HANDLER(disconnectrequested) +}; + +END_BLUETOOTH_NAMESPACE + +#endif diff --git a/dom/bluetooth/Makefile.in b/dom/bluetooth/Makefile.in index 7d782e5356c9..46860f2af568 100644 --- a/dom/bluetooth/Makefile.in +++ b/dom/bluetooth/Makefile.in @@ -19,11 +19,13 @@ include $(topsrcdir)/dom/dom-config.mk CPPSRCS = \ BluetoothAdapter.cpp \ + BluetoothDevice.cpp \ $(NULL) XPIDLSRCS = \ nsIDOMNavigatorBluetooth.idl \ nsIDOMBluetoothAdapter.idl \ + nsIDOMBluetoothDevice.idl \ $(NULL) include $(topsrcdir)/config/rules.mk diff --git a/dom/bluetooth/nsIDOMBluetoothDevice.idl b/dom/bluetooth/nsIDOMBluetoothDevice.idl new file mode 100644 index 000000000000..ae79dded421d --- /dev/null +++ b/dom/bluetooth/nsIDOMBluetoothDevice.idl @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIDOMEventTarget.idl" + +[scriptable, builtinclass, uuid(2da61f89-a7d2-4f7d-9f4c-fb8a99d9add2)] +interface nsIDOMBluetoothDevice : nsIDOMEventTarget +{ + readonly attribute DOMString adapter; + readonly attribute DOMString address; + readonly attribute unsigned long class; + readonly attribute boolean connected; + readonly attribute boolean legacyPairing; + readonly attribute DOMString name; + readonly attribute boolean paired; + readonly attribute jsval uuids; + + attribute boolean trusted; + attribute DOMString alias; + + attribute nsIDOMEventListener onpropertychanged; + attribute nsIDOMEventListener ondisconnectrequested; + + void disconnect(); + jsval getProperties(); + void setProperty(in DOMString name, in DOMString value); + jsval discoverServices(in DOMString pattern); + void cancelDiscovery(); +}; From 0bc1eebe87d6b52a624e9da6d289e622b9c00883 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:04 -0700 Subject: [PATCH 066/109] Bug 733984 - Stop specializing createHolder, and simplify holder creation in WrapperFactory::Rewrap. r=mrbkap --- js/xpconnect/wrappers/WrapperFactory.cpp | 26 ++++------ js/xpconnect/wrappers/XrayWrapper.cpp | 61 +++++++++++++----------- js/xpconnect/wrappers/XrayWrapper.h | 4 +- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 7babdfbbea8b..be799fceb852 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -287,7 +287,7 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO JSCompartment *origin = js::GetObjectCompartment(obj); JSCompartment *target = js::GetContextCompartment(cx); - JSObject *xrayHolder = nsnull; + bool usingXray = false; Wrapper *wrapper; CompartmentPrivate *targetdata = @@ -320,10 +320,8 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO wrapper = &XrayProxy::singleton; } else { typedef XrayWrapper Xray; + usingXray = true; wrapper = &Xray::singleton; - xrayHolder = Xray::createHolder(cx, obj, parent); - if (!xrayHolder) - return nsnull; } } else { wrapper = &NoWaiverWrapper::singleton; @@ -344,11 +342,9 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO (wn = GetWrappedNative(cx, obj)) && wn->HasProto() && wn->GetProto()->ClassIsDOMObject()) { typedef XrayWrapper Xray; + usingXray = true; wrapper = &FilteringWrapper::singleton; - xrayHolder = Xray::createHolder(cx, obj, parent); - if (!xrayHolder) - return nsnull; } else { wrapper = &FilteringWrapper::singleton; @@ -365,10 +361,8 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO wrapper = &XrayProxy::singleton; } else { typedef XrayWrapper Xray; + usingXray = true; wrapper = &Xray::singleton; - xrayHolder = Xray::createHolder(cx, obj, parent); - if (!xrayHolder) - return nsnull; } } else { wrapper = &CrossCompartmentWrapper::singleton; @@ -391,6 +385,7 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO CrossOriginAccessiblePropertiesOnly>::singleton; } else { typedef XrayWrapper Xray; + usingXray = true; // Location objects can become same origin after navigation, so we might // have to grant transparent access later on. @@ -401,18 +396,17 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO wrapper = &FilteringWrapper::singleton; } - - xrayHolder = Xray::createHolder(cx, obj, parent); - if (!xrayHolder) - return nsnull; } } } JSObject *wrapperObj = Wrapper::New(cx, obj, wrappedProto, parent, wrapper); - if (!wrapperObj || !xrayHolder) + if (!wrapperObj || !usingXray) return wrapperObj; + JSObject *xrayHolder = XrayUtils::createHolder(cx, obj, parent); + if (!xrayHolder) + return nsnull; js::SetProxyExtra(wrapperObj, 0, js::ObjectValue(*xrayHolder)); return wrapperObj; } @@ -430,7 +424,7 @@ WrapperFactory::IsLocationObject(JSObject *obj) JSObject * WrapperFactory::WrapLocationObject(JSContext *cx, JSObject *obj) { - JSObject *xrayHolder = LW::createHolder(cx, obj, js::GetObjectParent(obj)); + JSObject *xrayHolder = XrayUtils::createHolder(cx, obj, js::GetObjectParent(obj)); if (!xrayHolder) return nsnull; JSObject *wrapperObj = Wrapper::New(cx, obj, js::GetObjectProto(obj), js::GetObjectParent(obj), diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp index 7a6b738e2696..e7781bd0a345 100644 --- a/js/xpconnect/wrappers/XrayWrapper.cpp +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -103,6 +103,9 @@ holder_get(JSContext *cx, JSObject *holder, jsid id, jsval *vp); static JSBool holder_set(JSContext *cx, JSObject *holder, jsid id, JSBool strict, jsval *vp); + +static XPCWrappedNative *GetWrappedNative(JSObject *obj); + namespace XrayUtils { JSClass HolderClass = { @@ -112,6 +115,35 @@ JSClass HolderClass = { JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub }; +JSObject * +createHolder(JSContext *cx, JSObject *wrappedNative, JSObject *parent) +{ + JSObject *holder = JS_NewObjectWithGivenProto(cx, &HolderClass, nsnull, parent); + if (!holder) + return nsnull; + + CompartmentPrivate *priv = + (CompartmentPrivate *)JS_GetCompartmentPrivate(js::GetObjectCompartment(holder)); + JSObject *inner = JS_ObjectToInnerObject(cx, wrappedNative); + XPCWrappedNative *wn = GetWrappedNative(inner); + Value expando = ObjectOrNullValue(priv->LookupExpandoObject(wn)); + + // A note about ownership: the holder has a direct pointer to the wrapped + // native that we're wrapping. Normally, we'd have to AddRef the pointer + // so that it doesn't have to be collected, but then we'd have to tell the + // cycle collector. Fortunately for us, we know that the Xray wrapper + // itself has a reference to the flat JS object which will hold the + // wrapped native alive. Furthermore, the reachability of that object and + // the associated holder are exactly the same, so we can use that for our + // strong reference. + JS_ASSERT(IS_WN_WRAPPER(wrappedNative) || + js::GetObjectClass(wrappedNative)->ext.innerObject); + js::SetReservedSlot(holder, JSSLOT_WN, PrivateValue(wn)); + js::SetReservedSlot(holder, JSSLOT_RESOLVING, PrivateValue(NULL)); + js::SetReservedSlot(holder, JSSLOT_EXPANDO, expando); + return holder; +} + } using namespace XrayUtils; @@ -1042,35 +1074,6 @@ XrayWrapper::construct(JSContext *cx, JSObject *wrapper, unsigned argc, return true; } -template -JSObject * -XrayWrapper::createHolder(JSContext *cx, JSObject *wrappedNative, JSObject *parent) -{ - JSObject *holder = JS_NewObjectWithGivenProto(cx, &HolderClass, nsnull, parent); - if (!holder) - return nsnull; - - CompartmentPrivate *priv = - (CompartmentPrivate *)JS_GetCompartmentPrivate(js::GetObjectCompartment(holder)); - JSObject *inner = JS_ObjectToInnerObject(cx, wrappedNative); - XPCWrappedNative *wn = GetWrappedNative(inner); - Value expando = ObjectOrNullValue(priv->LookupExpandoObject(wn)); - - // A note about ownership: the holder has a direct pointer to the wrapped - // native that we're wrapping. Normally, we'd have to AddRef the pointer - // so that it doesn't have to be collected, but then we'd have to tell the - // cycle collector. Fortunately for us, we know that the Xray wrapper - // itself has a reference to the flat JS object which will hold the - // wrapped native alive. Furthermore, the reachability of that object and - // the associated holder are exactly the same, so we can use that for our - // strong reference. - JS_ASSERT(IS_WN_WRAPPER(wrappedNative) || - js::GetObjectClass(wrappedNative)->ext.innerObject); - js::SetReservedSlot(holder, JSSLOT_WN, PrivateValue(wn)); - js::SetReservedSlot(holder, JSSLOT_RESOLVING, PrivateValue(NULL)); - js::SetReservedSlot(holder, JSSLOT_EXPANDO, expando); - return holder; -} XrayProxy::XrayProxy(unsigned flags) : XrayWrapper(flags) diff --git a/js/xpconnect/wrappers/XrayWrapper.h b/js/xpconnect/wrappers/XrayWrapper.h index d9bd4ab592b7..18de57789439 100644 --- a/js/xpconnect/wrappers/XrayWrapper.h +++ b/js/xpconnect/wrappers/XrayWrapper.h @@ -52,6 +52,8 @@ namespace XrayUtils { extern JSClass HolderClass; +JSObject *createHolder(JSContext *cx, JSObject *wrappedNative, JSObject *parent); + bool IsTransparent(JSContext *cx, JSObject *wrapper); @@ -94,8 +96,6 @@ class XrayWrapper : public Base { virtual bool construct(JSContext *cx, JSObject *wrapper, unsigned argc, js::Value *argv, js::Value *rval); - static JSObject *createHolder(JSContext *cx, JSObject *wrappedNative, JSObject *parent); - static XrayWrapper singleton; private: From 61b04fb74295f25e718dc728cb16a89f9febba9e Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:07 -0700 Subject: [PATCH 067/109] Bug 733984 - Clarify the security characteristics of Location objects. r=mrbkap I was getting confused by some of the naming and lack of comments here. --- js/xpconnect/wrappers/AccessCheck.cpp | 13 +++++++-- js/xpconnect/wrappers/AccessCheck.h | 34 ++++++++++++++++++++-- js/xpconnect/wrappers/FilteringWrapper.cpp | 4 +-- js/xpconnect/wrappers/WrapperFactory.cpp | 7 ++--- js/xpconnect/wrappers/WrapperFactory.h | 5 ++++ 5 files changed, 50 insertions(+), 13 deletions(-) diff --git a/js/xpconnect/wrappers/AccessCheck.cpp b/js/xpconnect/wrappers/AccessCheck.cpp index 9e17cf49a8a9..007b6535b146 100644 --- a/js/xpconnect/wrappers/AccessCheck.cpp +++ b/js/xpconnect/wrappers/AccessCheck.cpp @@ -49,7 +49,6 @@ #include "XPCWrapper.h" #include "XrayWrapper.h" #include "FilteringWrapper.h" -#include "WrapperFactory.h" #include "jsfriendapi.h" @@ -89,6 +88,9 @@ AccessCheck::isSameOrigin(JSCompartment *a, JSCompartment *b) bool AccessCheck::isLocationObjectSameOrigin(JSContext *cx, JSObject *wrapper) { + // The caller must ensure that the given wrapper wraps a Location object. + MOZ_ASSERT(WrapperFactory::IsLocationObject(js::UnwrapObject(wrapper))); + // Location objects are parented to the outer window for which they // were created. This gives us an easy way to determine whether our // object is same origin with the current inner window: @@ -320,8 +322,13 @@ AccessCheck::isCrossOriginAccessPermitted(JSContext *cx, JSObject *wrapper, jsid if (IsWindow(name) && IsFrameId(cx, obj, id)) return true; - // We only reach this point for cross origin location objects (see - // SameOriginOrCrossOriginAccessiblePropertiesOnly::check). + // Do the dynamic document.domain check. + // + // Location also needs a dynamic access check, but it's a different one, and + // we do it in LocationPolicy::check. Before LocationPolicy::check does that + // though, it first calls this function to check whether the property is + // accessible to anyone regardless of origin. So make sure not to do the + // document.domain check in that case. if (!IsLocation(name) && documentDomainMakesSameOrigin(cx, obj)) return true; diff --git a/js/xpconnect/wrappers/AccessCheck.h b/js/xpconnect/wrappers/AccessCheck.h index 65d74d9d0d08..065821dd9883 100644 --- a/js/xpconnect/wrappers/AccessCheck.h +++ b/js/xpconnect/wrappers/AccessCheck.h @@ -39,6 +39,7 @@ #include "jsapi.h" #include "jswrapper.h" +#include "WrapperFactory.h" class nsIPrincipal; @@ -105,6 +106,9 @@ struct OnlyIfSubjectIsSystem : public Policy { struct CrossOriginAccessiblePropertiesOnly : public Policy { static bool check(JSContext *cx, JSObject *wrapper, jsid id, js::Wrapper::Action act, Permission &perm) { + // Location objects should always use LocationPolicy. + MOZ_ASSERT(!WrapperFactory::IsLocationObject(js::UnwrapObject(wrapper))); + if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act)) { perm = PermitPropertyAccess; return true; @@ -118,11 +122,35 @@ struct CrossOriginAccessiblePropertiesOnly : public Policy { } }; -// This policy only permits access to properties that are safe to be used -// across origins. -struct SameOriginOrCrossOriginAccessiblePropertiesOnly : public Policy { +// We need a special security policy for Location objects. +// +// Location objects are special because their effective principal is that of +// the outer window, not the inner window. So while the security characteristics +// of most objects can be inferred from their compartments, those of the Location +// object cannot. This has two implications: +// +// 1 - Same-compartment access of Location objects is not necessarily allowed. +// This means that objects must see a security wrapper around Location objects +// in their own compartment. +// 2 - Cross-origin access of Location objects is not necessarily forbidden. +// Since the security decision depends on the current state of the outer window, +// we can't make it at wrap time. Instead, we need to make it at the time of +// access. +// +// So for any Location object access, be it same-compartment or cross-compartment, +// we need to do a dynamic security check to determine whether the outer window is +// same-origin with the caller. +// +// So this policy first checks whether the access is something that any code, +// same-origin or not, is allowed to make. If it isn't, it _also_ checks the +// state of the outer window to determine whether we happen to be same-origin +// at the moment. +struct LocationPolicy : public Policy { static bool check(JSContext *cx, JSObject *wrapper, jsid id, js::Wrapper::Action act, Permission &perm) { + // We should only be dealing with Location objects here. + MOZ_ASSERT(WrapperFactory::IsLocationObject(js::UnwrapObject(wrapper))); + if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act) || AccessCheck::isLocationObjectSameOrigin(cx, wrapper)) { perm = PermitPropertyAccess; diff --git a/js/xpconnect/wrappers/FilteringWrapper.cpp b/js/xpconnect/wrappers/FilteringWrapper.cpp index 558310e3d5f5..8dbc2c7db80a 100644 --- a/js/xpconnect/wrappers/FilteringWrapper.cpp +++ b/js/xpconnect/wrappers/FilteringWrapper.cpp @@ -146,9 +146,9 @@ FilteringWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id, #define NNXOW FilteringWrapper #define LW FilteringWrapper, \ - SameOriginOrCrossOriginAccessiblePropertiesOnly> + LocationPolicy> #define XLW FilteringWrapper, \ - SameOriginOrCrossOriginAccessiblePropertiesOnly> + LocationPolicy> template<> SOW SOW::singleton(WrapperFactory::SCRIPT_ACCESS_ONLY_FLAG | WrapperFactory::SOW_FLAG); diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index be799fceb852..7ddafe18ac8d 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -37,7 +37,6 @@ * * ***** END LICENSE BLOCK ***** */ -#include "WrapperFactory.h" #include "CrossOriginWrapper.h" #include "FilteringWrapper.h" #include "XrayWrapper.h" @@ -390,8 +389,7 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO // Location objects can become same origin after navigation, so we might // have to grant transparent access later on. if (IsLocationObject(obj)) { - wrapper = &FilteringWrapper::singleton; + wrapper = &FilteringWrapper::singleton; } else { wrapper = &FilteringWrapper::singleton; @@ -411,8 +409,7 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO return wrapperObj; } -typedef FilteringWrapper, - SameOriginOrCrossOriginAccessiblePropertiesOnly> LW; +typedef FilteringWrapper, LocationPolicy> LW; bool WrapperFactory::IsLocationObject(JSObject *obj) diff --git a/js/xpconnect/wrappers/WrapperFactory.h b/js/xpconnect/wrappers/WrapperFactory.h index 439b885548c7..67ca2c1208e4 100644 --- a/js/xpconnect/wrappers/WrapperFactory.h +++ b/js/xpconnect/wrappers/WrapperFactory.h @@ -37,6 +37,9 @@ * * ***** END LICENSE BLOCK ***** */ +#ifndef _xpc_WRAPPERFACTORY_H +#define _xpc_WRAPPERFACTORY_H + #include "jsapi.h" #include "jswrapper.h" @@ -102,3 +105,5 @@ class WrapperFactory { extern js::Wrapper WaiveXrayWrapperWrapper; } + +#endif /* _xpc_WRAPPERFACTORY_H */ From a8eb45af3652cf58476c1ab74cf0bb5369f4b0fa Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:19 -0700 Subject: [PATCH 068/109] Bug 733984 - Use the Location security policy even for content accessing chrome. r=mrbkap I'm adding asserts about when we do and don't have a Location object behind the wrapper, and this case was hitting them. What we do here doesn't so much matter given how this stuff all works. On the one hand, statically using a restrictive policy is slightly more defense-in-depth. On the other hand, if this stuff is broken we're screwed in much more serious ways than content reading chrome locations, and using a consistent wrapper scheme allows us to make stronger asserts and assumptions. I opted for stronger assumptions and more understandable security code. If Blake feels strongly though, I could go the other way and sprinkle '|| isChrome(obj)' throughout the asserts though. --- js/xpconnect/wrappers/WrapperFactory.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 7ddafe18ac8d..fd1468b004d3 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -342,8 +342,10 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO wn->HasProto() && wn->GetProto()->ClassIsDOMObject()) { typedef XrayWrapper Xray; usingXray = true; - wrapper = &FilteringWrapper::singleton; + if (IsLocationObject(obj)) + wrapper = &FilteringWrapper::singleton; + else + wrapper = &FilteringWrapper::singleton; } else { wrapper = &FilteringWrapper::singleton; From a41865ae5972f9cf8025b721f7f2086ea902208b Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:23 -0700 Subject: [PATCH 069/109] Bug 733984 - Apply Location wrappers for same-origin cross-compartment wrapping. r=mrbkap This isn't an issue right now, since it can't ever happen outside of sandboxes, which content can't use. But if it could, it could get a pure CrossCompartmentWrapper to a Location object, which is bad. --- js/xpconnect/wrappers/WrapperFactory.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index fd1468b004d3..4228d9f2c7f2 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -351,12 +351,23 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO ExposedPropertiesOnly>::singleton; } } else if (AccessCheck::isSameOrigin(origin, target)) { - // Same origin we use a transparent wrapper, unless the compartment asks - // for an Xray or the wrapper needs a SOW. + // For the same-origin case we use a transparent wrapper, unless one + // of the following is true: + // * The wrapper is a Location object. + // * The wrapper is flagged as needing a SOW. + // * The context compartment specifically requested Xray vision into + // same-origin compartments. + // + // The first two cases always require a security wrapper for non-chrome + // access, regardless of the origin of the object. bool proxy; if (AccessCheck::needsSystemOnlyWrapper(obj)) { wrapper = &FilteringWrapper::singleton; + } else if (IsLocationObject(obj)) { + typedef XrayWrapper Xray; + usingXray = true; + wrapper = &FilteringWrapper::singleton; } else if (targetdata && targetdata->wantXrays && CanXray(obj, &proxy)) { if (proxy) { wrapper = &XrayProxy::singleton; From 6eee34997f655711f6848e0e8213283600a5358c Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:27 -0700 Subject: [PATCH 070/109] Bug 667388 - Make the chrome-to-content Xray wrapper derive CrossCompartmentWrapper. r=mrbkap The current situation seems incorrect, especially given the behavior of CrossOriginWrapper and XrayProxy. Currently it doesn't matter, but it probably will in the future. --- js/xpconnect/wrappers/WrapperFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 4228d9f2c7f2..0c1b67721356 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -318,7 +318,7 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO if (proxy) { wrapper = &XrayProxy::singleton; } else { - typedef XrayWrapper Xray; + typedef XrayWrapper Xray; usingXray = true; wrapper = &Xray::singleton; } From 02d5765e81671f3331bd8fc27cfa0a63ef934e02 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:27 -0700 Subject: [PATCH 071/109] Bug 667388 - Introduce the PUNCTURE wrapper action. r=mrbkap --- js/src/jswrapper.h | 31 +++++++++++++++++++++++++-- js/xpconnect/wrappers/AccessCheck.cpp | 13 +++++++++++ js/xpconnect/wrappers/AccessCheck.h | 8 ++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index 8bebe9768bc8..41a794cba373 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -100,8 +100,35 @@ class JS_FRIEND_API(Wrapper) : public ProxyHandler virtual void trace(JSTracer *trc, JSObject *wrapper) MOZ_OVERRIDE; - /* Policy enforcement traps. */ - enum Action { GET, SET, CALL }; + /* Policy enforcement traps. + * + * enter() allows the policy to specify whether the caller may perform |act| + * on the underlying object's |id| property. In the case when |act| is CALL, + * |id| is generally JSID_VOID. + * + * leave() allows the policy to undo various scoped state changes taken in + * enter(). If enter() succeeds, leave() must be called upon completion of + * the approved action. + * + * The |act| parameter to enter() specifies the action being performed. GET, + * SET, and CALL are self-explanatory, but PUNCTURE requires more explanation: + * + * GET and SET allow for a very fine-grained security membrane, through + * which access can be granted or denied on a per-property, per-object, and + * per-action basis. Sometimes though, we just want to asks if we can access + * _everything_ behind the wrapper barrier. For example, when the structured + * clone algorithm runs up against a cross-compartment wrapper, it needs to + * know whether it can enter the compartment and keep cloning, or whether it + * should throw. This is the role of PUNCTURE. + * + * PUNCTURE allows the policy to specify whether the wrapper barrier may + * be lifted - that is to say, whether the caller is allowed to access + * anything that the wrapped object could access. This is a very powerful + * permission, and thus should generally be denied for security wrappers + * except under very special circumstances. When |act| is PUNCTURE, |id| + * should be JSID_VOID. + * */ + enum Action { GET, SET, CALL, PUNCTURE }; virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, Action act, bool *bp); virtual void leave(JSContext *cx, JSObject *wrapper); diff --git a/js/xpconnect/wrappers/AccessCheck.cpp b/js/xpconnect/wrappers/AccessCheck.cpp index 007b6535b146..bc72afc7b00d 100644 --- a/js/xpconnect/wrappers/AccessCheck.cpp +++ b/js/xpconnect/wrappers/AccessCheck.cpp @@ -306,6 +306,15 @@ AccessCheck::isCrossOriginAccessPermitted(JSContext *cx, JSObject *wrapper, jsid JSObject *obj = Wrapper::wrappedObject(wrapper); + // LocationPolicy checks PUNCTURE first, so we should never get here for + // Location wrappers. For all other wrappers interested in cross-origin + // semantics, we want to allow puncturing only for the same-origin + // document.domain case. + if (act == Wrapper::PUNCTURE) { + MOZ_ASSERT(!WrapperFactory::IsLocationObject(obj)); + return documentDomainMakesSameOrigin(cx, obj); + } + const char *name; js::Class *clasp = js::GetObjectClass(obj); NS_ASSERTION(Jsvalify(clasp) != &XrayUtils::HolderClass, "shouldn't have a holder here"); @@ -498,6 +507,10 @@ ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapper, jsid id, Wrapper: perm = PermitObjectAccess; return true; } + if (act == Wrapper::PUNCTURE) { + perm = DenyAccess; + return false; + } perm = DenyAccess; diff --git a/js/xpconnect/wrappers/AccessCheck.h b/js/xpconnect/wrappers/AccessCheck.h index 065821dd9883..43e8d3f5c7ca 100644 --- a/js/xpconnect/wrappers/AccessCheck.h +++ b/js/xpconnect/wrappers/AccessCheck.h @@ -151,12 +151,18 @@ struct LocationPolicy : public Policy { // We should only be dealing with Location objects here. MOZ_ASSERT(WrapperFactory::IsLocationObject(js::UnwrapObject(wrapper))); + // Default to deny. + perm = DenyAccess; + + // Location object security is complicated enough. Don't allow punctures. + if (act == js::Wrapper::PUNCTURE) + return false; + if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act) || AccessCheck::isLocationObjectSameOrigin(cx, wrapper)) { perm = PermitPropertyAccess; return true; } - perm = DenyAccess; JSAutoEnterCompartment ac; if (!ac.enter(cx, wrapper)) return false; From 1e986914e43644c4cc860dfd160980c2caa4b49f Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 14:59:27 -0700 Subject: [PATCH 072/109] Bug 667388 - Handle wrappers during structured clone. r=mrbkap We also remove the declared-but-never-implemented JSObject::getWrapperHandler. --- js/src/jsclone.cpp | 21 +++++++++++++++++++++ js/src/jsclone.h | 3 +++ js/src/jsobj.h | 5 ----- js/src/jsscope.cpp | 1 + js/src/jswrapper.cpp | 19 +++++++++++++++++++ js/src/jswrapper.h | 6 ++++++ 6 files changed, 50 insertions(+), 5 deletions(-) diff --git a/js/src/jsclone.cpp b/js/src/jsclone.cpp index a6dbee4dd280..9db9a6849816 100644 --- a/js/src/jsclone.cpp +++ b/js/src/jsclone.cpp @@ -513,6 +513,8 @@ JSStructuredCloneWriter::startObject(JSObject *obj) bool JSStructuredCloneWriter::startWrite(const js::Value &v) { + assertSameCompartment(context(), v); + if (v.isString()) { return writeString(SCTAG_STRING, v.toString()); } else if (v.isNumber()) { @@ -525,6 +527,19 @@ JSStructuredCloneWriter::startWrite(const js::Value &v) return out.writePair(SCTAG_UNDEFINED, 0); } else if (v.isObject()) { JSObject *obj = &v.toObject(); + + // The object might be a security wrapper. See if we can clone what's + // behind it. If we can, unwrap the object. + obj = UnwrapObjectChecked(context(), obj); + if (!obj) + return false; + + // If we unwrapped above, we'll need to enter the underlying compartment. + // Let the AutoEnterCompartment do the right thing for us. + JSAutoEnterCompartment ac; + if (!ac.enter(context(), obj)) + return false; + if (obj->isRegExp()) { RegExpObject &reobj = obj->asRegExp(); return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) && @@ -564,6 +579,12 @@ JSStructuredCloneWriter::write(const Value &v) while (!counts.empty()) { JSObject *obj = &objs.back().toObject(); + + // The objects in |obj| can live in other compartments. + JSAutoEnterCompartment ac; + if (!ac.enter(context(), obj)) + return false; + if (counts.back()) { counts.back()--; jsid id = ids.back(); diff --git a/js/src/jsclone.h b/js/src/jsclone.h index 2d9f9bd67a86..a2453b5bd757 100644 --- a/js/src/jsclone.h +++ b/js/src/jsclone.h @@ -173,6 +173,9 @@ struct JSStructuredCloneWriter { js::SCOutput &out; // Vector of objects with properties remaining to be written. + // + // NB: These can span multiple compartments, so the compartment must be + // entered before any manipulation is performed. js::AutoValueVector objs; // counts[i] is the number of properties of objs[i] remaining to be written. diff --git a/js/src/jsobj.h b/js/src/jsobj.h index dc9292a5bb67..af1de1457405 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -865,11 +865,6 @@ struct JSObject : public js::ObjectImpl inline jsval getQNameLocalNameVal() const; inline void setQNameLocalName(JSAtom *name); - /* - * Proxy-specific getters and setters. - */ - inline js::Wrapper *getWrapperHandler() const; - /* * Back to generic stuff. */ diff --git a/js/src/jsscope.cpp b/js/src/jsscope.cpp index be61992445a1..72baeb5c1566 100644 --- a/js/src/jsscope.cpp +++ b/js/src/jsscope.cpp @@ -108,6 +108,7 @@ bool Shape::makeOwnBaseShape(JSContext *cx) { JS_ASSERT(!base()->isOwned()); + assertSameCompartment(cx, compartment()); RootedVarShape self(cx, this); diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index f7d50172312c..c5c1127aa874 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -86,6 +86,25 @@ js::UnwrapObject(JSObject *wrapped, bool stopAtOuter, unsigned *flagsp) return wrapped; } +JS_FRIEND_API(JSObject *) +js::UnwrapObjectChecked(JSContext *cx, JSObject *obj) +{ + while (obj->isWrapper()) { + JSObject *wrapper = obj; + Wrapper *handler = Wrapper::wrapperHandler(obj); + bool rvOnFailure; + if (!handler->enter(cx, wrapper, JSID_VOID, + Wrapper::PUNCTURE, &rvOnFailure)) + return rvOnFailure ? obj : NULL; + obj = Wrapper::wrappedObject(obj); + JS_ASSERT(obj); + handler->leave(cx, wrapper); + if (obj->getClass()->ext.innerObject) + break; + } + return obj; +} + bool js::IsCrossCompartmentWrapper(const JSObject *wrapper) { diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index 41a794cba373..5143f85d81ea 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -253,6 +253,12 @@ IsWrapper(const JSObject *obj) JS_FRIEND_API(JSObject *) UnwrapObject(JSObject *obj, bool stopAtOuter = true, unsigned *flagsp = NULL); +// Given a JSObject, returns that object stripped of wrappers. At each stage, +// the security wrapper has the opportunity to veto the unwrap. Since checked +// code should never be unwrapping outer window wrappers, we always stop at +// outer windows. +JS_FRIEND_API(JSObject *) UnwrapObjectChecked(JSContext *cx, JSObject *obj); + bool IsCrossCompartmentWrapper(const JSObject *obj); } /* namespace js */ From 0729071e9efc2eab356e5dd9faf095135b533fdb Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 23 Mar 2012 15:00:22 -0700 Subject: [PATCH 073/109] Bug 667388 - Tests. r=mrbkap --- dom/tests/mochitest/chrome/Makefile.in | 1 + .../mochitest/chrome/test_clonewrapper.xul | 126 ++++++++++++++++++ dom/tests/mochitest/general/Makefile.in | 1 + .../mochitest/general/file_clonewrapper.html | 29 ++++ 4 files changed, 157 insertions(+) create mode 100644 dom/tests/mochitest/chrome/test_clonewrapper.xul create mode 100644 dom/tests/mochitest/general/file_clonewrapper.html diff --git a/dom/tests/mochitest/chrome/Makefile.in b/dom/tests/mochitest/chrome/Makefile.in index c2fae9bafa69..15e62a1497bc 100644 --- a/dom/tests/mochitest/chrome/Makefile.in +++ b/dom/tests/mochitest/chrome/Makefile.in @@ -73,6 +73,7 @@ _TEST_FILES = \ 489127.html \ test_focus_docnav.xul \ window_focus_docnav.xul \ + test_clonewrapper.xul \ $(NULL) ifeq (WINNT,$(OS_ARCH)) diff --git a/dom/tests/mochitest/chrome/test_clonewrapper.xul b/dom/tests/mochitest/chrome/test_clonewrapper.xul new file mode 100644 index 000000000000..e2d2b005e6bc --- /dev/null +++ b/dom/tests/mochitest/chrome/test_clonewrapper.xul @@ -0,0 +1,126 @@ + + + + + + + + + + Mozilla Bug 667388 +