Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/toolkit/components/places/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 61 kB image not shown  

Quelle  nsNavBookmarks.cpp   Sprache: C

 
/* -*- 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;
}

Messung V0.5
C=93 H=91 G=91

¤ Dauer der Verarbeitung: 0.15 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.