--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1678,17 +1678,17 @@ var BookmarkingUI = {
_itemGuids: new Set(),
uninit: function BUI_uninit() {
this.updateBookmarkPageMenuItem(true);
CustomizableUI.removeListener(this);
this._uninitView();
if (this._hasBookmarksObserver) {
- PlacesUtils.removeLazyBookmarkObserver(this);
+ PlacesUtils.bookmarks.removeObserver(this);
}
if (this._pendingUpdate) {
delete this._pendingUpdate;
}
},
onLocationChange: function BUI_onLocationChange() {
@@ -1723,17 +1723,17 @@ var BookmarkingUI = {
this._itemGuids = guids;
}
this._updateStar();
// Start observing bookmarks if needed.
if (!this._hasBookmarksObserver) {
try {
- PlacesUtils.addLazyBookmarkObserver(this);
+ PlacesUtils.bookmarks.addObserver(this);
this._hasBookmarksObserver = true;
} catch (ex) {
Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
}
}
delete this._pendingUpdate;
});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -267,20 +267,16 @@ function BrowserGlue() {
/*
* OS X has the concept of zero-window sessions and therefore ignores the
* browser-lastwindow-close-* topics.
*/
const OBSERVE_LASTWINDOW_CLOSE_TOPICS = AppConstants.platform != "macosx";
BrowserGlue.prototype = {
_saveSession: false,
- _isPlacesInitObserver: false,
- _isPlacesLockedObserver: false,
- _isPlacesShutdownObserver: false,
- _isPlacesDatabaseLocked: false,
_migrationImportsDefaultBookmarks: false,
_setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
if (!this._saveSession && !aForce)
return;
Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
@@ -379,31 +375,19 @@ BrowserGlue.prototype = {
this._onDisplaySyncURIs(subject);
break;
case "session-save":
this._setPrefToSaveSession(true);
subject.QueryInterface(Ci.nsISupportsPRBool);
subject.data = true;
break;
case "places-init-complete":
+ Services.obs.removeObserver(this, "places-init-complete");
if (!this._migrationImportsDefaultBookmarks)
this._initPlaces(false);
-
- Services.obs.removeObserver(this, "places-init-complete");
- this._isPlacesInitObserver = false;
- // no longer needed, since history was initialized completely.
- Services.obs.removeObserver(this, "places-database-locked");
- this._isPlacesLockedObserver = false;
- break;
- case "places-database-locked":
- this._isPlacesDatabaseLocked = true;
- // Stop observing, so further attempts to load history service
- // will not show the prompt.
- Services.obs.removeObserver(this, "places-database-locked");
- this._isPlacesLockedObserver = false;
break;
case "idle":
this._backupBookmarks();
break;
case "distribution-customization-complete":
Services.obs.removeObserver(this, "distribution-customization-complete");
// Customization has finished, we don't need the customizer anymore.
delete this._distributionCustomizer;
@@ -530,19 +514,16 @@ BrowserGlue.prototype = {
os.addObserver(this, "weave:service:ready");
os.addObserver(this, "fxaccounts:onverified");
os.addObserver(this, "fxaccounts:device_connected");
os.addObserver(this, "fxaccounts:verify_login");
os.addObserver(this, "fxaccounts:device_disconnected");
os.addObserver(this, "weave:engine:clients:display-uris");
os.addObserver(this, "session-save");
os.addObserver(this, "places-init-complete");
- this._isPlacesInitObserver = true;
- os.addObserver(this, "places-database-locked");
- this._isPlacesLockedObserver = true;
os.addObserver(this, "distribution-customization-complete");
os.addObserver(this, "handle-xul-text-link");
os.addObserver(this, "profile-before-change");
os.addObserver(this, "keyword-search");
os.addObserver(this, "browser-search-engine-modified");
os.addObserver(this, "restart-in-safe-mode");
os.addObserver(this, "flash-plugin-hang");
os.addObserver(this, "xpi-signature-changed");
@@ -584,20 +565,19 @@ BrowserGlue.prototype = {
if (this._bookmarksBackupIdleTime) {
this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
delete this._bookmarksBackupIdleTime;
}
if (this._mediaTelemetryIdleObserver) {
this._idleService.removeIdleObserver(this._mediaTelemetryIdleObserver, MEDIA_TELEMETRY_IDLE_TIME_SEC);
delete this._mediaTelemetryIdleObserver;
}
- if (this._isPlacesInitObserver)
+ try {
os.removeObserver(this, "places-init-complete");
- if (this._isPlacesLockedObserver)
- os.removeObserver(this, "places-database-locked");
+ } catch (ex) { /* Could have been removed already */ }
os.removeObserver(this, "handle-xul-text-link");
os.removeObserver(this, "profile-before-change");
os.removeObserver(this, "keyword-search");
os.removeObserver(this, "browser-search-engine-modified");
os.removeObserver(this, "flash-plugin-hang");
os.removeObserver(this, "xpi-signature-changed");
os.removeObserver(this, "sync-ui-state:update");
},
@@ -1075,22 +1055,16 @@ BrowserGlue.prototype = {
}
this._initServiceDiscovery();
// Show update notification, if needed.
if (Services.prefs.prefHasUserValue("app.update.postupdate"))
this._showUpdateNotification();
- // Load the "more info" page for a locked places.sqlite
- // This property is set earlier by places-database-locked topic.
- if (this._isPlacesDatabaseLocked) {
- this._showPlacesLockedNotificationBox();
- }
-
ExtensionsUI.init();
let signingRequired;
if (AppConstants.MOZ_REQUIRE_SIGNING) {
signingRequired = true;
} else {
signingRequired = Services.prefs.getBoolPref("xpinstall.signatures.required");
}
@@ -1483,16 +1457,28 @@ BrowserGlue.prototype = {
*/
_initPlaces: function BG__initPlaces(aInitialMigrationPerformed) {
// We must instantiate the history service since it will tell us if we
// need to import or restore bookmarks due to first-run, corruption or
// forced migration (due to a major schema change).
// If the database is corrupt or has been newly created we should
// import bookmarks.
let dbStatus = PlacesUtils.history.databaseStatus;
+
+ // Show a notification with a "more info" link for a locked places.sqlite.
+ if (dbStatus == PlacesUtils.history.DATABASE_STATUS_LOCKED) {
+ // Note: initPlaces should always happen when the first window is ready,
+ // in any case, better safe than sorry.
+ this._firstWindowReady.then(() => {
+ this._showPlacesLockedNotificationBox();
+ Services.obs.notifyObservers(null, "places-browser-init-complete");
+ });
+ return;
+ }
+
let importBookmarks = !aInitialMigrationPerformed &&
(dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT);
// Check if user or an extension has required to import bookmarks.html
let importBookmarksHTML = false;
try {
importBookmarksHTML =
--- a/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js
+++ b/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js
@@ -4,30 +4,26 @@
* 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/. */
/**
* Tests that nsBrowserGlue correctly exports bookmarks.html at shutdown if
* browser.bookmarks.autoExportHTML is set to true.
*/
-function run_test() {
- run_next_test();
-}
-
add_task(async function() {
remove_bookmarks_html();
Services.prefs.setBoolPref("browser.bookmarks.autoExportHTML", true);
do_register_cleanup(() => Services.prefs.clearUserPref("browser.bookmarks.autoExportHTML"));
// Initialize nsBrowserGlue before Places.
Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
// Initialize Places through the History Service.
- Cc["@mozilla.org/browser/nav-history-service;1"]
- .getService(Ci.nsINavHistoryService);
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
Services.obs.addObserver(function observer() {
Services.obs.removeObserver(observer, "profile-before-change");
check_bookmarks_html();
}, "profile-before-change");
});
--- a/browser/components/places/tests/unit/test_browserGlue_distribution.js
+++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js
@@ -46,23 +46,22 @@ do_register_cleanup(function() {
}
Assert.ok(!iniFile.exists());
});
add_task(async function() {
// Disable Smart Bookmarks creation.
Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1);
+ let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
// Initialize Places through the History Service and check that a new
// database has been created.
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CREATE);
-
// Force distribution.
- let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
// Test will continue on customization complete notification.
await promiseTopicObserved(TOPIC_CUSTOMIZATION_COMPLETE);
// Check the custom bookmarks exist on menu.
let menuItem = await PlacesUtils.bookmarks.fetch({
parentGuid: PlacesUtils.bookmarks.menuGuid,
--- a/browser/components/places/tests/unit/test_browserGlue_prefs.js
+++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js
@@ -12,50 +12,47 @@ const PREF_SMART_BOOKMARKS_VERSION = "br
const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML";
const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
const TOPICDATA_FORCE_PLACES_INIT = "force-places-init";
var bg = Cc["@mozilla.org/browser/browserglue;1"].
getService(Ci.nsIObserver);
-function run_test() {
+add_task(async function setup() {
// Create our bookmarks.html from bookmarks.glue.html.
create_bookmarks_html("bookmarks.glue.html");
remove_all_JSON_backups();
// Create our JSON backup from bookmarks.glue.json.
create_JSON_backup("bookmarks.glue.json");
- run_next_test();
-}
+ do_register_cleanup(function() {
+ remove_bookmarks_html();
+ remove_all_JSON_backups();
-do_register_cleanup(function() {
- remove_bookmarks_html();
- remove_all_JSON_backups();
-
- return PlacesUtils.bookmarks.eraseEverything();
+ return PlacesUtils.bookmarks.eraseEverything();
+ });
});
function simulatePlacesInit() {
do_print("Simulate Places init");
// Force nsBrowserGlue::_initPlaces().
bg.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT);
return promiseTopicObserved("places-browser-init-complete");
}
add_task(async function test_checkPreferences() {
// Initialize Places through the History Service and check that a new
// database has been created.
+ let promiseComplete = promiseTopicObserved("places-browser-init-complete");
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CREATE);
-
- // Wait for Places init notification.
- await promiseTopicObserved("places-browser-init-complete");
+ await promiseComplete;
// Ensure preferences status.
Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML));
Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
Assert.throws(() => Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
});
--- a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js
+++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js
@@ -34,21 +34,22 @@ function countFolderChildren(aFolderItem
rootNode.containerOpen = false;
return cc;
}
add_task(async function setup() {
// Initialize browserGlue, but remove it's listener to places-init-complete.
Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
- // Initialize Places.
- PlacesUtils.history;
-
- // Wait for Places init notification.
- await promiseTopicObserved("places-browser-init-complete");
+ // Initialize Places through the History Service and check that a new
+ // database has been created.
+ let promiseComplete = promiseTopicObserved("places-browser-init-complete");
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
+ await promiseComplete;
// Ensure preferences status.
Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML));
Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
});
add_task(async function test_version_0() {
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -408,16 +408,17 @@ Database::Database()
, mMainThreadAsyncStatements(mMainConn)
, mAsyncThreadStatements(mMainConn)
, mDBPageSize(0)
, mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
, mClosed(false)
, mClientsShutdown(new ClientsShutdownBlocker())
, mConnectionShutdown(new ConnectionShutdownBlocker(this))
, mMaxUrlLength(0)
+ , mCacheObservers(TOPIC_PLACES_INIT_COMPLETE)
{
MOZ_ASSERT(!XRE_IsContentProcess(),
"Cannot instantiate Places in the content process");
// Attempting to create two instances of the service?
MOZ_ASSERT(!gDatabase);
gDatabase = this;
}
@@ -465,34 +466,41 @@ Database::IsShutdownStarted() const
if (!mConnectionShutdown) {
// We have already broken the cycle between `this` and `mConnectionShutdown`.
return true;
}
return mConnectionShutdown->IsStarted();
}
already_AddRefed<mozIStorageAsyncStatement>
-Database::GetAsyncStatement(const nsACString& aQuery) const
+Database::GetAsyncStatement(const nsACString& aQuery)
{
- if (IsShutdownStarted()) {
+ if (IsShutdownStarted() || NS_FAILED(EnsureConnection())) {
return nullptr;
}
+
MOZ_ASSERT(NS_IsMainThread());
return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
}
already_AddRefed<mozIStorageStatement>
-Database::GetStatement(const nsACString& aQuery) const
+Database::GetStatement(const nsACString& aQuery)
{
if (IsShutdownStarted()) {
return nullptr;
}
if (NS_IsMainThread()) {
+ if (NS_FAILED(EnsureConnection())) {
+ return nullptr;
+ }
return mMainThreadStatements.GetCachedStatement(aQuery);
}
+ // In the async case, the connection must have been started on the main-thread
+ // already.
+ MOZ_ASSERT(mMainConn);
return mAsyncThreadStatements.GetCachedStatement(aQuery);
}
already_AddRefed<nsIAsyncShutdownClient>
Database::GetClientsShutdown()
{
MOZ_ASSERT(mClientsShutdown);
return mClientsShutdown->GetClient();
@@ -508,112 +516,19 @@ Database::GetDatabase()
return GetSingleton();
}
nsresult
Database::Init()
{
MOZ_ASSERT(NS_IsMainThread());
- bool initSucceeded = false;
- auto guard = MakeScopeExit([&]() {
- if (!initSucceeded) {
- // If we bail out early here without doing anything else, the Database object
- // will leak due to the cycle between the shutdown blockers and itself, so
- // let's break the cycle first.
- Shutdown(false);
- }
- });
-
- nsCOMPtr<mozIStorageService> storage =
- do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
- NS_ENSURE_STATE(storage);
-
- // Init the database file and connect to it.
- bool databaseCreated = false;
- nsresult rv = InitDatabaseFile(storage, &databaseCreated);
- if (NS_SUCCEEDED(rv) && databaseCreated) {
- mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
- }
- else if (rv == NS_ERROR_FILE_CORRUPTED) {
- // The database is corrupt, backup and replace it with a new one.
- mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
- rv = BackupAndReplaceDatabaseFile(storage);
- // Fallback to catch-all handler, that notifies a database locked failure.
- }
-
- // If the database connection still cannot be opened, it may just be locked
- // by third parties. Send out a notification and interrupt initialization.
- if (NS_FAILED(rv)) {
- RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
- (void)NS_DispatchToMainThread(lockedEvent);
- return rv;
- }
-
- // Ensure the icons database exists.
- rv = EnsureFaviconsDatabaseFile(storage);
- if (NS_FAILED(rv)) {
- RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
- (void)NS_DispatchToMainThread(lockedEvent);
- return rv;
- }
-
- // Initialize the database schema. In case of failure the existing schema is
- // is corrupt or incoherent, thus the database should be replaced.
- bool databaseMigrated = false;
- rv = SetupDatabaseConnection(storage);
- if (NS_SUCCEEDED(rv)) {
- // Failing to initialize the schema always indicates a corruption.
- if (NS_FAILED(InitSchema(&databaseMigrated))) {
- rv = NS_ERROR_FILE_CORRUPTED;
- }
- }
- if (NS_FAILED(rv)) {
- mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
- // Some errors may not indicate a database corruption, for those cases we
- // just bail out without throwing away a possibly valid places.sqlite.
- if (rv == NS_ERROR_FILE_CORRUPTED) {
- rv = BackupAndReplaceDatabaseFile(storage);
- NS_ENSURE_SUCCESS(rv, rv);
- // Try to initialize the new database again.
- rv = SetupDatabaseConnection(storage);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = InitSchema(&databaseMigrated);
- }
- // Bail out if we couldn't fix the database.
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- if (databaseMigrated) {
- mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
- }
-
- if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
- rv = updateSQLiteStatistics(MainConn());
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- // Initialize here all the items that are not part of the on-disk database,
- // like views, temp triggers or temp tables. The database should not be
- // considered corrupt if any of the following fails.
-
- rv = InitTempEntities();
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Notify we have finished database initialization.
- // Enqueue the notification, so if we init another service that requires
- // nsNavHistoryService we don't recursive try to get it.
- RefPtr<PlacesEvent> completeEvent =
- new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
- rv = NS_DispatchToMainThread(completeEvent);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // At this point we know the Database object points to a valid connection
- // and we need to setup async shutdown.
- initSucceeded = true;
+ // DO NOT FAIL HERE, otherwise we would never break the cycle between this
+ // object and the shutdown blockers, causing unexpected leaks.
+
{
// First of all Places clients should block profile-change-teardown.
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
MOZ_ASSERT(shutdownPhase);
if (shutdownPhase) {
DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
NS_LITERAL_STRING(__FILE__),
@@ -637,17 +552,133 @@ Database::Init()
}
}
// Finally observe profile shutdown notifications.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
(void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
}
-
+ return NS_OK;
+}
+
+nsresult
+Database::EnsureConnection()
+{
+ // Run this only once.
+ if (mMainConn ||
+ mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_LOCKED) {
+ return NS_OK;
+ }
+ // Don't try to create a database too late.
+ if (IsShutdownStarted()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Database initialization must happen on the main-thread");
+
+ {
+ bool initSucceeded = false;
+ auto notify = MakeScopeExit([&] () {
+ // If the database connection cannot be opened, it may just be locked
+ // by third parties. Set a locked state.
+ if (!initSucceeded) {
+ mMainConn = nullptr;
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_LOCKED;
+ }
+ // Notify at the next tick, to avoid re-entrancy problems.
+ NS_DispatchToMainThread(
+ NewRunnableMethod("places::Database::EnsureConnection()",
+ this, &Database::NotifyConnectionInitalized)
+ );
+ });
+
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_STATE(storage);
+
+ // Init the database file and connect to it.
+ bool databaseCreated = false;
+ nsresult rv = InitDatabaseFile(storage, &databaseCreated);
+ if (NS_SUCCEEDED(rv) && databaseCreated) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
+ }
+ else if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // The database is corrupt, backup and replace it with a new one.
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
+ rv = BackupAndReplaceDatabaseFile(storage);
+ // Fallback to catch-all handler.
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure the icons database exists.
+ rv = EnsureFaviconsDatabaseFile(storage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the database schema. In case of failure the existing schema is
+ // is corrupt or incoherent, thus the database should be replaced.
+ bool databaseMigrated = false;
+ rv = SetupDatabaseConnection(storage);
+ if (NS_SUCCEEDED(rv)) {
+ // Failing to initialize the schema always indicates a corruption.
+ if (NS_FAILED(InitSchema(&databaseMigrated))) {
+ rv = NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
+ // Some errors may not indicate a database corruption, for those cases we
+ // just bail out without throwing away a possibly valid places.sqlite.
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ rv = BackupAndReplaceDatabaseFile(storage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Try to initialize the new database again.
+ rv = SetupDatabaseConnection(storage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InitSchema(&databaseMigrated);
+ }
+ // Bail out if we couldn't fix the database.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (databaseMigrated) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
+ }
+
+ if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_UPGRADED ||
+ mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
+ MOZ_ALWAYS_SUCCEEDS(updateSQLiteStatistics(mMainConn));
+ }
+
+ // Initialize here all the items that are not part of the on-disk database,
+ // like views, temp triggers or temp tables. The database should not be
+ // considered corrupt if any of the following fails.
+
+ rv = InitTempEntities();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ initSucceeded = true;
+ }
+ return NS_OK;
+}
+
+nsresult
+Database::NotifyConnectionInitalized()
+{
+ // Notify about Places initialization.
+ nsCOMArray<nsIObserver> entries;
+ mCacheObservers.GetEntries(entries);
+ for (int32_t idx = 0; idx < entries.Count(); ++idx) {
+ MOZ_ALWAYS_SUCCEEDS(entries[idx]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
+ }
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ MOZ_ALWAYS_SUCCEEDS(obs->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
+ }
return NS_OK;
}
nsresult
Database::EnsureFaviconsDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
{
MOZ_ASSERT(NS_IsMainThread());
@@ -666,55 +697,57 @@ Database::EnsureFaviconsDatabaseFile(nsC
return NS_OK;
}
// Open the database file, this will also create it.
nsCOMPtr<mozIStorageConnection> conn;
rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(conn));
NS_ENSURE_SUCCESS(rv, rv);
- // Ensure we'll close the connection when done.
- auto cleanup = MakeScopeExit([&] () {
- // We cannot use AsyncClose() here, because by the time we try to ATTACH
- // this database, its transaction could be still be running and that would
- // cause the ATTACH query to fail.
- MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
- });
-
- int32_t defaultPageSize;
- rv = conn->GetDefaultPageSize(&defaultPageSize);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = SetupDurability(conn, defaultPageSize);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Enable incremental vacuum for this database. Since it will contain even
- // large blobs and can be cleared with history, it's worth to have it.
- // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
- rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
- "PRAGMA auto_vacuum = INCREMENTAL"
- ));
- NS_ENSURE_SUCCESS(rv, rv);
-
- // We are going to update the database, so everything from now on should be
- // in a transaction for performances.
- mozStorageTransaction transaction(conn, false);
- rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
- NS_ENSURE_SUCCESS(rv, rv);
- rv = transaction.Commit();
- NS_ENSURE_SUCCESS(rv, rv);
-
- // The scope exit will take care of closing the connection.
+ {
+ // Ensure we'll close the connection when done.
+ auto cleanup = MakeScopeExit([&] () {
+ // We cannot use AsyncClose() here, because by the time we try to ATTACH
+ // this database, its transaction could be still be running and that would
+ // cause the ATTACH query to fail.
+ MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
+ });
+
+ int32_t defaultPageSize;
+ rv = conn->GetDefaultPageSize(&defaultPageSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetupDurability(conn, defaultPageSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enable incremental vacuum for this database. Since it will contain even
+ // large blobs and can be cleared with history, it's worth to have it.
+ // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
+ rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA auto_vacuum = INCREMENTAL"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We are going to update the database, so everything from now on should be
+ // in a transaction for performances.
+ mozStorageTransaction transaction(conn, false);
+ rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The scope exit will take care of closing the connection.
+ }
return NS_OK;
}
nsresult
Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
bool* aNewDatabaseCreated)
{
@@ -2502,17 +2535,17 @@ Database::CreateMobileRoot()
rv = addAnnoStmt->Execute();
if (NS_FAILED(rv)) return -1;
return rootId;
}
void
-Database::Shutdown(bool aInitSucceeded)
+Database::Shutdown()
{
// As the last step in the shutdown path, finalize the database handle.
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mClosed);
// Break cycles with the shutdown blockers.
mClientsShutdown = nullptr;
nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown = mConnectionShutdown.forget();
@@ -2520,17 +2553,17 @@ Database::Shutdown(bool aInitSucceeded)
if (!mMainConn) {
// The connection has never been initialized. Just mark it as closed.
mClosed = true;
(void)connectionShutdown->Complete(NS_OK, nullptr);
return;
}
#ifdef DEBUG
- if (aInitSucceeded) {
+ {
bool hasResult;
nsCOMPtr<mozIStorageStatement> stmt;
// Sanity check for missing guids.
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT 1 "
"FROM moz_places "
"WHERE guid IS NULL "
@@ -2589,16 +2622,17 @@ Database::Shutdown(bool aInitSucceeded)
mAsyncThreadStatements,
NS_ISUPPORTS_CAST(nsIObserver*, this)
);
DispatchToAsyncThread(event);
mClosed = true;
(void)mMainConn->AsyncClose(connectionShutdown);
+ mMainConn = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIObserver
NS_IMETHODIMP
Database::Observe(nsISupports *aSubject,
const char *aTopic,
@@ -2653,18 +2687,18 @@ Database::Observe(nsISupports *aSubject,
shutdownPhase->RemoveBlocker(mClientsShutdown.get());
}
(void)mClientsShutdown->BlockShutdown(nullptr);
}
// Spin the events loop until the clients are done.
// Note, this is just for tests, specifically test_clearHistory_shutdown.js
SpinEventLoopUntil([&]() {
- return mClientsShutdown->State() == PlacesShutdownBlocker::States::RECEIVED_DONE;
- });
+ return mClientsShutdown->State() == PlacesShutdownBlocker::States::RECEIVED_DONE;
+ });
{
nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
if (shutdownPhase) {
shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
}
(void)mConnectionShutdown->BlockShutdown(nullptr);
}
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -10,25 +10,24 @@
#include "nsIInterfaceRequestorUtils.h"
#include "nsIObserver.h"
#include "nsIAsyncShutdown.h"
#include "mozilla/storage.h"
#include "mozilla/storage/StatementCache.h"
#include "mozilla/Attributes.h"
#include "nsIEventTarget.h"
#include "Shutdown.h"
+#include "nsCategoryCache.h"
// This is the schema version. Update it at any schema change and add a
// corresponding migrateVxx method below.
#define DATABASE_SCHEMA_VERSION 38
// Fired after Places inited.
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
-// Fired when initialization fails due to a locked database.
-#define TOPIC_DATABASE_LOCKED "places-database-locked"
// This topic is received when the profile is about to be lost. Places does
// initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
// Any shutdown work that requires the Places APIs should happen here.
#define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
// Fired when Places is shutting down. Any code should stop accessing Places
// APIs after this notification. If you need to listen for Places shutdown
// you should only use this notification, next ones are intended only for
// internal Places use.
@@ -90,45 +89,57 @@ public:
/**
* Getter to use when instantiating the class.
*
* @return Singleton instance of this class.
*/
static already_AddRefed<Database> GetDatabase();
/**
+ * Actually initialized the connection on first need.
+ */
+ nsresult EnsureConnection();
+
+ /**
+ * Notifies that the connection has been initialized.
+ */
+ nsresult NotifyConnectionInitalized();
+
+ /**
* Returns last known database status.
*
* @return one of the nsINavHistoryService::DATABASE_STATUS_* constants.
*/
- uint16_t GetDatabaseStatus() const
+ uint16_t GetDatabaseStatus()
{
+ mozilla::Unused << EnsureConnection();
return mDatabaseStatus;
}
/**
* Returns a pointer to the storage connection.
*
* @return The connection handle.
*/
- mozIStorageConnection* MainConn() const
+ mozIStorageConnection* MainConn()
{
+ mozilla::Unused << EnsureConnection();
return mMainConn;
}
/**
* Dispatches a runnable to the connection async thread, to be serialized
* with async statements.
*
* @param aEvent
* The runnable to be dispatched.
*/
- void DispatchToAsyncThread(nsIRunnable* aEvent) const
+ void DispatchToAsyncThread(nsIRunnable* aEvent)
{
- if (mClosed) {
+ if (mClosed || NS_FAILED(EnsureConnection())) {
return;
}
nsCOMPtr<nsIEventTarget> target = do_GetInterface(mMainConn);
if (target) {
(void)target->Dispatch(aEvent, NS_DISPATCH_NORMAL);
}
}
@@ -141,71 +152,69 @@ public:
* @param aQuery
* SQL query literal.
* @return The cached statement.
* @note Always null check the result.
* @note Always use a scoper to reset the statement.
*/
template<int N>
already_AddRefed<mozIStorageStatement>
- GetStatement(const char (&aQuery)[N]) const
+ GetStatement(const char (&aQuery)[N])
{
nsDependentCString query(aQuery, N - 1);
return GetStatement(query);
}
/**
* Gets a cached synchronous statement.
*
* @param aQuery
* nsCString of SQL query.
* @return The cached statement.
* @note Always null check the result.
* @note Always use a scoper to reset the statement.
*/
- already_AddRefed<mozIStorageStatement> GetStatement(const nsACString& aQuery) const;
+ already_AddRefed<mozIStorageStatement> GetStatement(const nsACString& aQuery);
/**
* Gets a cached asynchronous statement.
*
* @param aQuery
* SQL query literal.
* @return The cached statement.
* @note Always null check the result.
* @note AsyncStatements are automatically reset on execution.
*/
template<int N>
already_AddRefed<mozIStorageAsyncStatement>
- GetAsyncStatement(const char (&aQuery)[N]) const
+ GetAsyncStatement(const char (&aQuery)[N])
{
nsDependentCString query(aQuery, N - 1);
return GetAsyncStatement(query);
}
/**
* Gets a cached asynchronous statement.
*
* @param aQuery
* nsCString of SQL query.
* @return The cached statement.
* @note Always null check the result.
* @note AsyncStatements are automatically reset on execution.
*/
- already_AddRefed<mozIStorageAsyncStatement> GetAsyncStatement(const nsACString& aQuery) const;
+ already_AddRefed<mozIStorageAsyncStatement> GetAsyncStatement(const nsACString& aQuery);
uint32_t MaxUrlLength();
protected:
/**
* Finalizes the cached statements and closes the database connection.
* A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
- *
- * @param Whether database init succeeded.
*/
- void Shutdown(bool aInitSucceeded);
+ void Shutdown();
bool IsShutdownStarted() const;
/**
* Initializes the database file. If the database does not exist or is
* corrupt, a new one is created. In case of corruption it also creates a
* backup copy of the database.
*
@@ -340,14 +349,17 @@ private:
RefPtr<ClientsShutdownBlocker> mClientsShutdown;
RefPtr<ConnectionShutdownBlocker> mConnectionShutdown;
// Maximum length of a stored url.
// For performance reasons we don't store very long urls in history, since
// they are slower to search through and cause abnormal database growth,
// affecting the awesomebar fetch time.
uint32_t mMaxUrlLength;
+
+ // Used to initialize components on places startup.
+ nsCategoryCache<nsIObserver> mCacheObservers;
};
} // namespace places
} // namespace mozilla
#endif // mozilla_places_Database_h_
--- a/toolkit/components/places/Helpers.cpp
+++ b/toolkit/components/places/Helpers.cpp
@@ -316,47 +316,16 @@ GetHiddenState(bool aIsRedirect,
uint32_t aTransitionType)
{
return aTransitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
aTransitionType == nsINavHistoryService::TRANSITION_EMBED ||
aIsRedirect;
}
////////////////////////////////////////////////////////////////////////////////
-//// PlacesEvent
-
-PlacesEvent::PlacesEvent(const char* aTopic)
- : Runnable("places::PlacesEvent")
- , mTopic(aTopic)
-{
-}
-
-NS_IMETHODIMP
-PlacesEvent::Run()
-{
- Notify();
- return NS_OK;
-}
-
-void
-PlacesEvent::Notify()
-{
- NS_ASSERTION(NS_IsMainThread(), "Must only be used on the main thread!");
- nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
- if (obs) {
- (void)obs->NotifyObservers(nullptr, mTopic, nullptr);
- }
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(
- PlacesEvent
-, Runnable
-)
-
-////////////////////////////////////////////////////////////////////////////////
//// AsyncStatementCallbackNotifier
NS_IMETHODIMP
AsyncStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
{
if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
return NS_ERROR_UNEXPECTED;
--- a/toolkit/components/places/Helpers.h
+++ b/toolkit/components/places/Helpers.h
@@ -225,33 +225,16 @@ protected:
* @param aTransitionType
* The transition type of the visit.
* @return true if this visit should be hidden.
*/
bool GetHiddenState(bool aIsRedirect,
uint32_t aTransitionType);
/**
- * Notifies a specified topic via the observer service.
- */
-class PlacesEvent : public Runnable
-{
-public:
- NS_DECL_ISUPPORTS_INHERITED
- NS_DECL_NSIRUNNABLE
-
- explicit PlacesEvent(const char* aTopic);
-protected:
- ~PlacesEvent() {}
- void Notify();
-
- const char* const mTopic;
-};
-
-/**
* Used to notify a topic to system observers on async execute completion.
*/
class AsyncStatementCallbackNotifier : public AsyncStatementCallback
{
public:
explicit AsyncStatementCallbackNotifier(const char* aTopic)
: mTopic(aTopic)
{
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -2345,21 +2345,36 @@ History::GetSingleton()
NS_ADDREF(gService);
return gService;
}
mozIStorageConnection*
History::GetDBConn()
{
+ MOZ_ASSERT(NS_IsMainThread());
if (mShuttingDown)
return nullptr;
if (!mDB) {
mDB = Database::GetDatabase();
NS_ENSURE_TRUE(mDB, nullptr);
+ // This must happen on the main-thread, so when we try to use the connection
+ // later it's initialized.
+ mDB->EnsureConnection();
+ NS_ENSURE_TRUE(mDB, nullptr);
+ }
+ return mDB->MainConn();
+}
+
+const mozIStorageConnection*
+History::GetConstDBConn()
+{
+ MOZ_ASSERT(mDB || mShuttingDown);
+ if (mShuttingDown || !mDB) {
+ return nullptr;
}
return mDB->MainConn();
}
void
History::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
@@ -2406,16 +2421,17 @@ History::IsRecentlyVisitedURI(nsIURI* aU
////////////////////////////////////////////////////////////////////////////////
//// IHistory
NS_IMETHODIMP
History::VisitURI(nsIURI* aURI,
nsIURI* aLastVisitedURI,
uint32_t aFlags)
{
+ MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aURI);
if (mShuttingDown) {
return NS_OK;
}
if (XRE_IsContentProcess()) {
URIParams uri;
@@ -2622,16 +2638,17 @@ History::UnregisterVisitedCallback(nsIUR
}
return NS_OK;
}
NS_IMETHODIMP
History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
{
+ MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aURI);
if (mShuttingDown) {
return NS_OK;
}
if (XRE_IsContentProcess()) {
URIParams uri;
--- a/toolkit/components/places/History.h
+++ b/toolkit/components/places/History.h
@@ -111,25 +111,27 @@ public:
* manager only.
*/
static History* GetSingleton();
template<int N>
already_AddRefed<mozIStorageStatement>
GetStatement(const char (&aQuery)[N])
{
- mozIStorageConnection* dbConn = GetDBConn();
+ // May be invoked on both threads.
+ const mozIStorageConnection* dbConn = GetConstDBConn();
NS_ENSURE_TRUE(dbConn, nullptr);
return mDB->GetStatement(aQuery);
}
already_AddRefed<mozIStorageStatement>
GetStatement(const nsACString& aQuery)
{
- mozIStorageConnection* dbConn = GetDBConn();
+ // May be invoked on both threads.
+ const mozIStorageConnection* dbConn = GetConstDBConn();
NS_ENSURE_TRUE(dbConn, nullptr);
return mDB->GetStatement(aQuery);
}
bool IsShuttingDown() const {
return mShuttingDown;
}
Mutex& GetShutdownMutex() {
@@ -143,21 +145,29 @@ public:
void AppendToRecentlyVisitedURIs(nsIURI* aURI);
private:
virtual ~History();
void InitMemoryReporter();
/**
- * Obtains a read-write database connection.
+ * Obtains a read-write database connection, initializing the connection
+ * if needed. Must be invoked on the main thread.
*/
mozIStorageConnection* GetDBConn();
/**
+ * Obtains a read-write database connection, but won't try to initialize it.
+ * May be invoked on both threads, but first one must invoke GetDBConn() on
+ * the main-thread at least once.
+ */
+ const mozIStorageConnection* GetConstDBConn();
+
+ /**
* The database handle. This is initialized lazily by the first call to
* GetDBConn(), so never use it directly, or, if you really need, always
* invoke GetDBConn() before.
*/
RefPtr<mozilla::places::Database> mDB;
RefPtr<ConcurrentStatementsHolder> mConcurrentStatementsHolder;
--- a/toolkit/components/places/PlacesCategoriesStarter.js
+++ b/toolkit/components/places/PlacesCategoriesStarter.js
@@ -1,101 +1,70 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* vim: sw=2 ts=2 sts=2 expandtab
* 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/. */
-// Constants
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
// Fired by TelemetryController when async telemetry data should be collected.
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";
// Seconds between maintenance runs.
const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;
-// Imports
-
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
"resource://gre/modules/PlacesDBUtils.jsm");
/**
* This component can be used as a starter for modules that have to run when
* certain categories are invoked.
*/
function PlacesCategoriesStarter() {
Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY);
Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
-
- // nsINavBookmarkObserver implementation.
- let notify = () => {
- if (!this._notifiedBookmarksSvcReady) {
- // TODO (bug 1145424): for whatever reason, even if we remove this
- // component from the category (and thus from the category cache we use
- // to notify), we keep being notified.
- this._notifiedBookmarksSvcReady = true;
- // For perf reasons unregister from the category, since no further
- // notifications are needed.
- Cc["@mozilla.org/categorymanager;1"]
- .getService(Ci.nsICategoryManager)
- .deleteCategoryEntry("bookmark-observers", "PlacesCategoriesStarter", false);
- // Directly notify PlacesUtils, to ensure it catches the notification.
- PlacesUtils.observe(null, "bookmarks-service-ready", null);
- }
- };
-
- [ "onItemAdded", "onItemRemoved", "onItemChanged", "onBeginUpdateBatch",
- "onEndUpdateBatch", "onItemVisited", "onItemMoved"
- ].forEach(aMethod => this[aMethod] = notify);
}
PlacesCategoriesStarter.prototype = {
- // nsIObserver
-
observe: function PCS_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case PlacesUtils.TOPIC_SHUTDOWN:
Services.obs.removeObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
if (Cu.isModuleLoaded("resource://gre/modules/PlacesDBUtils.jsm")) {
PlacesDBUtils.shutdown();
}
break;
case TOPIC_GATHER_TELEMETRY:
PlacesDBUtils.telemetry();
break;
+ case PlacesUtils.TOPIC_INIT_COMPLETE:
+ // Init keywords so it starts listening to bookmarks notifications.
+ PlacesUtils.keywords;
+ break;
case "idle-daily":
// Once a week run places.sqlite maintenance tasks.
let lastMaintenance =
Services.prefs.getIntPref("places.database.lastMaintenance", 0);
let nowSeconds = parseInt(Date.now() / 1000);
if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
PlacesDBUtils.maintenanceOnIdle();
}
break;
default:
throw new Error("Trying to handle an unknown category.");
}
},
- // nsISupports
-
classID: Components.ID("803938d5-e26d-4453-bf46-ad4b26e41114"),
-
_xpcom_factory: XPCOMUtils.generateSingletonFactory(PlacesCategoriesStarter),
-
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
- Ci.nsINavBookmarkObserver
])
};
-// Module Registration
-
var components = [PlacesCategoriesStarter];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -611,32 +611,16 @@ this.PlacesUtils = {
// nsIObserver
observe: function PU_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case this.TOPIC_SHUTDOWN:
Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
while (this._shutdownFunctions.length > 0) {
this._shutdownFunctions.shift().apply(this);
}
- if (this._bookmarksServiceObserversQueue.length > 0) {
- // Since we are shutting down, there's no reason to add the observers.
- this._bookmarksServiceObserversQueue.length = 0;
- }
- break;
- case "bookmarks-service-ready":
- this._bookmarksServiceReady = true;
- while (this._bookmarksServiceObserversQueue.length > 0) {
- let observerInfo = this._bookmarksServiceObserversQueue.shift();
- this.bookmarks.addObserver(observerInfo.observer, observerInfo.weak);
- }
-
- // Initialize the keywords cache to start observing bookmarks
- // notifications. This is needed as far as we support both the old and
- // the new bookmarking APIs at the same time.
- gKeywordsCachePromise.catch(Cu.reportError);
break;
}
},
onPageAnnotationSet() {},
onPageAnnotationRemoved() {},
@@ -1553,73 +1537,56 @@ this.PlacesUtils = {
* }));
*
* @param {string} name The name of the operation. Used for debugging, logging
* and crash reporting.
* @param {function(db)} task A function that takes as argument a Sqlite.jsm
* connection and returns a Promise. Shutdown is guaranteed to not interrupt
* execution of `task`.
*/
- withConnectionWrapper: (name, task) => {
+ async withConnectionWrapper(name, task) {
if (!name) {
throw new TypeError("Expecting a user-readable name");
}
- return (async function() {
- let db = await gAsyncDBWrapperPromised;
- return db.executeBeforeShutdown(name, task);
- })();
+ let db = await gAsyncDBWrapperPromised;
+ return db.executeBeforeShutdown(name, task);
},
/**
* Lazily adds a bookmarks observer, waiting for the bookmarks service to be
* alive before registering the observer. This is especially useful in the
* startup path, to avoid initializing the service just to add an observer.
*
* @param aObserver
* Object implementing nsINavBookmarkObserver
* @param [optional]aWeakOwner
* Whether to use weak ownership.
*
* @note Correct functionality of lazy observers relies on the fact Places
* notifies categories before real observers, and uses
* PlacesCategoriesStarter component to kick-off the registration.
*/
- _bookmarksServiceReady: false,
- _bookmarksServiceObserversQueue: [],
- addLazyBookmarkObserver:
- function PU_addLazyBookmarkObserver(aObserver, aWeakOwner) {
- if (this._bookmarksServiceReady) {
- this.bookmarks.addObserver(aObserver, aWeakOwner === true);
- return;
- }
- this._bookmarksServiceObserversQueue.push({ observer: aObserver,
- weak: aWeakOwner === true });
+ addLazyBookmarkObserver(aObserver, aWeakOwner) {
+ Deprecated.warning(`PlacesUtils.addLazyBookmarkObserver() is deprecated.
+ Please use PlacesUtils.bookmarks.addObserver()`,
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1371677");
+ this.bookmarks.addObserver(aObserver, aWeakOwner === true);
},
/**
* Removes a bookmarks observer added through addLazyBookmarkObserver.
*
* @param aObserver
* Object implementing nsINavBookmarkObserver
*/
- removeLazyBookmarkObserver:
- function PU_removeLazyBookmarkObserver(aObserver) {
- if (this._bookmarksServiceReady) {
- this.bookmarks.removeObserver(aObserver);
- return;
- }
- let index = -1;
- for (let i = 0;
- i < this._bookmarksServiceObserversQueue.length && index == -1; i++) {
- if (this._bookmarksServiceObserversQueue[i].observer === aObserver)
- index = i;
- }
- if (index != -1) {
- this._bookmarksServiceObserversQueue.splice(index, 1);
- }
+ removeLazyBookmarkObserver(aObserver) {
+ Deprecated.warning(`PlacesUtils.removeLazyBookmarkObserver() is deprecated.
+ Please use PlacesUtils.bookmarks.removeObserver()`,
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1371677");
+ this.bookmarks.removeObserver(aObserver);
},
/**
* Sets the character-set for a URI.
*
* @param {nsIURI} aURI
* @param {String} aCharset character-set value.
* @return {Promise}
@@ -2105,17 +2072,20 @@ XPCOMUtils.defineLazyServiceGetter(Place
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging",
"@mozilla.org/browser/tagging-service;1",
"nsITaggingService");
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "livemarks",
"@mozilla.org/browser/livemark-service;2",
"mozIAsyncLivemarks");
-XPCOMUtils.defineLazyGetter(PlacesUtils, "keywords", () => Keywords);
+XPCOMUtils.defineLazyGetter(PlacesUtils, "keywords", () => {
+ gKeywordsCachePromise.catch(Cu.reportError);
+ return Keywords;
+});
XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() {
let tm = Cc["@mozilla.org/transactionmanager;1"].
createInstance(Ci.nsITransactionManager);
tm.AddListener(PlacesUtils);
this.registerShutdownFunction(function() {
// Clear all references to local transactions in the transaction manager,
// this prevents from leaking it.
@@ -2429,51 +2399,16 @@ var Keywords = {
// Set by the keywords API to distinguish notifications fired by the old API.
// Once the old API will be gone, we can remove this and stop observing.
var gIgnoreKeywordNotifications = false;
XPCOMUtils.defineLazyGetter(this, "gKeywordsCachePromise", () =>
PlacesUtils.withConnectionWrapper("PlacesUtils: gKeywordsCachePromise",
async function(db) {
let cache = new Map();
- let rows = await db.execute(
- `SELECT keyword, url, post_data
- FROM moz_keywords k
- JOIN moz_places h ON h.id = k.place_id
- `);
- let brokenKeywords = [];
- for (let row of rows) {
- let keyword = row.getResultByName("keyword");
- try {
- let entry = { keyword,
- url: new URL(row.getResultByName("url")),
- postData: row.getResultByName("post_data") };
- cache.set(keyword, entry);
- } catch (ex) {
- // The url is invalid, don't load the keyword and remove it, or it
- // would break the whole keywords API.
- brokenKeywords.push(keyword);
- }
- }
- if (brokenKeywords.length) {
- await db.execute(
- `DELETE FROM moz_keywords
- WHERE keyword IN (${brokenKeywords.map(JSON.stringify).join(",")})
- `);
- }
-
- // Helper to get a keyword from an href.
- function keywordsForHref(href) {
- let keywords = [];
- for (let [ key, val ] of cache) {
- if (val.url.href == href)
- keywords.push(key);
- }
- return keywords;
- }
// Start observing changes to bookmarks. For now we are going to keep that
// relation for backwards compatibility reasons, but mostly because we are
// lacking a UI to manage keywords directly.
let observer = {
QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
@@ -2503,39 +2438,32 @@ XPCOMUtils.defineLazyGetter(this, "gKeyw
onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid,
parentGuid, oldVal) {
if (gIgnoreKeywordNotifications) {
return;
}
if (prop == "keyword") {
- this._onKeywordChanged(guid, val).catch(Cu.reportError);
+ this._onKeywordChanged(guid, val, oldVal).catch(Cu.reportError);
} else if (prop == "uri") {
this._onUrlChanged(guid, val, oldVal).catch(Cu.reportError);
}
},
- async _onKeywordChanged(guid, keyword) {
- let bookmark = await PlacesUtils.bookmarks.fetch(guid);
- // Due to mixed sync/async operations, by this time the bookmark could
- // have disappeared and we already handle removals in onItemRemoved.
- if (!bookmark) {
- return;
- }
-
+ async _onKeywordChanged(guid, keyword, href) {
if (keyword.length == 0) {
// We are removing a keyword.
- let keywords = keywordsForHref(bookmark.url.href)
+ let keywords = keywordsForHref(href)
for (let kw of keywords) {
cache.delete(kw);
}
} else {
// We are adding a new keyword.
- cache.set(keyword, { keyword, url: bookmark.url });
+ cache.set(keyword, { keyword, url: new URL(href) });
}
},
async _onUrlChanged(guid, url, oldUrl) {
// Check if the old url is associated with keywords.
let entries = [];
await PlacesUtils.keywords.fetch({ url: oldUrl }, e => entries.push(e));
if (entries.length == 0) {
@@ -2550,16 +2478,54 @@ XPCOMUtils.defineLazyGetter(this, "gKeyw
}
},
};
PlacesUtils.bookmarks.addObserver(observer);
PlacesUtils.registerShutdownFunction(() => {
PlacesUtils.bookmarks.removeObserver(observer);
});
+
+ let rows = await db.execute(
+ `SELECT keyword, url, post_data
+ FROM moz_keywords k
+ JOIN moz_places h ON h.id = k.place_id
+ `);
+ let brokenKeywords = [];
+ for (let row of rows) {
+ let keyword = row.getResultByName("keyword");
+ try {
+ let entry = { keyword,
+ url: new URL(row.getResultByName("url")),
+ postData: row.getResultByName("post_data") };
+ cache.set(keyword, entry);
+ } catch (ex) {
+ // The url is invalid, don't load the keyword and remove it, or it
+ // would break the whole keywords API.
+ brokenKeywords.push(keyword);
+ }
+ }
+
+ if (brokenKeywords.length) {
+ await db.execute(
+ `DELETE FROM moz_keywords
+ WHERE keyword IN (${brokenKeywords.map(JSON.stringify).join(",")})
+ `);
+ }
+
+ // Helper to get a keyword from an href.
+ function keywordsForHref(href) {
+ let keywords = [];
+ for (let [ key, val ] of cache) {
+ if (val.url.href == href)
+ keywords.push(key);
+ }
+ return keywords;
+ }
+
return cache;
}
));
// Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
// itemIds will be deprecated in favour of GUIDs, which play much better
// with multiple undo/redo operations. Because these GUIDs are already stored,
// and because we don't want to revise the transactions API once more when this
--- a/toolkit/components/places/Shutdown.cpp
+++ b/toolkit/components/places/Shutdown.cpp
@@ -186,17 +186,17 @@ ConnectionShutdownBlocker::BlockShutdown
// At this stage, any use of this database is forbidden. Get rid of
// `gDatabase`. Note, however, that the database could be
// resurrected. This can happen in particular during tests.
MOZ_ASSERT(Database::gDatabase == nullptr || Database::gDatabase == mDatabase);
Database::gDatabase = nullptr;
// Database::Shutdown will invoke Complete once the connection is closed.
- mDatabase->Shutdown(true);
+ mDatabase->Shutdown();
mState = CALLED_STORAGESHUTDOWN;
return NS_OK;
}
// mozIStorageCompletionCallback
NS_IMETHODIMP
ConnectionShutdownBlocker::Complete(nsresult, nsISupports*)
{
--- a/toolkit/components/places/nsAnnotationService.cpp
+++ b/toolkit/components/places/nsAnnotationService.cpp
@@ -1994,17 +1994,21 @@ nsAnnotationService::Observe(nsISupports
EXPIRE_SESSION);
NS_ENSURE_SUCCESS(rv, rv);
mozIStorageBaseStatement *stmts[] = {
pageAnnoStmt.get()
, itemAnnoStmt.get()
};
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
nsCOMPtr<mozIStoragePendingStatement> ps;
- rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
+ rv = conn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -197,21 +197,25 @@ nsFaviconService::ExpireAllFavicons()
);
NS_ENSURE_STATE(unlinkIconsStmt);
mozIStorageBaseStatement* stmts[] = {
removePagesStmt.get()
, removeIconsStmt.get()
, unlinkIconsStmt.get()
};
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
nsCOMPtr<mozIStoragePendingStatement> ps;
RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
new ExpireFaviconsStatementCallbackNotifier();
- return mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts),
- callback, getter_AddRefs(ps));
+ return conn->ExecuteAsync(stmts, ArrayLength(stmts),
+ callback, getter_AddRefs(ps));
}
////////////////////////////////////////////////////////////////////////////////
//// nsITimerCallback
NS_IMETHODIMP
nsFaviconService::Notify(nsITimer* timer)
{
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -1260,31 +1260,36 @@ interface nsINavHistoryService : nsISupp
const unsigned long TRANSITION_RELOAD = 9;
/**
* Set when database is coherent
*/
const unsigned short DATABASE_STATUS_OK = 0;
/**
- * Set when database did not exist and we created a new one
+ * Set when database did not exist and we created a new one.
*/
const unsigned short DATABASE_STATUS_CREATE = 1;
/**
- * Set when database was corrupt and we replaced it
+ * Set when database was corrupt and we replaced it with a new one.
*/
const unsigned short DATABASE_STATUS_CORRUPT = 2;
/**
- * Set when database schema has been upgraded
+ * Set when database schema has been upgraded.
*/
const unsigned short DATABASE_STATUS_UPGRADED = 3;
/**
+ * Set when database couldn't be opened.
+ */
+ const unsigned short DATABASE_STATUS_LOCKED = 4;
+
+ /**
* Returns the current database status
*/
readonly attribute unsigned short databaseStatus;
/**
* True if there is any history. This can be used in UI to determine whether
* the "clear history" button should be enabled or not. This is much better
* than using BrowserHistory.count since that can be very slow if there is
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -105,17 +105,17 @@ function toDate(time) {
// LivemarkService
function LivemarkService() {
// Cleanup on shutdown.
Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);
// Observe bookmarks but don't init the service just for that.
- PlacesUtils.addLazyBookmarkObserver(this, true);
+ PlacesUtils.bookmarks.addObserver(this, true);
}
LivemarkService.prototype = {
// This is just an helper for code readability.
_promiseLivemarksMap: () => gLivemarksCachePromised,
_reloading: false,
_startReloadTimer(livemarksMap, forceUpdate, reloaded) {
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -209,19 +209,16 @@ nsNavBookmarks::Init()
NS_ENSURE_STATE(mDB);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
(void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
(void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
}
- nsresult rv = ReadRoots();
- NS_ENSURE_SUCCESS(rv, rv);
-
mCanNotify = true;
// Observe annotations.
nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
annosvc->AddObserver(this);
// Allows us to notify on title changes. MUST BE LAST so it is impossible
@@ -232,20 +229,28 @@ nsNavBookmarks::Init()
history->AddObserver(this, true);
// DO NOT PUT STUFF HERE that can fail. See observer comment above.
return NS_OK;
}
nsresult
-nsNavBookmarks::ReadRoots()
+nsNavBookmarks::EnsureRoots()
{
+ if (mRoot)
+ return NS_OK;
+
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
nsCOMPtr<mozIStorageStatement> stmt;
- nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
+ nsresult rv = conn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
"'root________', 'menu________', 'toolbar_____', "
"'tags________', 'unfiled_____', 'mobile______' )"
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
@@ -370,56 +375,68 @@ nsNavBookmarks::AdjustSeparatorsSyncCoun
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetPlacesRoot(int64_t* aRoot)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aRoot = mRoot;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aRoot = mMenuRoot;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetToolbarFolder(int64_t* aFolderId)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aFolderId = mToolbarRoot;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetTagsFolder(int64_t* aRoot)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aRoot = mTagsRoot;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetUnfiledBookmarksFolder(int64_t* aRoot)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aRoot = mUnfiledRoot;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetMobileFolder(int64_t* aRoot)
{
+ nsresult rv = EnsureRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
*aRoot = mMobileRoot;
return NS_OK;
}
nsresult
nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
enum ItemType aItemType,
@@ -533,17 +550,20 @@ nsNavBookmarks::InsertBookmarkInDB(int64
// Update last modified date of the ancestors.
// TODO (bug 408991): Doing this for all ancestors would be slow without a
// nested tree, so for now update only the parent.
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, aParentId,
aDateAdded);
NS_ENSURE_SUCCESS(rv, rv);
}
- bool isTagging = aGrandParentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isTagging = aGrandParentId == tagsRootId;
if (isTagging) {
// If we're tagging a bookmark, increment the change counter for all
// bookmarks with the URI.
rv = AddSyncChangesForBookmarksWithURI(aURI, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
// Mark all affected separators as changed
@@ -626,34 +646,37 @@ nsNavBookmarks::InsertBookmark(int64_t a
TruncateTitle(aTitle, title);
rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
0, folderGuid, grandParentId, aURI, aSource,
aNewBookmarkId, guid);
NS_ENSURE_SUCCESS(rv, rv);
// If not a tag, recalculate frecency for this entry, since it changed.
- if (grandParentId != mTagsRoot) {
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (grandParentId != tagsRootId) {
rv = history->UpdateFrecency(placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
SKIP_TAGS(grandParentId == mTagsRoot),
OnItemAdded(*aNewBookmarkId, aFolder, index,
TYPE_BOOKMARK, aURI, title, dateAdded,
guid, folderGuid, aSource));
// If the bookmark has been added to a tag container, notify all
// bookmark-folder result nodes which contain a bookmark for the new
// bookmark's url.
- if (grandParentId == mTagsRoot) {
+ if (grandParentId == tagsRootId) {
// Notify a tags change to all bookmarks for this URI.
nsTArray<BookmarkData> bookmarks;
rv = GetBookmarksForURI(aURI, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
// Check that bookmarks doesn't include the current tag itemId.
MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
@@ -687,18 +710,21 @@ nsNavBookmarks::RemoveItem(int64_t aItem
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
mozStorageTransaction transaction(mDB->MainConn(), false);
// First, if not a tag, remove item annotations.
- bool isUntagging = bookmark.grandParentId == mTagsRoot;
- if (bookmark.parentId != mTagsRoot && !isUntagging) {
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isUntagging = bookmark.grandParentId == tagsRootId;
+ if (bookmark.parentId != tagsRootId && !isUntagging) {
nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
rv = annosvc->RemoveItemAnnotations(bookmark.id, aSource);
NS_ENSURE_SUCCESS(rv, rv);
}
if (bookmark.type == TYPE_FOLDER) {
// Remove all of the folder's children.
@@ -749,41 +775,41 @@ nsNavBookmarks::RemoveItem(int64_t aItem
}
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
if (bookmark.type == TYPE_BOOKMARK) {
// If not a tag, recalculate frecency for this entry, since it changed.
- if (bookmark.grandParentId != mTagsRoot) {
+ if (bookmark.grandParentId != tagsRootId) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
rv = history->UpdateFrecency(bookmark.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
// A broken url should not interrupt the removal process.
(void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
// We cannot assert since some automated tests are checking this path.
NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
}
NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
- SKIP_TAGS(bookmark.parentId == mTagsRoot ||
- bookmark.grandParentId == mTagsRoot),
+ SKIP_TAGS(bookmark.parentId == tagsRootId ||
+ bookmark.grandParentId == tagsRootId),
OnItemRemoved(bookmark.id,
bookmark.parentId,
bookmark.position,
bookmark.type,
uri,
bookmark.guid,
bookmark.parentGuid,
aSource));
- if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == mTagsRoot &&
+ if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == tagsRootId &&
uri) {
// If the removed bookmark was child of a tag container, notify a tags
// change to all bookmarks for this URI.
nsTArray<BookmarkData> bookmarks;
rv = GetBookmarksForURI(uri, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
@@ -882,18 +908,22 @@ nsNavBookmarks::CreateContainerWithID(in
rv = InsertBookmarkInDB(-1, FOLDER, aParent, index,
title, dateAdded, 0, folderGuid, grandParentId,
nullptr, aSource, aNewFolder, guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
- SKIP_TAGS(aParent == mTagsRoot),
+ SKIP_TAGS(aParent == tagsRootId),
OnItemAdded(*aNewFolder, aParent, index, FOLDER,
nullptr, title, dateAdded, guid,
folderGuid, aSource));
*aIndex = index;
return NS_OK;
}
@@ -1168,20 +1198,23 @@ nsNavBookmarks::GetDescendantChildren(in
NS_IMETHODIMP
nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId, uint16_t aSource)
{
AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveFolderChilder", OTHER);
NS_ENSURE_ARG_MIN(aFolderId, 1);
- NS_ENSURE_ARG(aFolderId != mRoot);
+ int64_t rootId;
+ nsresult rv = GetPlacesRoot(&rootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG(aFolderId != rootId);
BookmarkData folder;
- nsresult rv = FetchItemInfo(aFolderId, folder);
+ rv = FetchItemInfo(aFolderId, folder);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
// Fill folder children array recursively.
nsTArray<BookmarkData> folderChildrenArray;
rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
folderChildrenArray);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1211,42 +1244,50 @@ nsNavBookmarks::RemoveFolderChildren(int
mozStorageStatementScoper deleteStatementScoper(deleteStatement);
rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteStatement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Clean up orphan items annotations.
- rv = mDB->MainConn()->ExecuteSimpleSQL(
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = conn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos "
"WHERE id IN ("
"SELECT a.id from moz_items_annos a "
"LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
"WHERE b.id ISNULL)"));
NS_ENSURE_SUCCESS(rv, rv);
// Set the lastModified date.
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, folder.id,
RoundedPRNow());
NS_ENSURE_SUCCESS(rv, rv);
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
if (syncChangeDelta) {
nsTArray<TombstoneData> tombstones(folderChildrenArray.Length());
PRTime dateRemoved = RoundedPRNow();
for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
BookmarkData& child = folderChildrenArray[i];
if (NeedsTombstone(child)) {
// Write tombstones for synced children.
TombstoneData childTombstone = {child.guid, dateRemoved};
tombstones.AppendElement(childTombstone);
}
- bool isUntagging = child.grandParentId == mTagsRoot;
+ bool isUntagging = child.grandParentId == tagsRootId;
if (isUntagging) {
// Bump the change counter for all tagged bookmarks when removing a tag
// folder.
rv = AddSyncChangesForBookmarksWithURL(child.url, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
}
@@ -1259,40 +1300,40 @@ nsNavBookmarks::RemoveFolderChildren(int
// Call observers in reverse order to serve children before their parent.
for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
BookmarkData& child = folderChildrenArray[i];
nsCOMPtr<nsIURI> uri;
if (child.type == TYPE_BOOKMARK) {
// If not a tag, recalculate frecency for this entry, since it changed.
- if (child.grandParentId != mTagsRoot) {
+ if (child.grandParentId != tagsRootId) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
rv = history->UpdateFrecency(child.placeId);
NS_ENSURE_SUCCESS(rv, rv);
}
// A broken url should not interrupt the removal process.
(void)NS_NewURI(getter_AddRefs(uri), child.url);
// We cannot assert since some automated tests are checking this path.
NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
}
NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
- ((child.grandParentId == mTagsRoot) ? SkipTags : SkipDescendants),
+ ((child.grandParentId == tagsRootId) ? SkipTags : SkipDescendants),
OnItemRemoved(child.id,
child.parentId,
child.position,
child.type,
uri,
child.guid,
child.parentGuid,
aSource));
- if (child.type == TYPE_BOOKMARK && child.grandParentId == mTagsRoot &&
+ if (child.type == TYPE_BOOKMARK && child.grandParentId == tagsRootId &&
uri) {
// If the removed bookmark was a child of a tag container, notify all
// bookmark-folder result nodes which contain a bookmark for the removed
// bookmark's url.
nsTArray<BookmarkData> bookmarks;
rv = GetBookmarksForURI(uri, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
@@ -1462,17 +1503,20 @@ nsNavBookmarks::MoveItem(int64_t aItemId
// Mark all affected separators as changed
rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = AdjustSeparatorsSyncCounter(aNewParent, newIndex, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
- bool isChangingTagFolder = bookmark.parentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isChangingTagFolder = bookmark.parentId == tagsRootId;
if (isChangingTagFolder) {
// Moving a tag folder out of the tags root untags all its bookmarks. This
// is an odd case, but the tagging service adds an observer to handle it,
// so we bump the change counter for each untagged item for consistency.
rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
@@ -1617,18 +1661,20 @@ NS_IMETHODIMP
nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded,
uint16_t aSource)
{
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
-
- bool isTagging = bookmark.grandParentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isTagging = bookmark.grandParentId == tagsRootId;
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
// Round here so that we notify with the right value.
bookmark.dateAdded = RoundToMilliseconds(aDateAdded);
if (isTagging) {
// If we're changing a tag, bump the change counter for all tagged
// bookmarks. We use a separate code path to avoid a transaction for
@@ -1688,17 +1734,20 @@ nsNavBookmarks::SetItemLastModified(int6
uint16_t aSource)
{
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
- bool isTagging = bookmark.grandParentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isTagging = bookmark.grandParentId == tagsRootId;
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
// Round here so that we notify with the right value.
bookmark.lastModified = RoundToMilliseconds(aLastModified);
if (isTagging) {
// If we're changing a tag, bump the change counter for all tagged
// bookmarks. We use a separate code path to avoid a transaction for
@@ -1957,17 +2006,20 @@ nsNavBookmarks::SetItemTitle(int64_t aIt
uint16_t aSource)
{
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
- bool isChangingTagFolder = bookmark.parentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isChangingTagFolder = bookmark.parentId == tagsRootId;
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
nsAutoCString title;
TruncateTitle(aTitle, title);
if (isChangingTagFolder) {
// If we're changing the title of a tag folder, bump the change counter
// for all tagged bookmarks. We use a separate code path to avoid a
@@ -2464,17 +2516,20 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG(bookmark.type == TYPE_BOOKMARK);
mozStorageTransaction transaction(mDB->MainConn(), false);
- bool isTagging = bookmark.grandParentId == mTagsRoot;
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isTagging = bookmark.grandParentId == tagsRootId;
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
int64_t newPlaceId;
nsAutoCString newPlaceGuid;
rv = history->GetOrCreateIdForPage(aNewURI, &newPlaceId, newPlaceGuid);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2578,19 +2633,22 @@ nsNavBookmarks::GetBookmarkIdsForURITArr
"JOIN moz_bookmarks t on t.id = b.parent "
"WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) AND "
"t.parent IS NOT :tags_root "
"ORDER BY b.lastModified DESC, b.id DESC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
- nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ int64_t tagsRootId;
+ nsresult rv = GetTagsFolder(&tagsRootId);
NS_ENSURE_SUCCESS(rv, rv);
- rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_root"), mTagsRoot);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_root"), tagsRootId);
NS_ENSURE_SUCCESS(rv, rv);
bool more;
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
int64_t bookmarkId;
rv = stmt->GetInt64(0, &bookmarkId);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
@@ -2618,24 +2676,28 @@ nsNavBookmarks::GetBookmarksForURI(nsIUR
"ORDER BY b.lastModified DESC, b.id DESC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
+ int64_t tagsRootId;
+ rv = GetTagsFolder(&tagsRootId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
bool more;
nsAutoString tags;
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
// Skip tags.
int64_t grandParentId;
nsresult rv = stmt->GetInt64(5, &grandParentId);
NS_ENSURE_SUCCESS(rv, rv);
- if (grandParentId == mTagsRoot) {
+ if (grandParentId == tagsRootId) {
continue;
}
BookmarkData bookmark;
bookmark.grandParentId = grandParentId;
rv = stmt->GetInt64(0, &bookmark.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(1, bookmark.guid);
@@ -2901,45 +2963,46 @@ nsNavBookmarks::SetKeywordForBookmark(in
NS_LITERAL_CSTRING("keyword"),
false,
EmptyCString(),
bookmarks[i].lastModified,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid,
- EmptyCString(),
+ // Abusing oldVal to pass out the url.
+ bookmark.url,
aSource));
}
return NS_OK;
}
// A keyword can only be associated to a single URI. Check if the requested
// keyword was already associated, in such a case we will need to notify about
// the change.
+ nsAutoCString oldSpec;
nsCOMPtr<nsIURI> oldUri;
{
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT url "
"FROM moz_keywords "
"JOIN moz_places h ON h.id = place_id "
"WHERE keyword = :keyword"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore;
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
- nsAutoCString spec;
- rv = stmt->GetUTF8String(0, spec);
+ rv = stmt->GetUTF8String(0, oldSpec);
NS_ENSURE_SUCCESS(rv, rv);
- rv = NS_NewURI(getter_AddRefs(oldUri), spec);
+ rv = NS_NewURI(getter_AddRefs(oldUri), oldSpec);
NS_ENSURE_SUCCESS(rv, rv);
}
}
// If another uri is using the new keyword, we must update the keyword entry.
// Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
// trigger.
mozStorageTransaction updateTxn(mDB->MainConn(), false);
@@ -2957,17 +3020,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
NS_LITERAL_CSTRING("keyword"),
false,
EmptyCString(),
bookmarks[i].lastModified,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid,
- EmptyCString(),
+ // Abusing oldVal to pass out the url.
+ oldSpec,
aSource));
}
stmt = mDB->GetStatement(
"UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
);
NS_ENSURE_STATE(stmt);
}
@@ -3027,17 +3091,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
NS_LITERAL_CSTRING("keyword"),
false,
NS_ConvertUTF16toUTF8(keyword),
bookmarks[i].lastModified,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid,
- EmptyCString(),
+ // Abusing oldVal to pass out the url.
+ bookmark.url,
aSource));
}
return NS_OK;
}
NS_IMETHODIMP
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -240,17 +240,17 @@ private:
* @return true if aFolderId points to live bookmarks, false otherwise.
*/
bool IsLivemark(int64_t aFolderId);
/**
* Locates the root items in the bookmarks folder hierarchy assigning folder
* ids to the root properties that are exposed through the service interface.
*/
- nsresult ReadRoots();
+ nsresult EnsureRoots();
nsresult AdjustIndices(int64_t aFolder,
int32_t aStartIndex,
int32_t aEndIndex,
int32_t aDelta);
nsresult AdjustSeparatorsSyncCounter(int64_t aFolderId,
int32_t aStartIndex,
@@ -313,16 +313,18 @@ private:
* This is an handle to the Places database.
*/
RefPtr<mozilla::places::Database> mDB;
int32_t mItemCount;
nsMaybeWeakPtrArray<nsINavBookmarkObserver> mObservers;
+ // These are lazy loaded, so never access them directly, always use the
+ // XPIDL getters.
int64_t mRoot;
int64_t mMenuRoot;
int64_t mTagsRoot;
int64_t mUnfiledRoot;
int64_t mToolbarRoot;
int64_t mMobileRoot;
inline bool IsRoot(int64_t aFolderId) {
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -2215,22 +2215,25 @@ nsNavHistory::GetQueryResults(nsNavHisto
nsresult rv = ConstructQueryString(aQueries, aOptions, queryString,
paramsPresent, addParams);
NS_ENSURE_SUCCESS(rv,rv);
// create statement
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString);
#ifdef DEBUG
if (!statement) {
- nsAutoCString lastErrorString;
- (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
- int32_t lastError = 0;
- (void)mDB->MainConn()->GetLastError(&lastError);
- printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
- queryString.get(), lastError, lastErrorString.get());
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (conn) {
+ nsAutoCString lastErrorString;
+ (void)conn->GetLastErrorString(lastErrorString);
+ int32_t lastError = 0;
+ (void)conn->GetLastError(&lastError);
+ printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
+ queryString.get(), lastError, lastErrorString.get());
+ }
}
#endif
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
if (paramsPresent) {
// bind parameters
int32_t i;
@@ -2396,17 +2399,21 @@ nsNavHistory::RemovePagesInternal(const
if (aPlaceIdsQueryString.IsEmpty())
return NS_OK;
mozStorageTransaction transaction(mDB->MainConn(), false,
mozIStorageConnection::TRANSACTION_DEFERRED,
true);
// Delete all visits for the specified place ids.
- nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsresult rv = conn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING(
"DELETE FROM moz_historyvisits WHERE place_id IN (") +
aPlaceIdsQueryString +
NS_LITERAL_CSTRING(")")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString);
@@ -2481,40 +2488,44 @@ nsNavHistory::CleanupPlacesOnVisitsDelet
OnDeleteVisits(uri, 0, guid, nsINavHistoryObserver::REASON_DELETED, 0));
}
}
// if the entry is not bookmarked and is not a place: uri
// then we can remove it from moz_places.
// Note that we do NOT delete favicons. Any unreferenced favicons will be
// deleted next time the browser is shut down.
- nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsresult rv = conn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING(
"DELETE FROM moz_places WHERE id IN ( "
) + filteredPlaceIds + NS_LITERAL_CSTRING(
") "
)
);
NS_ENSURE_SUCCESS(rv, rv);
// Expire orphan icons.
- rv = mDB->MainConn()->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_pages_w_icons "
"WHERE page_url_hash NOT IN (SELECT url_hash FROM moz_places) "
));
NS_ENSURE_SUCCESS(rv, rv);
- rv = mDB->MainConn()->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_icons "
"WHERE root = 0 AND id NOT IN (SELECT icon_id FROM moz_icons_to_pages) "
));
NS_ENSURE_SUCCESS(rv, rv);
// Hosts accumulated during the places delete are updated through a trigger
// (see nsPlacesTriggers.h).
- rv = mDB->MainConn()->ExecuteSimpleSQL(
+ rv = conn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DELETE FROM moz_updatehosts_temp")
);
NS_ENSURE_SUCCESS(rv, rv);
// Invalidate frecencies of touched places, since they need recalculation.
rv = invalidateFrecencies(aPlaceIdsQueryString);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2933,22 +2944,25 @@ nsNavHistory::AsyncExecuteLegacyQueries(
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<mozIStorageAsyncStatement> statement =
mDB->GetAsyncStatement(queryString);
NS_ENSURE_STATE(statement);
#ifdef DEBUG
if (NS_FAILED(rv)) {
- nsAutoCString lastErrorString;
- (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
- int32_t lastError = 0;
- (void)mDB->MainConn()->GetLastError(&lastError);
- printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
- queryString.get(), lastError, lastErrorString.get());
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (conn) {
+ nsAutoCString lastErrorString;
+ (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
+ int32_t lastError = 0;
+ (void)mDB->MainConn()->GetLastError(&lastError);
+ printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
+ queryString.get(), lastError, lastErrorString.get());
+ }
}
#endif
NS_ENSURE_SUCCESS(rv, rv);
if (paramsPresent) {
// bind parameters
int32_t i;
for (i = 0; i < queries.Count(); i++) {
@@ -3129,24 +3143,28 @@ nsNavHistory::DecayFrecency()
NS_ENSURE_STATE(decayAdaptive);
// Delete any adaptive entries that won't help in ordering anymore.
nsCOMPtr<mozIStorageAsyncStatement> deleteAdaptive = mDB->GetAsyncStatement(
"DELETE FROM moz_inputhistory WHERE use_count < .01"
);
NS_ENSURE_STATE(deleteAdaptive);
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
mozIStorageBaseStatement *stmts[] = {
decayFrecency.get(),
decayAdaptive.get(),
deleteAdaptive.get()
};
nsCOMPtr<mozIStoragePendingStatement> ps;
RefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback();
- rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
+ rv = conn->ExecuteAsync(stmts, ArrayLength(stmts), cb,
getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
// Query stuff *****************************************************************
@@ -4378,25 +4396,29 @@ nsNavHistory::UpdateFrecency(int64_t aPl
"SET hidden = 0 "
"WHERE id = :page_id AND frecency <> 0"
);
NS_ENSURE_STATE(updateHiddenStmt);
rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
aPlaceId);
NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
mozIStorageBaseStatement *stmts[] = {
updateFrecencyStmt.get()
, updateHiddenStmt.get()
};
-
RefPtr<AsyncStatementCallbackNotifier> cb =
new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED);
nsCOMPtr<mozIStoragePendingStatement> ps;
- rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
+ rv = conn->ExecuteAsync(stmts, ArrayLength(stmts), cb,
getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
namespace {
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -836,37 +836,41 @@ nsPlacesExpiration.prototype = {
diskAvailableBytes * DATABASE_TO_DISK_PERC / 100,
DATABASE_MAX_SIZE
);
// Calculate avg size of a URI in the database.
let db;
try {
db = await PlacesUtils.promiseDBConnection();
+ if (db) {
+ let row = (await db.execute(`SELECT * FROM pragma_page_size(),
+ pragma_page_count(),
+ pragma_freelist_count(),
+ (SELECT count(*) FROM moz_places)`))[0];
+ let pageSize = row.getResultByIndex(0);
+ let pageCount = row.getResultByIndex(1);
+ let freelistCount = row.getResultByIndex(2);
+ let uriCount = row.getResultByIndex(3);
+ let dbSize = (pageCount - freelistCount) * pageSize;
+ let avgURISize = Math.ceil(dbSize / uriCount);
+ // For new profiles this value may be too large, due to the Sqlite header,
+ // or Infinity when there are no pages. Thus we must limit it.
+ if (avgURISize > (URIENTRY_AVG_SIZE * 3)) {
+ avgURISize = URIENTRY_AVG_SIZE;
+ }
+ this._urisLimit = Math.ceil(optimalDatabaseSize / avgURISize);
+ }
} catch (ex) {
// We may have been initialized late in the shutdown process, maybe
// by a call to clear history on shutdown.
// If we're unable to get a connection clone, we'll just proceed with
// the default value, it should not be critical at this point in the
// application life-cycle.
}
- if (db) {
- let pageSize = (await db.execute(`PRAGMA page_size`))[0].getResultByIndex(0);
- let pageCount = (await db.execute(`PRAGMA page_count`))[0].getResultByIndex(0);
- let freelistCount = (await db.execute(`PRAGMA freelist_count`))[0].getResultByIndex(0);
- let dbSize = (pageCount - freelistCount) * pageSize;
- let uriCount = (await db.execute(`SELECT count(*) FROM moz_places`))[0].getResultByIndex(0);
- let avgURISize = Math.ceil(dbSize / uriCount);
- // For new profiles this value may be too large, due to the Sqlite header,
- // or Infinity when there are no pages. Thus we must limit it.
- if (avgURISize > (URIENTRY_AVG_SIZE * 3)) {
- avgURISize = URIENTRY_AVG_SIZE;
- }
- this._urisLimit = Math.ceil(optimalDatabaseSize / avgURISize);
- }
}
// Expose the calculated limit to other components.
this._prefBranch.setIntPref(PREF_READONLY_CALCULATED_MAX_URIS,
this._urisLimit);
// Get the expiration interval value.
this._interval = this._prefBranch.getIntPref(PREF_INTERVAL_SECONDS,
--- a/toolkit/components/places/tests/bookmarks/test_keywords.js
+++ b/toolkit/components/places/tests/bookmarks/test_keywords.js
@@ -72,160 +72,165 @@ add_task(function test_invalid_input() {
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(null, "k"),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(0, "k"),
/NS_ERROR_ILLEGAL_VALUE/);
});
add_task(async function test_addBookmarkAndKeyword() {
- check_keyword(URI1, null);
+ await check_keyword(URI1, null);
let fc = await foreign_count(URI1);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI1,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
let bookmark = await PlacesUtils.bookmarks.fetch({ url: URI1 });
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "keyword", false, "keyword",
bookmark.lastModified * 1000, bookmark.type,
(await PlacesUtils.promiseItemId(bookmark.parentGuid)),
- bookmark.guid, bookmark.parentGuid, "",
+ bookmark.guid, bookmark.parentGuid,
+ bookmark.url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
await PlacesTestUtils.promiseAsyncUpdates();
- check_keyword(URI1, "keyword");
+ await check_keyword(URI1, "keyword");
Assert.equal((await foreign_count(URI1)), fc + 2); // + 1 bookmark + 1 keyword
await PlacesTestUtils.promiseAsyncUpdates();
await check_orphans();
});
add_task(async function test_addBookmarkToURIHavingKeyword() {
// The uri has already a keyword.
- check_keyword(URI1, "keyword");
+ await check_keyword(URI1, "keyword");
let fc = await foreign_count(URI1);
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI1,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
- check_keyword(URI1, "keyword");
+ await check_keyword(URI1, "keyword");
Assert.equal((await foreign_count(URI1)), fc + 1); // + 1 bookmark
PlacesUtils.bookmarks.removeItem(itemId);
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
add_task(async function test_sameKeywordDifferentURI() {
let fc1 = await foreign_count(URI1);
let fc2 = await foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test2");
- check_keyword(URI1, "keyword");
- check_keyword(URI2, null);
+ await check_keyword(URI1, "keyword");
+ await check_keyword(URI2, null);
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "kEyWoRd");
let bookmark1 = await PlacesUtils.bookmarks.fetch({ url: URI1 });
let bookmark2 = await PlacesUtils.bookmarks.fetch({ url: URI2 });
observer.check([ { name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmark1.guid)),
"keyword", false, "",
bookmark1.lastModified * 1000, bookmark1.type,
(await PlacesUtils.promiseItemId(bookmark1.parentGuid)),
- bookmark1.guid, bookmark1.parentGuid, "",
+ bookmark1.guid, bookmark1.parentGuid,
+ bookmark1.url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ itemId, "keyword", false, "keyword",
bookmark2.lastModified * 1000, bookmark2.type,
(await PlacesUtils.promiseItemId(bookmark2.parentGuid)),
- bookmark2.guid, bookmark2.parentGuid, "",
+ bookmark2.guid, bookmark2.parentGuid,
+ bookmark2.url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
await PlacesTestUtils.promiseAsyncUpdates();
// The keyword should have been "moved" to the new URI.
- check_keyword(URI1, null);
+ await check_keyword(URI1, null);
Assert.equal((await foreign_count(URI1)), fc1 - 1); // - 1 keyword
- check_keyword(URI2, "keyword");
+ await check_keyword(URI2, "keyword");
Assert.equal((await foreign_count(URI2)), fc2 + 2); // + 1 bookmark + 1 keyword
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
add_task(async function test_sameURIDifferentKeyword() {
let fc = await foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test2");
- check_keyword(URI2, "keyword");
+ await check_keyword(URI2, "keyword");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword2");
let bookmarks = [];
await PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
observer.check([ { name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmarks[0].guid)),
"keyword", false, "keyword2",
bookmarks[0].lastModified * 1000, bookmarks[0].type,
(await PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
- bookmarks[0].guid, bookmarks[0].parentGuid, "",
+ bookmarks[0].guid, bookmarks[0].parentGuid,
+ bookmarks[0].url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "keyword2",
bookmarks[1].lastModified * 1000, bookmarks[1].type,
(await PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
- bookmarks[1].guid, bookmarks[1].parentGuid, "",
+ bookmarks[1].guid, bookmarks[1].parentGuid,
+ bookmarks[0].url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
await PlacesTestUtils.promiseAsyncUpdates();
- check_keyword(URI2, "keyword2");
+ await check_keyword(URI2, "keyword2");
Assert.equal((await foreign_count(URI2)), fc + 2); // + 1 bookmark + 1 keyword
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
add_task(async function test_removeBookmarkWithKeyword() {
let fc = await foreign_count(URI2);
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
// The keyword should not be removed, since there are other bookmarks yet.
PlacesUtils.bookmarks.removeItem(itemId);
- check_keyword(URI2, "keyword2");
+ await check_keyword(URI2, "keyword2");
Assert.equal((await foreign_count(URI2)), fc); // + 1 bookmark - 1 bookmark
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
add_task(async function test_unsetKeyword() {
let fc = await foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
@@ -239,40 +244,43 @@ add_task(async function test_unsetKeywor
let bookmarks = [];
await PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
do_print(bookmarks.length);
observer.check([ { name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmarks[0].guid)),
"keyword", false, "",
bookmarks[0].lastModified * 1000, bookmarks[0].type,
(await PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
- bookmarks[0].guid, bookmarks[0].parentGuid, "",
+ bookmarks[0].guid, bookmarks[0].parentGuid,
+ bookmarks[0].url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "",
bookmarks[1].lastModified * 1000, bookmarks[1].type,
(await PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
- bookmarks[1].guid, bookmarks[1].parentGuid, "",
+ bookmarks[1].guid, bookmarks[1].parentGuid,
+ bookmarks[1].url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (await PlacesUtils.promiseItemId(bookmarks[2].guid)),
"keyword", false, "",
bookmarks[2].lastModified * 1000, bookmarks[2].type,
(await PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
- bookmarks[2].guid, bookmarks[2].parentGuid, "",
+ bookmarks[2].guid, bookmarks[2].parentGuid,
+ bookmarks[2].url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
- check_keyword(URI1, null);
- check_keyword(URI2, null);
+ await check_keyword(URI1, null);
+ await check_keyword(URI2, null);
Assert.equal((await foreign_count(URI2)), fc - 1); // + 1 bookmark - 2 keyword
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
add_task(async function test_addRemoveBookmark() {
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI3,
@@ -284,19 +292,20 @@ add_task(async function test_addRemoveBo
let parentId = await PlacesUtils.promiseItemId(bookmark.parentGuid);
PlacesUtils.bookmarks.removeItem(itemId);
observer.check([ { name: "onItemChanged",
arguments: [ itemId,
"keyword", false, "keyword",
bookmark.lastModified * 1000, bookmark.type,
parentId,
- bookmark.guid, bookmark.parentGuid, "",
+ bookmark.guid, bookmark.parentGuid,
+ bookmark.url.href,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
- check_keyword(URI3, null);
+ await check_keyword(URI3, null);
// Don't check the foreign count since the process is async.
// The new test_keywords.js in unit is checking this though.
await PlacesTestUtils.promiseAsyncUpdates();
- check_orphans();
+ await check_orphans();
});
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -45,16 +45,18 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
"resource://gre/modules/PlacesTransactions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
+ "resource://testing-common/TestUtils.jsm");
// This imports various other objects in addition to PlacesUtils.
Cu.import("resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "SMALLPNG_DATA_URI", function() {
return NetUtil.newURI(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
"AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
deleted file mode 100644
--- a/toolkit/components/places/tests/unit/test_PlacesUtils_lazyobservers.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-add_task(async function test_lazyBookmarksObservers() {
- const TEST_URI = Services.io.newURI("http://moz.org/");
-
- let promise = PromiseUtils.defer();
-
- let observer = {
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsINavBookmarkObserver,
- ]),
-
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
- do_check_true(aURI.equals(TEST_URI));
- PlacesUtils.removeLazyBookmarkObserver(this);
- promise.resolve();
- },
- onItemRemoved() {},
- onItemChanged() {},
- onItemVisited() {},
- onItemMoved() {},
- };
-
- // Check registration and removal with uninitialized bookmarks service.
- PlacesUtils.addLazyBookmarkObserver(observer);
- PlacesUtils.removeLazyBookmarkObserver(observer);
-
- // Add a proper lazy observer we will test.
- PlacesUtils.addLazyBookmarkObserver(observer);
-
- // Check that we don't leak when adding and removing an observer while the
- // bookmarks service is instantiated but no change happened (bug 721319).
- PlacesUtils.bookmarks;
- PlacesUtils.addLazyBookmarkObserver(observer);
- PlacesUtils.removeLazyBookmarkObserver(observer);
- try {
- PlacesUtils.bookmarks.removeObserver(observer);
- do_throw("Trying to remove a nonexisting observer should throw!");
- } catch (ex) {}
-
- await PlacesUtils.bookmarks.insert({
- parentGuid: PlacesUtils.bookmarks.unfiledGuid,
- url: TEST_URI,
- title: "Bookmark title"
- });
-
- await promise;
-});
--- a/toolkit/components/places/tests/unit/test_history_catobs.js
+++ b/toolkit/components/places/tests/unit/test_history_catobs.js
@@ -1,50 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function() {
do_load_manifest("nsDummyObserver.manifest");
- let dummyCreated = false;
- let dummyReceivedOnVisit = false;
-
- Services.obs.addObserver(function created() {
- Services.obs.removeObserver(created, "dummy-observer-created");
- dummyCreated = true;
- }, "dummy-observer-created");
- Services.obs.addObserver(function visited() {
- Services.obs.removeObserver(visited, "dummy-observer-visited");
- dummyReceivedOnVisit = true;
- }, "dummy-observer-visited");
+ let promises = [];
+ let resolved = 0;
+ promises.push(TestUtils.topicObserved("dummy-observer-created", () => ++resolved));
+ promises.push(TestUtils.topicObserved("dummy-observer-visited", () => ++resolved));
let initialObservers = PlacesUtils.history.getObservers();
// Add a common observer, it should be invoked after the category observer.
- let notificationsPromised = new Promise((resolve, reject) => {
- PlacesUtils.history.addObserver({
- __proto__: NavHistoryObserver.prototype,
- onVisit() {
- let observers = PlacesUtils.history.getObservers();
- Assert.equal(observers.length, initialObservers.length + 1);
+ promises.push(new Promise(resolve => {
+ let observer = new NavHistoryObserver();
+ observer.onVisit = uri => {
+ do_print("Got visit for " + uri.spec);
+ let observers = PlacesUtils.history.getObservers();
+ let observersCount = observers.length;
+ Assert.ok(observersCount > initialObservers.length);
- // Check the common observer is the last one.
- for (let i = 0; i < initialObservers.length; ++i) {
- Assert.equal(initialObservers[i], observers[i]);
- }
+ // Check the common observer is the last one.
+ for (let i = 0; i < initialObservers.length; ++i) {
+ Assert.equal(initialObservers[i], observers[i]);
+ }
- PlacesUtils.history.removeObserver(this);
- observers = PlacesUtils.history.getObservers();
- Assert.equal(observers.length, initialObservers.length);
+ PlacesUtils.history.removeObserver(observer);
+ observers = PlacesUtils.history.getObservers();
+ Assert.ok(observers.length < observersCount);
- // Check the category observer has been invoked before this one.
- Assert.ok(dummyCreated);
- Assert.ok(dummyReceivedOnVisit);
- resolve();
- }
- });
- });
+ // Check the category observer has been invoked before this one.
+ Assert.equal(resolved, 2);
+ resolve();
+ };
+ PlacesUtils.history.addObserver(observer);
+ }));
- // Add a visit.
+ do_print("Add a visit");
await PlacesTestUtils.addVisits(uri("http://typed.mozilla.org"));
-
- await notificationsPromised;
+ await Promise.all(promises);
});
--- a/toolkit/components/places/tests/unit/test_history_clear.js
+++ b/toolkit/components/places/tests/unit/test_history_clear.js
@@ -26,34 +26,17 @@ function promiseOnClearHistoryObserved()
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavHistoryObserver,
])
}
PlacesUtils.history.addObserver(historyObserver);
});
}
-// This global variable is a promise object, initialized in run_test and waited
-// upon in the first asynchronous test. It is resolved when the
-// "places-init-complete" notification is received. We cannot initialize it in
-// the asynchronous test, because then it's too late to register the observer.
-var promiseInit;
-
-function run_test() {
- // places-init-complete is notified after run_test, and it will
- // run a first frecency fix through async statements.
- // To avoid random failures we have to run after all of this.
- promiseInit = promiseTopicObserved(PlacesUtils.TOPIC_INIT_COMPLETE);
-
- run_next_test();
-}
-
add_task(async function test_history_clear() {
- await promiseInit;
-
await PlacesTestUtils.addVisits([
{ uri: uri("http://typed.mozilla.org/"),
transition: TRANSITION_TYPED },
{ uri: uri("http://link.mozilla.org/"),
transition: TRANSITION_LINK },
{ uri: uri("http://download.mozilla.org/"),
transition: TRANSITION_DOWNLOAD },
{ uri: uri("http://redir_temp.mozilla.org/"),
--- a/toolkit/components/places/tests/unit/test_history_notifications.js
+++ b/toolkit/components/places/tests/unit/test_history_notifications.js
@@ -1,38 +1,44 @@
const NS_PLACES_INIT_COMPLETE_TOPIC = "places-init-complete";
-const NS_PLACES_DATABASE_LOCKED_TOPIC = "places-database-locked";
+let gLockedConn;
-add_task(async function() {
+add_task(async function setup() {
// Create a dummy places.sqlite and open an unshared connection on it
let db = Services.dirsvc.get("ProfD", Ci.nsIFile);
db.append("places.sqlite");
- let dbConn = Services.storage.openUnsharedDatabase(db);
+ gLockedConn = Services.storage.openUnsharedDatabase(db);
Assert.ok(db.exists(), "The database should have been created");
// We need an exclusive lock on the db
- dbConn.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
+ gLockedConn.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
// Exclusive locking is lazy applied, we need to make a write to activate it
- dbConn.executeSimpleSQL("PRAGMA USER_VERSION = 1");
+ gLockedConn.executeSimpleSQL("PRAGMA USER_VERSION = 1");
+});
- // Try to create history service while the db is locked
- let promiseLocked = promiseTopicObserved(NS_PLACES_DATABASE_LOCKED_TOPIC);
- Assert.throws(() => Cc["@mozilla.org/browser/nav-history-service;1"]
- .getService(Ci.nsINavHistoryService),
- /NS_ERROR_XPC_GS_RETURNED_FAILURE/);
- await promiseLocked;
+add_task(async function locked() {
+ // Try to create history service while the db is locked.
+ // It should be possible to create the service, but any method using the
+ // database will fail.
+ let resolved = false;
+ let promiseComplete = promiseTopicObserved(NS_PLACES_INIT_COMPLETE_TOPIC)
+ .then(() => resolved = true);
+ let history = Cc["@mozilla.org/browser/nav-history-service;1"]
+ .createInstance(Ci.nsINavHistoryService);
+ // The notification shouldn't happen until something tries to use the database.
+ await new Promise(resolve => do_timeout(100, resolve));
+ Assert.equal(resolved, false, "The notification should not have been fired yet");
+ // This will initialize the database.
+ Assert.equal(history.databaseStatus, history.DATABASE_STATUS_LOCKED);
+ await promiseComplete;
// Close our connection and try to cleanup the file (could fail on Windows)
- dbConn.close();
+ gLockedConn.close();
+ let db = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ db.append("places.sqlite");
if (db.exists()) {
try {
db.remove(false);
} catch (e) {
do_print("Unable to remove dummy places.sqlite");
}
}
-
- // Create history service correctly
- let promiseComplete = promiseTopicObserved(NS_PLACES_INIT_COMPLETE_TOPIC);
- Cc["@mozilla.org/browser/nav-history-service;1"]
- .getService(Ci.nsINavHistoryService);
- await promiseComplete;
});
--- a/toolkit/components/places/tests/unit/test_keywords.js
+++ b/toolkit/components/places/tests/unit/test_keywords.js
@@ -78,40 +78,16 @@ function expectBookmarkNotifications() {
return target[name];
return undefined;
}
});
PlacesUtils.bookmarks.addObserver(observer);
return observer;
}
-// This test must be the first one, since it creates the keywords cache.
-add_task(async function test_invalidURL() {
- await PlacesTestUtils.addVisits("http://test.com/");
- // Change to url to an invalid one, there's no API for that, so we must do
- // that manually.
- await PlacesUtils.withConnectionWrapper("test_invalidURL", async function(db) {
- await db.execute(
- `UPDATE moz_places SET url = :broken, url_hash = hash(:broken)
- WHERE id = (SELECT id FROM moz_places WHERE url_hash = hash(:url))`,
- { url: "http://test.com/", broken: "<invalid url>" });
-
- await db.execute(
- `INSERT INTO moz_keywords (keyword, place_id)
- VALUES (:kw, (SELECT id FROM moz_places WHERE url_hash = hash(:broken)))`,
- { broken: "<invalid url>", kw: "keyword" });
- });
- await check_keyword(false, "http://broken.com/", "keyword");
- await check_keyword(false, null, "keyword");
- await PlacesUtils.withConnectionWrapper("test_invalidURL", async function(db) {
- let rows = await db.execute(`SELECT * FROM moz_keywords`);
- Assert.equal(rows.length, 0, "The broken keyword should have been removed");
- });
-});
-
add_task(async function test_invalid_input() {
Assert.throws(() => PlacesUtils.keywords.fetch(null),
/Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.fetch(5),
/Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.fetch(undefined),
/Invalid keyword/);
Assert.throws(() => PlacesUtils.keywords.fetch({ keyword: null }),
@@ -433,17 +409,17 @@ add_task(async function test_sameURIDiff
// Now remove the bookmark.
await PlacesUtils.bookmarks.remove(bookmark);
while ((await foreign_count("http://example.com/")));
await check_keyword(false, "http://example.com/", "keyword");
await check_keyword(false, "http://example.com/", "keyword2");
await check_keyword(false, "http://example.com/", "keyword3");
- check_no_orphans();
+ await check_no_orphans();
});
add_task(async function test_deleteKeywordMultipleBookmarks() {
let fc = await foreign_count("http://example.com/");
let observer = expectBookmarkNotifications();
let bookmark1 = await PlacesUtils.bookmarks.insert({ url: "http://example.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
@@ -487,45 +463,45 @@ add_task(async function test_deleteKeywo
bookmark1.guid, bookmark1.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] } ]);
// Now remove the bookmarks.
await PlacesUtils.bookmarks.remove(bookmark1);
await PlacesUtils.bookmarks.remove(bookmark2);
Assert.equal((await foreign_count("http://example.com/")), fc); // -2 bookmarks
- check_no_orphans();
+ await check_no_orphans();
});
add_task(async function test_multipleKeywordsSamePostData() {
await PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/", postData: "postData1" });
await check_keyword(true, "http://example.com/", "keyword", "postData1");
// Add another keyword with same postData, should fail.
await Assert.rejects(PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/", postData: "postData1" }),
/constraint failed/);
await check_keyword(false, "http://example.com/", "keyword2", "postData1");
await PlacesUtils.keywords.remove("keyword");
- check_no_orphans();
+ await check_no_orphans();
});
add_task(async function test_oldPostDataAPI() {
let bookmark = await PlacesUtils.bookmarks.insert({ url: "http://example.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
await PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
let itemId = await PlacesUtils.promiseItemId(bookmark.guid);
await PlacesUtils.setPostDataForBookmark(itemId, "postData");
await check_keyword(true, "http://example.com/", "keyword", "postData");
Assert.equal(PlacesUtils.getPostDataForBookmark(itemId), "postData");
await PlacesUtils.keywords.remove("keyword");
await PlacesUtils.bookmarks.remove(bookmark);
- check_no_orphans();
+ await check_no_orphans();
});
add_task(async function test_oldKeywordsAPI() {
let bookmark = await PlacesUtils.bookmarks.insert({ url: "http://example.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
await check_keyword(false, "http://example.com/", "keyword");
let itemId = await PlacesUtils.promiseItemId(bookmark.guid);
@@ -539,17 +515,17 @@ add_task(async function test_oldKeywords
await PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com" });
Assert.equal(PlacesUtils.bookmarks.getKeywordForBookmark(itemId), "keyword");
let entry = await PlacesUtils.keywords.fetch("keyword");
Assert.equal(entry.url, "http://example.com/");
await PlacesUtils.bookmarks.remove(bookmark);
- check_no_orphans();
+ await check_no_orphans();
});
add_task(async function test_bookmarkURLChange() {
let fc1 = await foreign_count("http://example1.com/");
let fc2 = await foreign_count("http://example2.com/");
let bookmark = await PlacesUtils.bookmarks.insert({ url: "http://example1.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
await PlacesUtils.keywords.insert({ keyword: "keyword",
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -106,17 +106,16 @@ skip-if = (os == "win" && os_version ==
skip-if = true
[test_null_interfaces.js]
[test_onItemChanged_tags.js]
[test_pageGuid_bookmarkGuid.js]
[test_frecency_observers.js]
[test_placeURIs.js]
[test_PlacesSearchAutocompleteProvider.js]
[test_PlacesUtils_invalidateCachedGuidFor.js]
-[test_PlacesUtils_lazyobservers.js]
[test_placesTxn.js]
[test_preventive_maintenance.js]
[test_preventive_maintenance_checkAndFixDatabase.js]
[test_preventive_maintenance_runTasks.js]
[test_promiseBookmarksTree.js]
[test_resolveNullBookmarkTitles.js]
[test_result_sort.js]
[test_resultsAsVisit_details.js]
--- a/toolkit/components/places/toolkitplaces.manifest
+++ b/toolkit/components/places/toolkitplaces.manifest
@@ -12,17 +12,17 @@ contract @mozilla.org/autocomplete/searc
component {705a423f-2f69-42f3-b9fe-1517e0dee56f} nsPlacesExpiration.js
contract @mozilla.org/places/expiration;1 {705a423f-2f69-42f3-b9fe-1517e0dee56f}
category history-observers nsPlacesExpiration @mozilla.org/places/expiration;1
# PlacesCategoriesStarter.js
component {803938d5-e26d-4453-bf46-ad4b26e41114} PlacesCategoriesStarter.js
contract @mozilla.org/places/categoriesStarter;1 {803938d5-e26d-4453-bf46-ad4b26e41114}
category idle-daily PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
-category bookmark-observers PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
+category places-init-complete PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
# ColorAnalyzer.js
component {d056186c-28a0-494e-aacc-9e433772b143} ColorAnalyzer.js
contract @mozilla.org/places/colorAnalyzer;1 {d056186c-28a0-494e-aacc-9e433772b143}
# UnifiedComplete.js
component {f964a319-397a-4d21-8be6-5cdd1ee3e3ae} UnifiedComplete.js
contract @mozilla.org/autocomplete/search;1?name=unifiedcomplete {f964a319-397a-4d21-8be6-5cdd1ee3e3ae}