Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsNavBookmarks.h"
#include "nsNavHistory.h"
#include "nsPlacesMacros.h"
#include "Helpers.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsITaggingService.h"
#include "nsNetUtil.h"
#include "nsIProtocolHandler.h"
#include "nsIObserverService.h"
#include "nsUnicharUtils.h"
#include "nsPrintfCString.h"
#include "nsQueryObject.h"
#include "mozIStorageValueArray.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/storage.h"
#include "mozilla/dom/PlacesBookmarkAddition.h"
#include "mozilla/dom/PlacesBookmarkRemoved.h"
#include "mozilla/dom/PlacesBookmarkTags.h"
#include "mozilla/dom/PlacesBookmarkTime.h"
#include "mozilla/dom/PlacesBookmarkTitle.h"
#include "mozilla/dom/PlacesObservers.h"
#include "mozilla/dom/PlacesVisit.h"
using namespace mozilla;
// These columns sit to the right of the kGetInfoIndex_* columns.
const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
const int32_t nsNavBookmarks::kGetChildrenIndex_SyncStatus = 22;
using namespace mozilla::dom;
using namespace mozilla::places;
extern "C" {
// Returns the total number of Sync changes recorded since Places startup for
// all bookmarks. This function uses C linkage because it's called from the
// Rust synced bookmarks mirror, on the storage thread. Using `get_service` to
// access the bookmarks service from Rust trips a thread-safety assertion, so
// we can't use `nsNavBookmarks::GetTotalSyncChanges`.
int64_t NS_NavBookmarksTotalSyncChanges() {
return nsNavBookmarks::sTotalSyncChanges;
}
} // extern "C"
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
namespace {
// Returns the sync change counter increment for a change source constant.
inline int64_t DetermineSyncChangeDelta(uint16_t aSource) {
return aSource == nsINavBookmarksService::SOURCE_SYNC ? 0 : 1;
}
// Returns the sync status for a new item inserted by a change source.
inline int32_t DetermineInitialSyncStatus(uint16_t aSource) {
if (aSource == nsINavBookmarksService::SOURCE_SYNC) {
return nsINavBookmarksService::SYNC_STATUS_NORMAL;
}
if (aSource == nsINavBookmarksService::SOURCE_RESTORE_ON_STARTUP) {
return nsINavBookmarksService::SYNC_STATUS_UNKNOWN;
}
return nsINavBookmarksService::SYNC_STATUS_NEW;
}
// Indicates whether an item has been uploaded to the server and
// needs a tombstone on deletion.
inline bool NeedsTombstone(const BookmarkData& aBookmark) {
return aBookmark.syncStatus == nsINavBookmarksService::SYNC_STATUS_NORMAL;
}
inline nsresult GetTags(nsIURI* aURI, nsTArray<nsString>& aResult) {
nsresult rv;
nsCOMPtr<nsITaggingService> taggingService =
do_GetService("@mozilla.org/browser/tagging-service;1", &rv);
if (NS_FAILED(rv)) {
return rv;
}
return taggingService->GetTagsForURI(aURI, aResult);
}
} // namespace
nsNavBookmarks::nsNavBookmarks() : mCanNotify(false) {
NS_ASSERTION(!gBookmarksService,
"Attempting to create two instances of the service!");
gBookmarksService = this;
}
nsNavBookmarks::~nsNavBookmarks() {
NS_ASSERTION(gBookmarksService == this,
"Deleting a non-singleton instance of the service");
if (gBookmarksService == this) gBookmarksService = nullptr;
}
NS_IMPL_ISUPPORTS(nsNavBookmarks, nsINavBookmarksService, nsIObserver,
nsISupportsWeakReference)
Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
void // static
nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
const int64_t aLastInsertedId) {
MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
sLastInsertedItemId = aLastInsertedId;
}
Atomic<int64_t> nsNavBookmarks::sTotalSyncChanges(0);
void // static
nsNavBookmarks::NoteSyncChange() {
sTotalSyncChanges++;
}
nsresult nsNavBookmarks::Init() {
mDB = Database::GetDatabase();
NS_ENSURE_STATE(mDB);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
(void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
}
mCanNotify = true;
// DO NOT PUT STUFF HERE that can fail. See observer comment above.
return NS_OK;
}
nsresult nsNavBookmarks::AdjustIndices(int64_t aFolderId, int32_t aStartIndex,
int32_t aEndIndex, int32_t aDelta) {
NS_ASSERTION(
aStartIndex >= 0 && aEndIndex <= INT32_MAX && aStartIndex <= aEndIndex,
"Bad indices");
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"UPDATE moz_bookmarks SET position = position + :delta "
"WHERE parent = :parent "
"AND position BETWEEN :from_index AND :to_index");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt32ByName("delta"_ns, aDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("from_index"_ns, aStartIndex);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("to_index"_ns, aEndIndex);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavBookmarks::AdjustSeparatorsSyncCounter(int64_t aFolderId,
int32_t aStartIndex,
int64_t aSyncChangeDelta) {
MOZ_ASSERT(aStartIndex >= 0, "Bad start position");
if (!aSyncChangeDelta) {
return NS_OK;
}
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + :delta "
"WHERE parent = :parent AND position >= :start_index "
"AND type = :item_type ");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName("delta"_ns, aSyncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("start_index"_ns, aStartIndex);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("item_type"_ns, TYPE_SEPARATOR);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetTagsFolder(int64_t* aRoot) {
int64_t id = mDB->GetTagsFolderId();
NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
*aRoot = id;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetTotalSyncChanges(int64_t* aTotalSyncChanges) {
*aTotalSyncChanges = sTotalSyncChanges;
return NS_OK;
}
nsresult nsNavBookmarks::InsertBookmarkInDB(
int64_t aPlaceId, enum ItemType aItemType, int64_t aParentId,
int32_t aIndex, const nsACString& aTitle, PRTime aDateAdded,
PRTime aLastModified, const nsACString& aParentGuid, int64_t aGrandParentId,
nsIURI* aURI, uint16_t aSource, int64_t* _itemId, nsACString& _guid) {
// Check for a valid itemId.
MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
// Check for a valid placeId.
MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"INSERT INTO moz_bookmarks "
"(id, fk, type, parent, position, title, "
"dateAdded, lastModified, guid, syncStatus, syncChangeCounter) "
"VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
":item_title, :date_added, :last_modified, "
":item_guid, :sync_status, :change_counter)");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv;
if (*_itemId != -1)
rv = stmt->BindInt64ByName("item_id"_ns, *_itemId);
else
rv = stmt->BindNullByName("item_id"_ns);
NS_ENSURE_SUCCESS(rv, rv);
if (aPlaceId != -1)
rv = stmt->BindInt64ByName("page_id"_ns, aPlaceId);
else
rv = stmt->BindNullByName("page_id"_ns);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("item_type"_ns, aItemType);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("parent"_ns, aParentId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("item_index"_ns, aIndex);
NS_ENSURE_SUCCESS(rv, rv);
if (aTitle.IsEmpty())
rv = stmt->BindNullByName("item_title"_ns);
else
rv = stmt->BindUTF8StringByName("item_title"_ns, aTitle);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("date_added"_ns, aDateAdded);
NS_ENSURE_SUCCESS(rv, rv);
if (aLastModified) {
rv = stmt->BindInt64ByName("last_modified"_ns, aLastModified);
} else {
rv = stmt->BindInt64ByName("last_modified"_ns, aDateAdded);
}
NS_ENSURE_SUCCESS(rv, rv);
// Could use IsEmpty because our callers check for GUID validity,
// but it doesn't hurt.
bool hasExistingGuid = _guid.Length() == 12;
if (hasExistingGuid) {
MOZ_ASSERT(IsValidGUID(_guid));
rv = stmt->BindUTF8StringByName("item_guid"_ns, _guid);
NS_ENSURE_SUCCESS(rv, rv);
} else {
nsAutoCString guid;
rv = GenerateGUID(guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName("item_guid"_ns, guid);
NS_ENSURE_SUCCESS(rv, rv);
_guid.Assign(guid);
}
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
rv = stmt->BindInt64ByName("change_counter"_ns, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
uint16_t syncStatus = DetermineInitialSyncStatus(aSource);
rv = stmt->BindInt32ByName("sync_status"_ns, syncStatus);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Remove stale tombstones if we're reinserting an item.
if (hasExistingGuid) {
rv = RemoveTombstone(_guid);
NS_ENSURE_SUCCESS(rv, rv);
}
if (*_itemId == -1) {
*_itemId = sLastInsertedItemId;
}
if (aParentId > 0) {
// 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);
}
int64_t tagsRootId = mDB->GetTagsFolderId();
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
rv = AdjustSeparatorsSyncCounter(aParentId, aIndex + 1, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
// Add a cache entry since we know everything about this bookmark.
BookmarkData bookmark;
bookmark.id = *_itemId;
bookmark.guid.Assign(_guid);
if (!aTitle.IsEmpty()) {
bookmark.title.Assign(aTitle);
}
bookmark.position = aIndex;
bookmark.placeId = aPlaceId;
bookmark.parentId = aParentId;
bookmark.type = aItemType;
bookmark.dateAdded = aDateAdded;
if (aLastModified)
bookmark.lastModified = aLastModified;
else
bookmark.lastModified = aDateAdded;
if (aURI) {
rv = aURI->GetSpec(bookmark.url);
NS_ENSURE_SUCCESS(rv, rv);
}
bookmark.parentGuid = aParentGuid;
bookmark.grandParentId = aGrandParentId;
bookmark.syncStatus = syncStatus;
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::InsertBookmark(int64_t aFolder, nsIURI* aURI, int32_t aIndex,
const nsACString& aTitle,
const nsACString& aGUID, uint16_t aSource,
int64_t* aNewBookmarkId) {
NS_ENSURE_ARG(aURI);
NS_ENSURE_ARG_POINTER(aNewBookmarkId);
NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
if (!aGUID.IsEmpty() && !IsValidGUID(aGUID)) return NS_ERROR_INVALID_ARG;
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
int64_t placeId;
nsAutoCString placeGuid;
nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
NS_ENSURE_SUCCESS(rv, rv);
// Get the correct index for insertion. This also ensures the parent exists.
int32_t index, folderCount;
int64_t grandParentId;
nsAutoCString folderGuid;
rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
NS_ENSURE_SUCCESS(rv, rv);
if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
aIndex >= folderCount) {
index = folderCount;
} else {
index = aIndex;
// Create space for the insertion.
rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
NS_ENSURE_SUCCESS(rv, rv);
}
*aNewBookmarkId = -1;
PRTime dateAdded = RoundedPRNow();
nsAutoCString guid(aGUID);
nsCString title;
TruncateTitle(aTitle, title);
rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
0, folderGuid, grandParentId, aURI, aSource,
aNewBookmarkId, guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
if (!mCanNotify) {
return NS_OK;
}
Sequence<OwningNonNull<PlacesEvent>> notifications;
nsAutoCString utf8spec;
aURI->GetSpec(utf8spec);
int64_t tagsRootId = mDB->GetTagsFolderId();
RefPtr<PlacesBookmarkAddition> bookmark = new PlacesBookmarkAddition();
bookmark->mItemType = TYPE_BOOKMARK;
bookmark->mId = *aNewBookmarkId;
bookmark->mParentId = aFolder;
bookmark->mIndex = index;
bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
bookmark->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
bookmark->mDateAdded = dateAdded / 1000;
bookmark->mGuid.Assign(guid);
bookmark->mParentGuid.Assign(folderGuid);
bookmark->mSource = aSource;
bookmark->mIsTagging = grandParentId == tagsRootId;
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT "
" h.frecency, "
" h.hidden, "
" h.visit_count, "
" h.last_visit_date, "
" (SELECT group_concat(p.title ORDER BY p.title) "
" FROM moz_bookmarks b "
" JOIN moz_bookmarks p ON p.id = b.parent "
" JOIN moz_bookmarks g ON g.id = p.parent "
" WHERE g.guid = " SQL_QUOTE(TAGS_ROOT_GUID)
" AND b.fk = h.id "
" ) AS tags, "
" t.guid, t.id, t.title "
"FROM moz_places h "
"LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url) "
"WHERE h.id = :id");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName("id"_ns, placeId);
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
if (NS_SUCCEEDED(stmt->ExecuteStep(&exists)) && exists) {
int32_t frecency;
rv = stmt->GetInt32(0, &frecency);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mFrecency = frecency;
int32_t hidden;
rv = stmt->GetInt32(1, &hidden);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mHidden = !!hidden;
int32_t visitCount;
rv = stmt->GetInt32(2, &visitCount);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mVisitCount = visitCount;
bool isLastVisitDateNull;
rv = stmt->GetIsNull(3, &isLastVisitDateNull);
NS_ENSURE_SUCCESS(rv, rv);
if (isLastVisitDateNull) {
bookmark->mLastVisitDate.SetNull();
} else {
int64_t lastVisitDate;
rv = stmt->GetInt64(3, &lastVisitDate);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mLastVisitDate = lastVisitDate;
}
nsString tags;
rv = stmt->GetString(4, tags);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mTags.Assign(tags);
bool isTargetFolderNull;
rv = stmt->GetIsNull(5, &isTargetFolderNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isTargetFolderNull) {
nsCString targetFolderGuid;
rv = stmt->GetUTF8String(5, targetFolderGuid);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mTargetFolderGuid.Assign(targetFolderGuid);
int64_t targetFolderItemId = -1;
rv = stmt->GetInt64(6, &targetFolderItemId);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mTargetFolderItemId = targetFolderItemId;
nsString targetFolderTitle;
rv = stmt->GetString(7, targetFolderTitle);
NS_ENSURE_SUCCESS(rv, rv);
bookmark->mTargetFolderTitle.Assign(targetFolderTitle);
} else {
bookmark->mTargetFolderGuid.SetIsVoid(true);
bookmark->mTargetFolderItemId = -1;
bookmark->mTargetFolderTitle.SetIsVoid(true);
}
} else {
MOZ_ASSERT(false);
bookmark->mTags.SetIsVoid(true);
bookmark->mFrecency = 0;
bookmark->mHidden = false;
bookmark->mVisitCount = 0;
bookmark->mLastVisitDate.SetNull();
bookmark->mTargetFolderGuid.SetIsVoid(true);
bookmark->mTargetFolderItemId = -1;
bookmark->mTargetFolderTitle.SetIsVoid(true);
}
bool success = !!notifications.AppendElement(bookmark.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
// 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 == tagsRootId) {
// Notify a tags change to all bookmarks for this URI.
nsTArray<BookmarkData> bookmarks;
rv = GetBookmarksForURI(aURI, bookmarks);
NS_ENSURE_SUCCESS(rv, rv);
nsTArray<nsString> tags;
rv = GetTags(aURI, tags);
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);
RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
tagsChanged->mId = bookmarks[i].id;
tagsChanged->mItemType = TYPE_BOOKMARK;
tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
tagsChanged->mGuid = bookmarks[i].guid;
tagsChanged->mParentGuid = bookmarks[i].parentGuid;
tagsChanged->mTags.Assign(tags);
tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
tagsChanged->mSource = aSource;
tagsChanged->mIsTagging = false;
success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
}
}
PlacesObservers::NotifyListeners(notifications);
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource) {
AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveItem", OTHER);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
// Check we're not trying to remove a root.
NS_ENSURE_ARG(bookmark.parentId > 0 && bookmark.grandParentId > 0);
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
if (bookmark.type == TYPE_FOLDER) {
// Remove all of the folder's children.
rv = RemoveFolderChildren(bookmark.id, aSource);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<mozIStorageStatement> stmt =
mDB->GetStatement("DELETE FROM moz_bookmarks WHERE id = :item_id");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName("item_id"_ns, bookmark.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Fix indices in the parent.
if (bookmark.position != DEFAULT_INDEX) {
rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
NS_ENSURE_SUCCESS(rv, rv);
}
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
// Add a tombstone for synced items.
if (syncChangeDelta) {
rv = InsertTombstone(bookmark);
NS_ENSURE_SUCCESS(rv, rv);
}
bookmark.lastModified = RoundedPRNow();
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.parentId,
bookmark.lastModified);
NS_ENSURE_SUCCESS(rv, rv);
// Mark all affected separators as changed
rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position,
syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = mDB->GetTagsFolderId();
if (bookmark.grandParentId == tagsRootId) {
// If we're removing a tag, increment the change counter for all bookmarks
// with the URI.
rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
if (bookmark.type == TYPE_BOOKMARK) {
// 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");
}
if (!mCanNotify) {
return NS_OK;
}
Sequence<OwningNonNull<PlacesEvent>> notifications;
RefPtr<PlacesBookmarkRemoved> bookmarkRef = new PlacesBookmarkRemoved();
bookmarkRef->mItemType = bookmark.type;
bookmarkRef->mId = bookmark.id;
bookmarkRef->mParentId = bookmark.parentId;
bookmarkRef->mIndex = bookmark.position;
if (bookmark.type == TYPE_BOOKMARK) {
bookmarkRef->mUrl.Assign(NS_ConvertUTF8toUTF16(bookmark.url));
}
bookmarkRef->mTitle.Assign(NS_ConvertUTF8toUTF16(bookmark.title));
bookmarkRef->mGuid.Assign(bookmark.guid);
bookmarkRef->mParentGuid.Assign(bookmark.parentGuid);
bookmarkRef->mSource = aSource;
bookmarkRef->mIsTagging =
bookmark.parentId == tagsRootId || bookmark.grandParentId == tagsRootId;
bookmarkRef->mIsDescendantRemoval = false;
bool success = !!notifications.AppendElement(bookmarkRef.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
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);
nsAutoCString utf8spec;
uri->GetSpec(utf8spec);
nsTArray<nsString> tags;
rv = GetTags(uri, tags);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
tagsChanged->mId = bookmarks[i].id;
tagsChanged->mItemType = TYPE_BOOKMARK;
tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
tagsChanged->mGuid = bookmarks[i].guid;
tagsChanged->mParentGuid = bookmarks[i].parentGuid;
tagsChanged->mTags.Assign(tags);
tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
tagsChanged->mSource = aSource;
tagsChanged->mIsTagging = false;
success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
}
}
PlacesObservers::NotifyListeners(notifications);
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aTitle,
int32_t aIndex, const nsACString& aGUID,
uint16_t aSource, int64_t* aNewFolderId) {
// NOTE: aParent can be null for root creation, so not checked
NS_ENSURE_ARG_POINTER(aNewFolderId);
NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
if (!aGUID.IsEmpty() && !IsValidGUID(aGUID)) return NS_ERROR_INVALID_ARG;
// Get the correct index for insertion. This also ensures the parent exists.
int32_t index = aIndex, folderCount;
int64_t grandParentId;
nsAutoCString folderGuid;
nsresult rv =
FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
NS_ENSURE_SUCCESS(rv, rv);
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
aIndex >= folderCount) {
index = folderCount;
} else {
// Create space for the insertion.
rv = AdjustIndices(aParent, index, INT32_MAX, 1);
NS_ENSURE_SUCCESS(rv, rv);
}
*aNewFolderId = -1;
PRTime dateAdded = RoundedPRNow();
nsAutoCString guid(aGUID);
nsCString title;
TruncateTitle(aTitle, title);
rv = InsertBookmarkInDB(-1, FOLDER, aParent, index, title, dateAdded, 0,
folderGuid, grandParentId, nullptr, aSource,
aNewFolderId, guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = mDB->GetTagsFolderId();
if (mCanNotify) {
Sequence<OwningNonNull<PlacesEvent>> events;
RefPtr<PlacesBookmarkAddition> folder = new PlacesBookmarkAddition();
folder->mItemType = TYPE_FOLDER;
folder->mId = *aNewFolderId;
folder->mParentId = aParent;
folder->mIndex = index;
folder->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
folder->mDateAdded = dateAdded / 1000;
folder->mGuid.Assign(guid);
folder->mParentGuid.Assign(folderGuid);
folder->mSource = aSource;
folder->mIsTagging = aParent == tagsRootId;
folder->mTags.SetIsVoid(true);
folder->mFrecency = 0;
folder->mHidden = false;
folder->mVisitCount = 0;
folder->mLastVisitDate.SetNull();
folder->mTargetFolderGuid.SetIsVoid(true);
folder->mTargetFolderItemId = -1;
folder->mTargetFolderTitle.SetIsVoid(true);
bool success = !!events.AppendElement(folder.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
PlacesObservers::NotifyListeners(events);
}
return NS_OK;
}
nsresult nsNavBookmarks::GetDescendantChildren(
int64_t aFolderId, const nsACString& aFolderGuid, int64_t aGrandParentId,
nsTArray<BookmarkData>& aFolderChildrenArray) {
// New children will be added from this index on.
uint32_t startIndex = aFolderChildrenArray.Length();
nsresult rv;
{
// Collect children informations.
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match
// kGetInfoIndex_* order, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
"h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
"b.guid, b.position, b.type, b.fk, b.syncStatus "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
"WHERE b.parent = :parent "
"ORDER BY b.position ASC");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
BookmarkData child;
rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
NS_ENSURE_SUCCESS(rv, rv);
child.parentId = aFolderId;
child.grandParentId = aGrandParentId;
child.parentGuid = aFolderGuid;
rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt32(kGetChildrenIndex_SyncStatus, &child.syncStatus);
NS_ENSURE_SUCCESS(rv, rv);
if (child.type == TYPE_BOOKMARK) {
rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
NS_ENSURE_SUCCESS(rv, rv);
}
bool isNull;
rv = stmt->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv =
stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, child.title);
NS_ENSURE_SUCCESS(rv, rv);
}
// Append item to children's array.
aFolderChildrenArray.AppendElement(child);
}
}
// Recursively call GetDescendantChildren for added folders.
// We start at startIndex since previous folders are checked
// by previous calls to this method.
uint32_t childCount = aFolderChildrenArray.Length();
for (uint32_t i = startIndex; i < childCount; ++i) {
if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
// nsTarray assumes that all children can be memmove()d, thus we can't
// just pass aFolderChildrenArray[i].guid to a method that will change
// the array itself. Otherwise, since it's passed by reference, after a
// memmove() it could point to garbage and cause intermittent crashes.
nsCString guid = aFolderChildrenArray[i].guid;
GetDescendantChildren(aFolderChildrenArray[i].id, guid, aFolderId,
aFolderChildrenArray);
}
}
return NS_OK;
}
nsresult nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId,
uint16_t aSource) {
AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveFolderChilder", OTHER);
NS_ENSURE_ARG_MIN(aFolderId, 1);
BookmarkData folder;
nsresult rv = FetchItemInfo(aFolderId, folder);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
NS_ENSURE_ARG(folder.parentId != 0);
// Fill folder children array recursively.
nsTArray<BookmarkData> folderChildrenArray;
rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
folderChildrenArray);
NS_ENSURE_SUCCESS(rv, rv);
// Build a string of folders whose children will be removed.
nsCString foldersToRemove;
for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
BookmarkData& child = folderChildrenArray[i];
if (child.type == TYPE_FOLDER) {
foldersToRemove.Append(',');
foldersToRemove.AppendInt(child.id);
}
}
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
// Delete items from the database now.
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
nsCOMPtr<mozIStorageStatement> deleteStatement =
mDB->GetStatement(nsLiteralCString("DELETE FROM moz_bookmarks "
"WHERE parent IN (:parent") +
foldersToRemove + ")"_ns);
NS_ENSURE_STATE(deleteStatement);
mozStorageStatementScoper deleteStatementScoper(deleteStatement);
rv = deleteStatement->BindInt64ByName("parent"_ns, folder.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteStatement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Clean up orphan items annotations.
nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
if (!conn) {
return NS_ERROR_UNEXPECTED;
}
rv = conn->ExecuteSimpleSQL(
nsLiteralCString("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 = mDB->GetTagsFolderId();
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 == 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);
}
}
rv = InsertTombstones(tombstones);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
Sequence<OwningNonNull<PlacesEvent>> notifications;
// 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) {
// 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");
}
if (!mCanNotify) {
return NS_OK;
}
RefPtr<PlacesBookmarkRemoved> bookmark = new PlacesBookmarkRemoved();
bookmark->mItemType = TYPE_BOOKMARK;
bookmark->mId = child.id;
bookmark->mParentId = child.parentId;
bookmark->mIndex = child.position;
bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(child.url));
bookmark->mTitle.Assign(NS_ConvertUTF8toUTF16(child.title));
bookmark->mGuid.Assign(child.guid);
bookmark->mParentGuid.Assign(child.parentGuid);
bookmark->mSource = aSource;
bookmark->mIsTagging = (child.grandParentId == tagsRootId);
bookmark->mIsDescendantRemoval = (child.grandParentId != tagsRootId);
bool success = !!notifications.AppendElement(bookmark.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
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);
nsAutoCString utf8spec;
uri->GetSpec(utf8spec);
nsTArray<nsString> tags;
rv = GetTags(uri, tags);
NS_ENSURE_SUCCESS(rv, rv);
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
tagsChanged->mId = bookmarks[i].id;
tagsChanged->mItemType = TYPE_BOOKMARK;
tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
tagsChanged->mGuid = bookmarks[i].guid;
tagsChanged->mParentGuid = bookmarks[i].parentGuid;
tagsChanged->mTags.Assign(tags);
tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
tagsChanged->mSource = aSource;
tagsChanged->mIsTagging = false;
success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
}
}
}
if (notifications.Length()) {
PlacesObservers::NotifyListeners(notifications);
}
return NS_OK;
}
nsresult nsNavBookmarks::FetchItemInfo(int64_t aItemId,
BookmarkData& _bookmark) {
// LEFT JOIN since not all bookmarks have an associated place.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
"b.dateAdded, b.lastModified, b.guid, t.guid, t.parent, "
"b.syncStatus "
"FROM moz_bookmarks b "
"LEFT JOIN moz_bookmarks t ON t.id = b.parent "
"LEFT JOIN moz_places h ON h.id = b.fk "
"WHERE b.id = :item_id");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName("item_id"_ns, aItemId);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasResult) {
return NS_ERROR_INVALID_ARG;
}
_bookmark.id = aItemId;
rv = stmt->GetUTF8String(1, _bookmark.url);
NS_ENSURE_SUCCESS(rv, rv);
bool isNull;
rv = stmt->GetIsNull(2, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = stmt->GetUTF8String(2, _bookmark.title);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->GetInt32(3, &_bookmark.position);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(4, &_bookmark.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(5, &_bookmark.parentId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt32(6, &_bookmark.type);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(9, _bookmark.guid);
NS_ENSURE_SUCCESS(rv, rv);
// Getting properties of the root would show no parent.
rv = stmt->GetIsNull(10, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(11, &_bookmark.grandParentId);
NS_ENSURE_SUCCESS(rv, rv);
} else {
_bookmark.grandParentId = -1;
}
rv = stmt->GetInt32(12, &_bookmark.syncStatus);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavBookmarks::FetchItemInfo(const nsCString& aGUID,
BookmarkData& _bookmark) {
// LEFT JOIN since not all bookmarks have an associated place.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
"b.dateAdded, b.lastModified, t.guid, t.parent, "
"b.syncStatus "
"FROM moz_bookmarks b "
"LEFT JOIN moz_bookmarks t ON t.id = b.parent "
"LEFT JOIN moz_places h ON h.id = b.fk "
"WHERE b.guid = :item_guid");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindUTF8StringByName("item_guid"_ns, aGUID);
NS_ENSURE_SUCCESS(rv, rv);
_bookmark.guid = aGUID;
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
if (!hasResult) {
return NS_ERROR_INVALID_ARG;
}
rv = stmt->GetInt64(0, &_bookmark.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(1, _bookmark.url);
NS_ENSURE_SUCCESS(rv, rv);
bool isNull;
rv = stmt->GetIsNull(2, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = stmt->GetUTF8String(2, _bookmark.title);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->GetInt32(3, &_bookmark.position);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(4, &_bookmark.placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(5, &_bookmark.parentId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt32(6, &_bookmark.type);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
NS_ENSURE_SUCCESS(rv, rv);
// Getting properties of the root would show no parent.
rv = stmt->GetIsNull(9, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = stmt->GetUTF8String(9, _bookmark.parentGuid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(10, &_bookmark.grandParentId);
NS_ENSURE_SUCCESS(rv, rv);
} else {
_bookmark.grandParentId = -1;
}
rv = stmt->GetInt32(11, &_bookmark.syncStatus);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
int64_t aSyncChangeDelta,
int64_t aItemId, PRTime aValue) {
aValue = RoundToMilliseconds(aValue);
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"UPDATE moz_bookmarks SET lastModified = :date, "
"syncChangeCounter = syncChangeCounter + :delta "
"WHERE id = :item_id");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName("date"_ns, aValue);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("item_id"_ns, aItemId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("delta"_ns, aSyncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// note, we are not notifying the observers
// that the item has changed.
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
uint16_t aSource) {
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = mDB->GetTagsFolderId();
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
// non-tags.
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
bookmark.lastModified);
NS_ENSURE_SUCCESS(rv, rv);
rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
bookmark.lastModified);
NS_ENSURE_SUCCESS(rv, rv);
}
// Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
if (mCanNotify) {
Sequence<OwningNonNull<PlacesEvent>> events;
RefPtr<PlacesBookmarkTime> timeChanged = new PlacesBookmarkTime();
timeChanged->mId = bookmark.id;
timeChanged->mItemType = bookmark.type;
timeChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(bookmark.url));
timeChanged->mGuid = bookmark.guid;
timeChanged->mParentGuid = bookmark.parentGuid;
timeChanged->mDateAdded = bookmark.dateAdded / 1000;
timeChanged->mLastModified = bookmark.lastModified / 1000;
timeChanged->mSource = aSource;
timeChanged->mIsTagging =
bookmark.parentId == tagsRootId || bookmark.grandParentId == tagsRootId;
bool success = !!events.AppendElement(timeChanged.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
PlacesObservers::NotifyListeners(events);
}
return NS_OK;
}
nsresult nsNavBookmarks::AddSyncChangesForBookmarksWithURL(
const nsACString& aURL, int64_t aSyncChangeDelta) {
if (!aSyncChangeDelta) {
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
if (NS_WARN_IF(NS_FAILED(rv))) {
// Ignore sync changes for invalid URLs.
return NS_OK;
}
return AddSyncChangesForBookmarksWithURI(uri, aSyncChangeDelta);
}
nsresult nsNavBookmarks::AddSyncChangesForBookmarksWithURI(
nsIURI* aURI, int64_t aSyncChangeDelta) {
if (NS_WARN_IF(!aURI) || !aSyncChangeDelta) {
// Ignore sync changes for invalid URIs.
return NS_OK;
}
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
"UPDATE moz_bookmarks SET "
"syncChangeCounter = syncChangeCounter + :delta "
"WHERE type = :type AND "
"fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND "
"url = :url)");
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv = statement->BindInt64ByName("delta"_ns, aSyncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt64ByName("type"_ns,
nsINavBookmarksService::TYPE_BOOKMARK);
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(statement, "url"_ns, aURI);
NS_ENSURE_SUCCESS(rv, rv);
return statement->Execute();
}
nsresult nsNavBookmarks::AddSyncChangesForBookmarksInFolder(
int64_t aFolderId, int64_t aSyncChangeDelta) {
if (!aSyncChangeDelta) {
return NS_OK;
}
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
"UPDATE moz_bookmarks SET "
"syncChangeCounter = syncChangeCounter + :delta "
"WHERE type = :type AND "
"fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)");
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv = statement->BindInt64ByName("delta"_ns, aSyncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt64ByName("type"_ns,
nsINavBookmarksService::TYPE_BOOKMARK);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavBookmarks::InsertTombstone(const BookmarkData& aBookmark) {
if (!NeedsTombstone(aBookmark)) {
return NS_OK;
}
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"INSERT INTO moz_bookmarks_deleted (guid, dateRemoved) "
"VALUES (:guid, :date_removed)");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindUTF8StringByName("guid"_ns, aBookmark.guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByName("date_removed"_ns, RoundedPRNow());
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavBookmarks::InsertTombstones(
const nsTArray<TombstoneData>& aTombstones) {
if (aTombstones.IsEmpty()) {
return NS_OK;
}
nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
NS_ENSURE_STATE(conn);
int32_t variableLimit = 0;
nsresult rv = conn->GetVariableLimit(&variableLimit);
NS_ENSURE_SUCCESS(rv, rv);
size_t maxRowsPerChunk = variableLimit / 2;
for (uint32_t startIndex = 0; startIndex < aTombstones.Length();
startIndex += maxRowsPerChunk) {
size_t rowsPerChunk =
std::min(maxRowsPerChunk, aTombstones.Length() - startIndex);
// Build a query to insert all tombstones in a single statement, chunking to
// avoid the SQLite bound parameter limit.
nsAutoCString tombstonesToInsert;
tombstonesToInsert.AppendLiteral("VALUES (?, ?)");
for (uint32_t i = 1; i < rowsPerChunk; ++i) {
tombstonesToInsert.AppendLiteral(", (?, ?)");
}
#ifdef DEBUG
MOZ_ASSERT(tombstonesToInsert.CountChar('?') == rowsPerChunk * 2,
"Expected one binding param per column for each tombstone");
#endif
nsCOMPtr<mozIStorageStatement> stmt =
mDB->GetStatement(nsLiteralCString("INSERT INTO moz_bookmarks_deleted "
"(guid, dateRemoved) ") +
tombstonesToInsert);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
uint32_t paramIndex = 0;
for (uint32_t i = 0; i < rowsPerChunk; ++i) {
const TombstoneData& tombstone = aTombstones[startIndex + i];
rv = stmt->BindUTF8StringByIndex(paramIndex++, tombstone.guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt64ByIndex(paramIndex++, tombstone.dateRemoved);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult nsNavBookmarks::RemoveTombstone(const nsACString& aGUID) {
nsCOMPtr<mozIStorageStatement> stmt =
mDB->GetStatement("DELETE FROM moz_bookmarks_deleted WHERE guid = :guid");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindUTF8StringByName("guid"_ns, aGUID);
NS_ENSURE_SUCCESS(rv, rv);
return stmt->Execute();
}
NS_IMETHODIMP
nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
uint16_t aSource) {
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = mDB->GetTagsFolderId();
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
// transaction for non-tags.
mozStorageTransaction transaction(mDB->MainConn(), false);
// XXX Handle the error, bug 1696133.
Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mCanNotify) {
Sequence<OwningNonNull<PlacesEvent>> events;
RefPtr<PlacesBookmarkTitle> titleChanged = new PlacesBookmarkTitle();
titleChanged->mId = bookmark.id;
titleChanged->mItemType = bookmark.type;
titleChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(bookmark.url));
titleChanged->mGuid = bookmark.guid;
titleChanged->mParentGuid = bookmark.parentGuid;
titleChanged->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
titleChanged->mLastModified = bookmark.lastModified / 1000;
titleChanged->mSource = aSource;
titleChanged->mIsTagging =
bookmark.parentId == tagsRootId || bookmark.grandParentId == tagsRootId;
bool success = !!events.AppendElement(titleChanged.forget(), fallible);
MOZ_RELEASE_ASSERT(success);
PlacesObservers::NotifyListeners(events);
}
return NS_OK;
}
nsresult nsNavBookmarks::SetItemTitleInternal(BookmarkData& aBookmark,
const nsACString& aTitle,
int64_t aSyncChangeDelta) {
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
"UPDATE moz_bookmarks SET "
"title = :item_title, lastModified = :date, "
"syncChangeCounter = syncChangeCounter + :delta "
"WHERE id = :item_id");
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv;
if (aTitle.IsEmpty()) {
rv = statement->BindNullByName("item_title"_ns);
} else {
rv = statement->BindUTF8StringByName("item_title"_ns, aTitle);
}
NS_ENSURE_SUCCESS(rv, rv);
aBookmark.lastModified = RoundToMilliseconds(RoundedPRNow());
rv = statement->BindInt64ByName("date"_ns, aBookmark.lastModified);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt64ByName("item_id"_ns, aBookmark.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->BindInt64ByName("delta"_ns, aSyncChangeDelta);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsNavBookmarks::GetItemTitle(int64_t aItemId, nsACString& _title) {
NS_ENSURE_ARG_MIN(aItemId, 1);
BookmarkData bookmark;
nsresult rv = FetchItemInfo(aItemId, bookmark);
NS_ENSURE_SUCCESS(rv, rv);
_title = bookmark.title;
return NS_OK;
}
nsresult nsNavBookmarks::QueryFolderChildren(
int64_t aFolderId, nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aChildren) {
NS_ENSURE_ARG_POINTER(aOptions);
NS_ENSURE_ARG_POINTER(aChildren);
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match those returned
// by mDBGetURLPageInfo, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
nsNavHistory::GetTagsSqlFragment(
nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS,
aOptions->ExcludeItems()) +
"SELECT "
" h.id, h.url, b.title, h.rev_host, h.visit_count, "
" h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, b.parent, "
" (SELECT tags FROM tagged WHERE place_id = h.id) AS tags, "
" h.frecency, h.hidden, h.guid, null, null, null, "
" b.guid, b.position, b.type, b.fk, t.guid, t.id, t.title "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
"LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url) "
"WHERE b.parent = :parent "
"AND (NOT :excludeItems OR "
"b.type = :folder OR "
"h.url_hash BETWEEN hash('place', 'prefix_lo') "
" AND hash('place', 'prefix_hi')) "
"ORDER BY b.position ASC"_ns);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("folder"_ns, TYPE_FOLDER);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("excludeItems"_ns, aOptions->ExcludeItems());
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
NS_ENSURE_SUCCESS(rv, rv);
int32_t index = -1;
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult nsNavBookmarks::ProcessFolderNodeRow(
mozIStorageValueArray* aRow, nsNavHistoryQueryOptions* aOptions,
nsCOMArray<nsNavHistoryResultNode>* aChildren, int32_t& aCurrentIndex) {
NS_ENSURE_ARG_POINTER(aRow);
NS_ENSURE_ARG_POINTER(aOptions);
NS_ENSURE_ARG_POINTER(aChildren);
// The results will be in order of aCurrentIndex. Even if we don't add a node
// because it was excluded, we need to count its index, so do that before
// doing anything else.
aCurrentIndex++;
int32_t itemType;
nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
NS_ENSURE_SUCCESS(rv, rv);
int64_t id;
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<nsNavHistoryResultNode> node;
if (itemType == TYPE_BOOKMARK) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t nodeType;
node->GetType(&nodeType);
if (nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
aOptions->ExcludeQueries()) {
return NS_OK;
}
} else if (itemType == TYPE_FOLDER) {
nsAutoCString title;
bool isNull;
rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
NS_ENSURE_SUCCESS(rv, rv);
}
nsAutoCString guid;
rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, guid);
NS_ENSURE_SUCCESS(rv, rv);
// Don't use options from the parent to build the new folder node, it will
// inherit those later when it's inserted in the result.
node = new nsNavHistoryFolderResultNode(id, guid, id, guid, title,
new nsNavHistoryQueryOptions());
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
reinterpret_cast<int64_t*>(&node->mDateAdded));
NS_ENSURE_SUCCESS(rv, rv);
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
reinterpret_cast<int64_t*>(&node->mLastModified));
NS_ENSURE_SUCCESS(rv, rv);
} else {
// This is a separator.
node = new nsNavHistorySeparatorResultNode();
node->mItemId = id;
rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
NS_ENSURE_SUCCESS(rv, rv);
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
reinterpret_cast<int64_t*>(&node->mDateAdded));
NS_ENSURE_SUCCESS(rv, rv);
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
reinterpret_cast<int64_t*>(&node->mLastModified));
NS_ENSURE_SUCCESS(rv, rv);
}
// Store the index of the node within this container. Note that this is not
// moz_bookmarks.position.
node->mBookmarkIndex = aCurrentIndex;
NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
nsresult nsNavBookmarks::QueryFolderChildrenAsync(
nsNavHistoryFolderResultNode* aNode,
mozIStoragePendingStatement** _pendingStmt) {
NS_ENSURE_ARG_POINTER(aNode);
NS_ENSURE_ARG_POINTER(_pendingStmt);
// Select all children of a given folder, sorted by position.
// This is a LEFT JOIN because not all bookmarks types have a place.
// We construct a result where the first columns exactly match those returned
// by mDBGetURLPageInfo, and additionally contains columns for position,
// item_child, and folder_child from moz_bookmarks.
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
"SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
"h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
"b.guid, b.position, b.type, b.fk, t.guid, t.id, t.title "
"FROM moz_bookmarks b "
"LEFT JOIN moz_places h ON b.fk = h.id "
"LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url) "
"WHERE b.parent = :parent "
"AND (NOT :excludeItems OR "
"b.type = :folder OR "
"h.url_hash BETWEEN hash('place', 'prefix_lo') AND hash('place', "
"'prefix_hi')) "
"ORDER BY b.position ASC");
NS_ENSURE_STATE(stmt);
nsresult rv = stmt->BindInt64ByName("parent"_ns, aNode->mTargetFolderItemId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName("folder"_ns, TYPE_FOLDER);
NS_ENSURE_SUCCESS(rv, rv);
rv =
stmt->BindInt32ByName("excludeItems"_ns, aNode->mOptions->ExcludeItems());
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
NS_ENSURE_SUCCESS(rv, rv);
NS_IF_ADDREF(*_pendingStmt = pendingStmt);
return NS_OK;
}
nsresult nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
int32_t* _folderCount,
nsACString& _guid,
int64_t* _parentId) {
*_folderCount = 0;
*_parentId = -1;
// This query has to always return results, so it can't be written as a join,
// though a left join of 2 subqueries would have the same cost.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT count(*), "
"(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
"(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
"FROM moz_bookmarks "
"WHERE parent = :parent");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindInt64ByName("parent"_ns, aFolderId);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
// Ensure that the folder we are looking for exists.
// Can't rely only on parent, since the root has parent 0, that doesn't exist.
bool isNull;
rv = stmt->GetIsNull(2, &isNull);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
NS_ERROR_INVALID_ARG);
rv = stmt->GetInt32(0, _folderCount);
NS_ENSURE_SUCCESS(rv, rv);
if (!isNull) {
rv = stmt->GetUTF8String(1, _guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(2, _parentId);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult nsNavBookmarks::GetBookmarksForURI(
nsIURI* aURI, nsTArray<BookmarkData>& aBookmarks) {
NS_ENSURE_ARG(aURI);
// Double ordering covers possible lastModified ties, that could happen when
// importing, syncing or due to extensions.
// Note: not using a JOIN is cheaper in this case.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent, "
"b.syncStatus "
"FROM moz_bookmarks b "
"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) "
"ORDER BY b.lastModified DESC, b.id DESC ");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, "page_url"_ns, aURI);
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = mDB->GetTagsFolderId();
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 == tagsRootId) {
continue;
}
BookmarkData bookmark;
bookmark.grandParentId = grandParentId;
rv = stmt->GetInt64(0, &bookmark.id);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(1, bookmark.guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(2, &bookmark.parentId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt64(3, reinterpret_cast<int64_t*>(&bookmark.lastModified));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(4, bookmark.parentGuid);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetInt32(6, &bookmark.syncStatus);
NS_ENSURE_SUCCESS(rv, rv);
aBookmarks.AppendElement(bookmark);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// nsIObserver
NS_IMETHODIMP
nsNavBookmarks::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
// Don't even try to notify observers from this point on, the category
// cache would init services that could try to use our APIs.
mCanNotify = false;
}
return NS_OK;
}