toolkit/components/places/nsNavBookmarks.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nsNavBookmarks.h"
     8 #include "nsNavHistory.h"
     9 #include "nsAnnotationService.h"
    10 #include "nsPlacesMacros.h"
    11 #include "Helpers.h"
    13 #include "nsAppDirectoryServiceDefs.h"
    14 #include "nsNetUtil.h"
    15 #include "nsUnicharUtils.h"
    16 #include "nsPrintfCString.h"
    17 #include "prprf.h"
    18 #include "mozilla/storage.h"
    20 #include "GeckoProfiler.h"
    22 #define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_SIZE 64
    23 #define RECENT_BOOKMARKS_INITIAL_CACHE_SIZE 10
    24 // Threashold to expire old bookmarks if the initial cache size is exceeded.
    25 #define RECENT_BOOKMARKS_THRESHOLD PRTime((int64_t)1 * 60 * PR_USEC_PER_SEC)
    27 #define BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(_itemId_) \
    28   mUncachableBookmarks.PutEntry(_itemId_); \
    29   mRecentBookmarksCache.RemoveEntry(_itemId_)
    31 #define END_CRITICAL_BOOKMARK_CACHE_SECTION(_itemId_) \
    32   MOZ_ASSERT(!mRecentBookmarksCache.GetEntry(_itemId_)); \
    33   MOZ_ASSERT(mUncachableBookmarks.GetEntry(_itemId_)); \
    34   mUncachableBookmarks.RemoveEntry(_itemId_)
    36 #define ADD_TO_BOOKMARK_CACHE(_itemId_, _data_) \
    37   PR_BEGIN_MACRO \
    38   ExpireNonrecentBookmarks(&mRecentBookmarksCache); \
    39   if (!mUncachableBookmarks.GetEntry(_itemId_)) { \
    40     BookmarkKeyClass* key = mRecentBookmarksCache.PutEntry(_itemId_); \
    41     if (key) { \
    42       key->bookmark = _data_; \
    43     } \
    44   } \
    45   PR_END_MACRO
    47 #define TOPIC_PLACES_MAINTENANCE "places-maintenance-finished"
    49 using namespace mozilla;
    51 // These columns sit to the right of the kGetInfoIndex_* columns.
    52 const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 15;
    53 const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 16;
    54 const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 17;
    55 const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 18;
    57 using namespace mozilla::places;
    59 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
    61 #define BOOKMARKS_ANNO_PREFIX "bookmarks/"
    62 #define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
    63 #define READ_ONLY_ANNO NS_LITERAL_CSTRING("placesInternal/READ_ONLY")
    66 namespace {
    68 struct keywordSearchData
    69 {
    70   int64_t itemId;
    71   nsString keyword;
    72 };
    74 PLDHashOperator
    75 SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey,
    76                          const nsString aValue,
    77                          void* aUserArg)
    78 {
    79   keywordSearchData* data = reinterpret_cast<keywordSearchData*>(aUserArg);
    80   if (data->keyword.Equals(aValue)) {
    81     data->itemId = aKey;
    82     return PL_DHASH_STOP;
    83   }
    84   return PL_DHASH_NEXT;
    85 }
    87 template<typename Method, typename DataType>
    88 class AsyncGetBookmarksForURI : public AsyncStatementCallback
    89 {
    90 public:
    91   AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
    92                           Method aCallback,
    93                           const DataType& aData)
    94   : mBookmarksSvc(aBookmarksSvc)
    95   , mCallback(aCallback)
    96   , mData(aData)
    97   {
    98   }
   100   void Init()
   101   {
   102     nsRefPtr<Database> DB = Database::GetDatabase();
   103     if (DB) {
   104       nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
   105         "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
   106         "FROM moz_bookmarks b "
   107         "JOIN moz_bookmarks t on t.id = b.parent "
   108         "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
   109         "ORDER BY b.lastModified DESC, b.id DESC "
   110       );
   111       if (stmt) {
   112         (void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
   113                               mData.bookmark.url);
   114         nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
   115         (void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
   116       }
   117     }
   118   }
   120   NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet)
   121   {
   122     nsCOMPtr<mozIStorageRow> row;
   123     while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
   124       // Skip tags, for the use-cases of this async getter they are useless.
   125       int64_t grandParentId, tagsFolderId;
   126       nsresult rv = row->GetInt64(5, &grandParentId);
   127       NS_ENSURE_SUCCESS(rv, rv);
   128       rv = mBookmarksSvc->GetTagsFolder(&tagsFolderId);
   129       NS_ENSURE_SUCCESS(rv, rv);
   130       if (grandParentId == tagsFolderId) {
   131         continue;
   132       }
   134       mData.bookmark.grandParentId = grandParentId;
   135       rv = row->GetInt64(0, &mData.bookmark.id);
   136       NS_ENSURE_SUCCESS(rv, rv);
   137       rv = row->GetUTF8String(1, mData.bookmark.guid);
   138       NS_ENSURE_SUCCESS(rv, rv);
   139       rv = row->GetInt64(2, &mData.bookmark.parentId);
   140       NS_ENSURE_SUCCESS(rv, rv);
   141       // lastModified (3) should not be set for the use-cases of this getter.
   142       rv = row->GetUTF8String(4, mData.bookmark.parentGuid);
   143       NS_ENSURE_SUCCESS(rv, rv);
   145       if (mCallback) {
   146         ((*mBookmarksSvc).*mCallback)(mData);
   147       }
   148     }
   149     return NS_OK;
   150   }
   152 private:
   153   nsRefPtr<nsNavBookmarks> mBookmarksSvc;
   154   Method mCallback;
   155   DataType mData;
   156 };
   158 static PLDHashOperator
   159 ExpireNonrecentBookmarksCallback(BookmarkKeyClass* aKey,
   160                                  void* userArg)
   161 {
   162   int64_t* threshold = reinterpret_cast<int64_t*>(userArg);
   163   if (aKey->creationTime < *threshold) {
   164     return PL_DHASH_REMOVE;
   165   }
   166   return PL_DHASH_NEXT;
   167 }
   169 static void
   170 ExpireNonrecentBookmarks(nsTHashtable<BookmarkKeyClass>* hashTable)
   171 {
   172   if (hashTable->Count() > RECENT_BOOKMARKS_INITIAL_CACHE_SIZE) {
   173     int64_t threshold = PR_Now() - RECENT_BOOKMARKS_THRESHOLD;
   174     (void)hashTable->EnumerateEntries(ExpireNonrecentBookmarksCallback,
   175                                       reinterpret_cast<void*>(&threshold));
   176   }
   177 }
   179 static PLDHashOperator
   180 ExpireRecentBookmarksByParentCallback(BookmarkKeyClass* aKey,
   181                                       void* userArg)
   182 {
   183   int64_t* parentId = reinterpret_cast<int64_t*>(userArg);
   184   if (aKey->bookmark.parentId == *parentId) {
   185     return PL_DHASH_REMOVE;
   186   }
   187   return PL_DHASH_NEXT;
   188 }
   190 static void
   191 ExpireRecentBookmarksByParent(nsTHashtable<BookmarkKeyClass>* hashTable,
   192                               int64_t aParentId)
   193 {
   194   (void)hashTable->EnumerateEntries(ExpireRecentBookmarksByParentCallback,
   195                                     reinterpret_cast<void*>(&aParentId));
   196 }
   198 } // Anonymous namespace.
   201 nsNavBookmarks::nsNavBookmarks()
   202   : mItemCount(0)
   203   , mRoot(0)
   204   , mMenuRoot(0)
   205   , mTagsRoot(0)
   206   , mUnfiledRoot(0)
   207   , mToolbarRoot(0)
   208   , mCanNotify(false)
   209   , mCacheObservers("bookmark-observers")
   210   , mBatching(false)
   211   , mBookmarkToKeywordHash(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_SIZE)
   212   , mBookmarkToKeywordHashInitialized(false)
   213   , mRecentBookmarksCache(RECENT_BOOKMARKS_INITIAL_CACHE_SIZE)
   214   , mUncachableBookmarks(RECENT_BOOKMARKS_INITIAL_CACHE_SIZE)
   215 {
   216   NS_ASSERTION(!gBookmarksService,
   217                "Attempting to create two instances of the service!");
   218   gBookmarksService = this;
   219 }
   222 nsNavBookmarks::~nsNavBookmarks()
   223 {
   224   NS_ASSERTION(gBookmarksService == this,
   225                "Deleting a non-singleton instance of the service");
   226   if (gBookmarksService == this)
   227     gBookmarksService = nullptr;
   228 }
   231 NS_IMPL_ISUPPORTS(nsNavBookmarks
   232 , nsINavBookmarksService
   233 , nsINavHistoryObserver
   234 , nsIAnnotationObserver
   235 , nsIObserver
   236 , nsISupportsWeakReference
   237 )
   240 nsresult
   241 nsNavBookmarks::Init()
   242 {
   243   mDB = Database::GetDatabase();
   244   NS_ENSURE_STATE(mDB);
   246   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   247   if (os) {
   248     (void)os->AddObserver(this, TOPIC_PLACES_MAINTENANCE, true);
   249     (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
   250     (void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
   251   }
   253   nsresult rv = ReadRoots();
   254   NS_ENSURE_SUCCESS(rv, rv);
   256   mCanNotify = true;
   258   // Observe annotations.
   259   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
   260   NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
   261   annosvc->AddObserver(this);
   263   // Allows us to notify on title changes. MUST BE LAST so it is impossible
   264   // to fail after this call, or the history service will have a reference to
   265   // us and we won't go away.
   266   nsNavHistory* history = nsNavHistory::GetHistoryService();
   267   NS_ENSURE_STATE(history);
   268   history->AddObserver(this, true);
   270   // DO NOT PUT STUFF HERE that can fail. See observer comment above.
   272   return NS_OK;
   273 }
   275 nsresult
   276 nsNavBookmarks::ReadRoots()
   277 {
   278   nsCOMPtr<mozIStorageStatement> stmt;
   279   nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
   280     "SELECT root_name, folder_id FROM moz_bookmarks_roots"
   281   ), getter_AddRefs(stmt));
   282   NS_ENSURE_SUCCESS(rv, rv);
   284   bool hasResult;
   285   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
   286     nsAutoCString rootName;
   287     rv = stmt->GetUTF8String(0, rootName);
   288     NS_ENSURE_SUCCESS(rv, rv);
   289     int64_t rootId;
   290     rv = stmt->GetInt64(1, &rootId);
   291     NS_ENSURE_SUCCESS(rv, rv);
   292     NS_ABORT_IF_FALSE(rootId != 0, "Root id is 0, that is an invalid value.");
   294     if (rootName.EqualsLiteral("places")) {
   295       mRoot = rootId;
   296     }
   297     else if (rootName.EqualsLiteral("menu")) {
   298       mMenuRoot = rootId;
   299     }
   300     else if (rootName.EqualsLiteral("toolbar")) {
   301       mToolbarRoot = rootId;
   302     }
   303     else if (rootName.EqualsLiteral("tags")) {
   304       mTagsRoot = rootId;
   305     }
   306     else if (rootName.EqualsLiteral("unfiled")) {
   307       mUnfiledRoot = rootId;
   308     }
   309   }
   311   if (!mRoot || !mMenuRoot || !mToolbarRoot || !mTagsRoot || !mUnfiledRoot)
   312     return NS_ERROR_FAILURE;
   314   return NS_OK;
   315 }
   317 // nsNavBookmarks::IsBookmarkedInDatabase
   318 //
   319 //    This checks to see if the specified place_id is actually bookmarked.
   321 nsresult
   322 nsNavBookmarks::IsBookmarkedInDatabase(int64_t aPlaceId,
   323                                        bool* aIsBookmarked)
   324 {
   325   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   326     "SELECT 1 FROM moz_bookmarks WHERE fk = :page_id"
   327   );
   328   NS_ENSURE_STATE(stmt);
   329   mozStorageStatementScoper scoper(stmt);
   331   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
   332   NS_ENSURE_SUCCESS(rv, rv);
   333   rv = stmt->ExecuteStep(aIsBookmarked);
   334   NS_ENSURE_SUCCESS(rv, rv);
   335   return NS_OK;
   336 }
   339 nsresult
   340 nsNavBookmarks::AdjustIndices(int64_t aFolderId,
   341                               int32_t aStartIndex,
   342                               int32_t aEndIndex,
   343                               int32_t aDelta)
   344 {
   345   NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= INT32_MAX &&
   346                aStartIndex <= aEndIndex, "Bad indices");
   348   // Expire all cached items for this parent, since all positions are going to
   349   // change.
   350   ExpireRecentBookmarksByParent(&mRecentBookmarksCache, aFolderId);
   352   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   353     "UPDATE moz_bookmarks SET position = position + :delta "
   354       "WHERE parent = :parent "
   355         "AND position BETWEEN :from_index AND :to_index"
   356   );
   357   NS_ENSURE_STATE(stmt);
   358   mozStorageStatementScoper scoper(stmt);
   360   nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
   361   NS_ENSURE_SUCCESS(rv, rv);
   362   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
   363   NS_ENSURE_SUCCESS(rv, rv);
   364   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
   365   NS_ENSURE_SUCCESS(rv, rv);
   366   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
   367   NS_ENSURE_SUCCESS(rv, rv);
   369   rv = stmt->Execute();
   370   NS_ENSURE_SUCCESS(rv, rv);
   372   return NS_OK;
   373 }
   376 NS_IMETHODIMP
   377 nsNavBookmarks::GetPlacesRoot(int64_t* aRoot)
   378 {
   379   *aRoot = mRoot;
   380   return NS_OK;
   381 }
   384 NS_IMETHODIMP
   385 nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot)
   386 {
   387   *aRoot = mMenuRoot;
   388   return NS_OK;
   389 }
   392 NS_IMETHODIMP
   393 nsNavBookmarks::GetToolbarFolder(int64_t* aFolderId)
   394 {
   395   *aFolderId = mToolbarRoot;
   396   return NS_OK;
   397 }
   400 NS_IMETHODIMP
   401 nsNavBookmarks::GetTagsFolder(int64_t* aRoot)
   402 {
   403   *aRoot = mTagsRoot;
   404   return NS_OK;
   405 }
   408 NS_IMETHODIMP
   409 nsNavBookmarks::GetUnfiledBookmarksFolder(int64_t* aRoot)
   410 {
   411   *aRoot = mUnfiledRoot;
   412   return NS_OK;
   413 }
   416 nsresult
   417 nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
   418                                    enum ItemType aItemType,
   419                                    int64_t aParentId,
   420                                    int32_t aIndex,
   421                                    const nsACString& aTitle,
   422                                    PRTime aDateAdded,
   423                                    PRTime aLastModified,
   424                                    const nsACString& aParentGuid,
   425                                    int64_t aGrandParentId,
   426                                    nsIURI* aURI,
   427                                    int64_t* _itemId,
   428                                    nsACString& _guid)
   429 {
   430   // Check for a valid itemId.
   431   MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
   432   // Check for a valid placeId.
   433   MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
   435   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   436     "INSERT INTO moz_bookmarks "
   437       "(id, fk, type, parent, position, title, "
   438        "dateAdded, lastModified, guid) "
   439     "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
   440             ":item_title, :date_added, :last_modified, "
   441             "IFNULL(:item_guid, GENERATE_GUID()))"
   442   );
   443   NS_ENSURE_STATE(stmt);
   444   mozStorageStatementScoper scoper(stmt);
   446   nsresult rv;
   447   if (*_itemId != -1)
   448     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
   449   else
   450     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
   451   NS_ENSURE_SUCCESS(rv, rv);
   453   if (aPlaceId != -1)
   454     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
   455   else
   456     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
   457   NS_ENSURE_SUCCESS(rv, rv);
   459   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
   460   NS_ENSURE_SUCCESS(rv, rv);
   461   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
   462   NS_ENSURE_SUCCESS(rv, rv);
   463   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
   464   NS_ENSURE_SUCCESS(rv, rv);
   466   // Support NULL titles.
   467   if (aTitle.IsVoid())
   468     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
   469   else
   470     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
   471   NS_ENSURE_SUCCESS(rv, rv);
   473   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
   474   NS_ENSURE_SUCCESS(rv, rv);
   476   if (aLastModified) {
   477     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
   478                                aLastModified);
   479   }
   480   else {
   481     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
   482   }
   483   NS_ENSURE_SUCCESS(rv, rv);
   485   // Could use IsEmpty because our callers check for GUID validity,
   486   // but it doesn't hurt.
   487   if (_guid.Length() == 12) {
   488     MOZ_ASSERT(IsValidGUID(_guid));
   489     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
   490   }
   491   else {
   492     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_guid"));
   493   }
   494   NS_ENSURE_SUCCESS(rv, rv);
   496   rv = stmt->Execute();
   497   NS_ENSURE_SUCCESS(rv, rv);
   499   if (*_itemId == -1) {
   500     // Get the newly inserted item id and GUID.
   501     nsCOMPtr<mozIStorageStatement> lastInsertIdStmt = mDB->GetStatement(
   502       "SELECT id, guid "
   503       "FROM moz_bookmarks "
   504       "ORDER BY ROWID DESC "
   505       "LIMIT 1"
   506     );
   507     NS_ENSURE_STATE(lastInsertIdStmt);
   508     mozStorageStatementScoper lastInsertIdScoper(lastInsertIdStmt);
   510     bool hasResult;
   511     rv = lastInsertIdStmt->ExecuteStep(&hasResult);
   512     NS_ENSURE_SUCCESS(rv, rv);
   513     NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
   514     rv = lastInsertIdStmt->GetInt64(0, _itemId);
   515     NS_ENSURE_SUCCESS(rv, rv);
   516     rv = lastInsertIdStmt->GetUTF8String(1, _guid);
   517     NS_ENSURE_SUCCESS(rv, rv);
   518   }
   520   if (aParentId > 0) {
   521     // Update last modified date of the ancestors.
   522     // TODO (bug 408991): Doing this for all ancestors would be slow without a
   523     //                    nested tree, so for now update only the parent.
   524     rv = SetItemDateInternal(LAST_MODIFIED, aParentId, aDateAdded);
   525     NS_ENSURE_SUCCESS(rv, rv);
   526   }
   528   // Add a cache entry since we know everything about this bookmark.
   529   BookmarkData bookmark;
   530   bookmark.id = *_itemId;
   531   bookmark.guid.Assign(_guid);
   532   if (aTitle.IsVoid()) {
   533     bookmark.title.SetIsVoid(true);
   534   }
   535   else {
   536     bookmark.title.Assign(aTitle);
   537   }
   538   bookmark.position = aIndex;
   539   bookmark.placeId = aPlaceId;
   540   bookmark.parentId = aParentId;
   541   bookmark.type = aItemType;
   542   bookmark.dateAdded = aDateAdded;
   543   if (aLastModified)
   544     bookmark.lastModified = aLastModified;
   545   else
   546     bookmark.lastModified = aDateAdded;
   547   if (aURI) {
   548     rv = aURI->GetSpec(bookmark.url);
   549     NS_ENSURE_SUCCESS(rv, rv);
   550   }
   551   bookmark.parentGuid = aParentGuid;
   552   bookmark.grandParentId = aGrandParentId;
   554   ADD_TO_BOOKMARK_CACHE(*_itemId, bookmark);
   556   return NS_OK;
   557 }
   560 NS_IMETHODIMP
   561 nsNavBookmarks::InsertBookmark(int64_t aFolder,
   562                                nsIURI* aURI,
   563                                int32_t aIndex,
   564                                const nsACString& aTitle,
   565                                const nsACString& aGUID,
   566                                int64_t* aNewBookmarkId)
   567 {
   568   NS_ENSURE_ARG(aURI);
   569   NS_ENSURE_ARG_POINTER(aNewBookmarkId);
   570   NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
   572   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
   573     return NS_ERROR_INVALID_ARG;
   575   mozStorageTransaction transaction(mDB->MainConn(), false);
   577   nsNavHistory* history = nsNavHistory::GetHistoryService();
   578   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
   579   int64_t placeId;
   580   nsAutoCString placeGuid;
   581   nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
   582   NS_ENSURE_SUCCESS(rv, rv);
   584   // Get the correct index for insertion.  This also ensures the parent exists.
   585   int32_t index, folderCount;
   586   int64_t grandParentId;
   587   nsAutoCString folderGuid;
   588   rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
   589   NS_ENSURE_SUCCESS(rv, rv);
   590   if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
   591       aIndex >= folderCount) {
   592     index = folderCount;
   593   }
   594   else {
   595     index = aIndex;
   596     // Create space for the insertion.
   597     rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
   598     NS_ENSURE_SUCCESS(rv, rv);
   599   }
   601   *aNewBookmarkId = -1;
   602   PRTime dateAdded = PR_Now();
   603   nsAutoCString guid(aGUID);
   604   nsCString title;
   605   TruncateTitle(aTitle, title);
   607   rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
   608                           0, folderGuid, grandParentId, aURI,
   609                           aNewBookmarkId, guid);
   610   NS_ENSURE_SUCCESS(rv, rv);
   612   // If not a tag, recalculate frecency for this entry, since it changed.
   613   if (grandParentId != mTagsRoot) {
   614     rv = history->UpdateFrecency(placeId);
   615     NS_ENSURE_SUCCESS(rv, rv);
   616   }
   618   rv = transaction.Commit();
   619   NS_ENSURE_SUCCESS(rv, rv);
   621   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   622                    nsINavBookmarkObserver,
   623                    OnItemAdded(*aNewBookmarkId, aFolder, index, TYPE_BOOKMARK,
   624                                aURI, title, dateAdded, guid, folderGuid));
   626   // If the bookmark has been added to a tag container, notify all
   627   // bookmark-folder result nodes which contain a bookmark for the new
   628   // bookmark's url.
   629   if (grandParentId == mTagsRoot) {
   630     // Notify a tags change to all bookmarks for this URI.
   631     nsTArray<BookmarkData> bookmarks;
   632     rv = GetBookmarksForURI(aURI, bookmarks);
   633     NS_ENSURE_SUCCESS(rv, rv);
   635     for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
   636       // Check that bookmarks doesn't include the current tag itemId.
   637       MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
   639       NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   640                        nsINavBookmarkObserver,
   641                        OnItemChanged(bookmarks[i].id,
   642                                      NS_LITERAL_CSTRING("tags"),
   643                                      false,
   644                                      EmptyCString(),
   645                                      bookmarks[i].lastModified,
   646                                      TYPE_BOOKMARK,
   647                                      bookmarks[i].parentId,
   648                                      bookmarks[i].guid,
   649                                      bookmarks[i].parentGuid));
   650     }
   651   }
   653   return NS_OK;
   654 }
   657 NS_IMETHODIMP
   658 nsNavBookmarks::RemoveItem(int64_t aItemId)
   659 {
   660   PROFILER_LABEL("bookmarks", "RemoveItem");
   661   NS_ENSURE_ARG(!IsRoot(aItemId));
   663   BookmarkData bookmark;
   664   nsresult rv = FetchItemInfo(aItemId, bookmark);
   665   NS_ENSURE_SUCCESS(rv, rv);
   667   mozStorageTransaction transaction(mDB->MainConn(), false);
   669   // First, if not a tag, remove item annotations.
   670   if (bookmark.parentId != mTagsRoot &&
   671       bookmark.grandParentId != mTagsRoot) {
   672     nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
   673     NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
   674     rv = annosvc->RemoveItemAnnotations(bookmark.id);
   675     NS_ENSURE_SUCCESS(rv, rv);
   676   }
   678   if (bookmark.type == TYPE_FOLDER) {
   679     // Remove all of the folder's children.
   680     rv = RemoveFolderChildren(bookmark.id);
   681     NS_ENSURE_SUCCESS(rv, rv);
   682   }
   684   BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
   686   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   687     "DELETE FROM moz_bookmarks WHERE id = :item_id"
   688   );
   689   NS_ENSURE_STATE(stmt);
   690   mozStorageStatementScoper scoper(stmt);
   692   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
   693   NS_ENSURE_SUCCESS(rv, rv);
   694   rv = stmt->Execute();
   695   NS_ENSURE_SUCCESS(rv, rv);
   697   // Fix indices in the parent.
   698   if (bookmark.position != DEFAULT_INDEX) {
   699     rv = AdjustIndices(bookmark.parentId,
   700                        bookmark.position + 1, INT32_MAX, -1);
   701     NS_ENSURE_SUCCESS(rv, rv);
   702   }
   704   bookmark.lastModified = PR_Now();
   705   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId,
   706                            bookmark.lastModified);
   707   NS_ENSURE_SUCCESS(rv, rv);
   709   rv = transaction.Commit();
   710   NS_ENSURE_SUCCESS(rv, rv);
   712   END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
   714   nsCOMPtr<nsIURI> uri;
   715   if (bookmark.type == TYPE_BOOKMARK) {
   716     // If not a tag, recalculate frecency for this entry, since it changed.
   717     if (bookmark.grandParentId != mTagsRoot) {
   718       nsNavHistory* history = nsNavHistory::GetHistoryService();
   719       NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
   720       rv = history->UpdateFrecency(bookmark.placeId);
   721       NS_ENSURE_SUCCESS(rv, rv);
   722     }
   724     rv = UpdateKeywordsHashForRemovedBookmark(aItemId);
   725     NS_ENSURE_SUCCESS(rv, rv);
   727     // A broken url should not interrupt the removal process.
   728     (void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
   729   }
   731   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   732                    nsINavBookmarkObserver,
   733                    OnItemRemoved(bookmark.id,
   734                                  bookmark.parentId,
   735                                  bookmark.position,
   736                                  bookmark.type,
   737                                  uri,
   738                                  bookmark.guid,
   739                                  bookmark.parentGuid));
   741   if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == mTagsRoot &&
   742       uri) {
   743     // If the removed bookmark was child of a tag container, notify a tags
   744     // change to all bookmarks for this URI.
   745     nsTArray<BookmarkData> bookmarks;
   746     rv = GetBookmarksForURI(uri, bookmarks);
   747     NS_ENSURE_SUCCESS(rv, rv);
   749     for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
   750       NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   751                        nsINavBookmarkObserver,
   752                        OnItemChanged(bookmarks[i].id,
   753                                      NS_LITERAL_CSTRING("tags"),
   754                                      false,
   755                                      EmptyCString(),
   756                                      bookmarks[i].lastModified,
   757                                      TYPE_BOOKMARK,
   758                                      bookmarks[i].parentId,
   759                                      bookmarks[i].guid,
   760                                      bookmarks[i].parentGuid));
   761     }
   763   }
   765   return NS_OK;
   766 }
   769 NS_IMETHODIMP
   770 nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aName,
   771                              int32_t aIndex, const nsACString& aGUID,
   772                              int64_t* aNewFolder)
   773 {
   774   // NOTE: aParent can be null for root creation, so not checked
   775   NS_ENSURE_ARG_POINTER(aNewFolder);
   777   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
   778     return NS_ERROR_INVALID_ARG;
   780   // CreateContainerWithID returns the index of the new folder, but that's not
   781   // used here.  To avoid any risk of corrupting data should this function
   782   // be changed, we'll use a local variable to hold it.  The true argument
   783   // will cause notifications to be sent to bookmark observers.
   784   int32_t localIndex = aIndex;
   785   nsresult rv = CreateContainerWithID(-1, aParent, aName, true, &localIndex,
   786                                       aGUID, aNewFolder);
   787   NS_ENSURE_SUCCESS(rv, rv);
   788   return NS_OK;
   789 }
   791 NS_IMETHODIMP
   792 nsNavBookmarks::GetFolderReadonly(int64_t aFolder, bool* aResult)
   793 {
   794   NS_ENSURE_ARG_MIN(aFolder, 1);
   795   NS_ENSURE_ARG_POINTER(aResult);
   797   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
   798   NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
   799   nsresult rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, aResult);
   800   NS_ENSURE_SUCCESS(rv, rv);
   801   return NS_OK;
   802 }
   805 NS_IMETHODIMP
   806 nsNavBookmarks::SetFolderReadonly(int64_t aFolder, bool aReadOnly)
   807 {
   808   NS_ENSURE_ARG_MIN(aFolder, 1);
   810   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
   811   NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
   812   nsresult rv;
   813   if (aReadOnly) {
   814     rv = annosvc->SetItemAnnotationInt32(aFolder, READ_ONLY_ANNO, 1, 0,
   815                                          nsAnnotationService::EXPIRE_NEVER);
   816     NS_ENSURE_SUCCESS(rv, rv);
   817   }
   818   else {
   819     bool hasAnno;
   820     rv = annosvc->ItemHasAnnotation(aFolder, READ_ONLY_ANNO, &hasAnno);
   821     NS_ENSURE_SUCCESS(rv, rv);
   822     if (hasAnno) {
   823       rv = annosvc->RemoveItemAnnotation(aFolder, READ_ONLY_ANNO);
   824       NS_ENSURE_SUCCESS(rv, rv);
   825     }
   826   }
   827   return NS_OK;
   828 }
   831 nsresult
   832 nsNavBookmarks::CreateContainerWithID(int64_t aItemId,
   833                                       int64_t aParent,
   834                                       const nsACString& aTitle,
   835                                       bool aIsBookmarkFolder,
   836                                       int32_t* aIndex,
   837                                       const nsACString& aGUID,
   838                                       int64_t* aNewFolder)
   839 {
   840   NS_ENSURE_ARG_MIN(*aIndex, nsINavBookmarksService::DEFAULT_INDEX);
   842   // Get the correct index for insertion.  This also ensures the parent exists.
   843   int32_t index, folderCount;
   844   int64_t grandParentId;
   845   nsAutoCString folderGuid;
   846   nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
   847   NS_ENSURE_SUCCESS(rv, rv);
   849   mozStorageTransaction transaction(mDB->MainConn(), false);
   851   if (*aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
   852       *aIndex >= folderCount) {
   853     index = folderCount;
   854   } else {
   855     index = *aIndex;
   856     // Create space for the insertion.
   857     rv = AdjustIndices(aParent, index, INT32_MAX, 1);
   858     NS_ENSURE_SUCCESS(rv, rv);
   859   }
   861   *aNewFolder = aItemId;
   862   PRTime dateAdded = PR_Now();
   863   nsAutoCString guid(aGUID);
   864   nsCString title;
   865   TruncateTitle(aTitle, title);
   867   rv = InsertBookmarkInDB(-1, FOLDER, aParent, index,
   868                           title, dateAdded, 0, folderGuid, grandParentId,
   869                           nullptr, aNewFolder, guid);
   870   NS_ENSURE_SUCCESS(rv, rv);
   872   rv = transaction.Commit();
   873   NS_ENSURE_SUCCESS(rv, rv);
   875   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   876                    nsINavBookmarkObserver,
   877                    OnItemAdded(*aNewFolder, aParent, index, FOLDER,
   878                                nullptr, title, dateAdded, guid, folderGuid));
   880   *aIndex = index;
   881   return NS_OK;
   882 }
   885 NS_IMETHODIMP
   886 nsNavBookmarks::InsertSeparator(int64_t aParent,
   887                                 int32_t aIndex,
   888                                 const nsACString& aGUID,
   889                                 int64_t* aNewItemId)
   890 {
   891   NS_ENSURE_ARG_MIN(aParent, 1);
   892   NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
   893   NS_ENSURE_ARG_POINTER(aNewItemId);
   895   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
   896     return NS_ERROR_INVALID_ARG;
   898   // Get the correct index for insertion.  This also ensures the parent exists.
   899   int32_t index, folderCount;
   900   int64_t grandParentId;
   901   nsAutoCString folderGuid;
   902   nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
   903   NS_ENSURE_SUCCESS(rv, rv);
   905   mozStorageTransaction transaction(mDB->MainConn(), false);
   907   if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
   908       aIndex >= folderCount) {
   909     index = folderCount;
   910   }
   911   else {
   912     index = aIndex;
   913     // Create space for the insertion.
   914     rv = AdjustIndices(aParent, index, INT32_MAX, 1);
   915     NS_ENSURE_SUCCESS(rv, rv);
   916   }
   918   *aNewItemId = -1;
   919   // Set a NULL title rather than an empty string.
   920   nsCString voidString;
   921   voidString.SetIsVoid(true);
   922   nsAutoCString guid(aGUID);
   923   PRTime dateAdded = PR_Now();
   924   rv = InsertBookmarkInDB(-1, SEPARATOR, aParent, index, voidString, dateAdded,
   925                           0, folderGuid, grandParentId, nullptr,
   926                           aNewItemId, guid);
   927   NS_ENSURE_SUCCESS(rv, rv);
   929   rv = transaction.Commit();
   930   NS_ENSURE_SUCCESS(rv, rv);
   932   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
   933                    nsINavBookmarkObserver,
   934                    OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR,
   935                                nullptr, voidString, dateAdded, guid, folderGuid));
   937   return NS_OK;
   938 }
   941 nsresult
   942 nsNavBookmarks::GetLastChildId(int64_t aFolderId, int64_t* aItemId)
   943 {
   944   NS_ASSERTION(aFolderId > 0, "Invalid folder id");
   945   *aItemId = -1;
   947   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   948     "SELECT id FROM moz_bookmarks WHERE parent = :parent "
   949     "ORDER BY position DESC LIMIT 1"
   950   );
   951   NS_ENSURE_STATE(stmt);
   952   mozStorageStatementScoper scoper(stmt);
   954   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
   955   NS_ENSURE_SUCCESS(rv, rv);
   956   bool found;
   957   rv = stmt->ExecuteStep(&found);
   958   NS_ENSURE_SUCCESS(rv, rv);
   959   if (found) {
   960     rv = stmt->GetInt64(0, aItemId);
   961     NS_ENSURE_SUCCESS(rv, rv);
   962   }
   964   return NS_OK;
   965 }
   968 NS_IMETHODIMP
   969 nsNavBookmarks::GetIdForItemAt(int64_t aFolder,
   970                                int32_t aIndex,
   971                                int64_t* aItemId)
   972 {
   973   NS_ENSURE_ARG_MIN(aFolder, 1);
   974   NS_ENSURE_ARG_POINTER(aItemId);
   976   *aItemId = -1;
   978   nsresult rv;
   979   if (aIndex == nsINavBookmarksService::DEFAULT_INDEX) {
   980     // Get last item within aFolder.
   981     rv = GetLastChildId(aFolder, aItemId);
   982     NS_ENSURE_SUCCESS(rv, rv);
   983   }
   984   else {
   985     // Get the item in aFolder with position aIndex.
   986     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
   987       "SELECT id, fk, type FROM moz_bookmarks "
   988       "WHERE parent = :parent AND position = :item_index"
   989     );
   990     NS_ENSURE_STATE(stmt);
   991     mozStorageStatementScoper scoper(stmt);
   993     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolder);
   994     NS_ENSURE_SUCCESS(rv, rv);
   995     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
   996     NS_ENSURE_SUCCESS(rv, rv);
   998     bool found;
   999     rv = stmt->ExecuteStep(&found);
  1000     NS_ENSURE_SUCCESS(rv, rv);
  1001     if (found) {
  1002       rv = stmt->GetInt64(0, aItemId);
  1003       NS_ENSURE_SUCCESS(rv, rv);
  1006   return NS_OK;
  1009 NS_IMPL_ISUPPORTS(nsNavBookmarks::RemoveFolderTransaction, nsITransaction)
  1011 NS_IMETHODIMP
  1012 nsNavBookmarks::GetRemoveFolderTransaction(int64_t aFolderId, nsITransaction** aResult)
  1014   NS_ENSURE_ARG_MIN(aFolderId, 1);
  1015   NS_ENSURE_ARG_POINTER(aResult);
  1017   // Create and initialize a RemoveFolderTransaction object that can be used to
  1018   // recreate the folder safely later.
  1020   RemoveFolderTransaction* rft =
  1021     new RemoveFolderTransaction(aFolderId);
  1022   if (!rft)
  1023     return NS_ERROR_OUT_OF_MEMORY;
  1025   NS_ADDREF(*aResult = rft);
  1026   return NS_OK;
  1030 nsresult
  1031 nsNavBookmarks::GetDescendantFolders(int64_t aFolderId,
  1032                                      nsTArray<int64_t>& aDescendantFoldersArray) {
  1033   nsresult rv;
  1034   // New descendant folders will be added from this index on.
  1035   uint32_t startIndex = aDescendantFoldersArray.Length();
  1037     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1038       "SELECT id "
  1039       "FROM moz_bookmarks "
  1040       "WHERE parent = :parent "
  1041       "AND type = :item_type "
  1042     );
  1043     NS_ENSURE_STATE(stmt);
  1044     mozStorageStatementScoper scoper(stmt);
  1046     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
  1047     NS_ENSURE_SUCCESS(rv, rv);
  1048     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_FOLDER);
  1049     NS_ENSURE_SUCCESS(rv, rv);
  1051     bool hasMore = false;
  1052     while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
  1053       int64_t itemId;
  1054       rv = stmt->GetInt64(0, &itemId);
  1055       NS_ENSURE_SUCCESS(rv, rv);
  1056       aDescendantFoldersArray.AppendElement(itemId);
  1060   // Recursively call GetDescendantFolders for added folders.
  1061   // We start at startIndex since previous folders are checked
  1062   // by previous calls to this method.
  1063   uint32_t childCount = aDescendantFoldersArray.Length();
  1064   for (uint32_t i = startIndex; i < childCount; ++i) {
  1065     GetDescendantFolders(aDescendantFoldersArray[i], aDescendantFoldersArray);
  1068   return NS_OK;
  1072 nsresult
  1073 nsNavBookmarks::GetDescendantChildren(int64_t aFolderId,
  1074                                       const nsACString& aFolderGuid,
  1075                                       int64_t aGrandParentId,
  1076                                       nsTArray<BookmarkData>& aFolderChildrenArray) {
  1077   // New children will be added from this index on.
  1078   uint32_t startIndex = aFolderChildrenArray.Length();
  1079   nsresult rv;
  1081     // Collect children informations.
  1082     // Select all children of a given folder, sorted by position.
  1083     // This is a LEFT JOIN because not all bookmarks types have a place.
  1084     // We construct a result where the first columns exactly match
  1085     // kGetInfoIndex_* order, and additionally contains columns for position,
  1086     // item_child, and folder_child from moz_bookmarks.
  1087     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1088       "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
  1089              "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
  1090              "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
  1091              "b.position, b.type, b.fk "
  1092       "FROM moz_bookmarks b "
  1093       "LEFT JOIN moz_places h ON b.fk = h.id "
  1094       "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
  1095       "WHERE b.parent = :parent "
  1096       "ORDER BY b.position ASC"
  1097     );
  1098     NS_ENSURE_STATE(stmt);
  1099     mozStorageStatementScoper scoper(stmt);
  1101     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
  1102     NS_ENSURE_SUCCESS(rv, rv);
  1104     bool hasMore;
  1105     while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
  1106       BookmarkData child;
  1107       rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
  1108       NS_ENSURE_SUCCESS(rv, rv);
  1109       child.parentId = aFolderId;
  1110       child.grandParentId = aGrandParentId;
  1111       child.parentGuid = aFolderGuid;
  1112       rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
  1113       NS_ENSURE_SUCCESS(rv, rv);
  1114       rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
  1115       NS_ENSURE_SUCCESS(rv, rv);
  1116       rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
  1117       NS_ENSURE_SUCCESS(rv, rv);
  1118       rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
  1119       NS_ENSURE_SUCCESS(rv, rv);
  1121       if (child.type == TYPE_BOOKMARK) {
  1122         rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
  1123         NS_ENSURE_SUCCESS(rv, rv);
  1126       // Append item to children's array.
  1127       aFolderChildrenArray.AppendElement(child);
  1131   // Recursively call GetDescendantChildren for added folders.
  1132   // We start at startIndex since previous folders are checked
  1133   // by previous calls to this method.
  1134   uint32_t childCount = aFolderChildrenArray.Length();
  1135   for (uint32_t i = startIndex; i < childCount; ++i) {
  1136     if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
  1137       // nsTarray assumes that all children can be memmove()d, thus we can't
  1138       // just pass aFolderChildrenArray[i].guid to a method that will change
  1139       // the array itself.  Otherwise, since it's passed by reference, after a
  1140       // memmove() it could point to garbage and cause intermittent crashes.
  1141       nsCString guid = aFolderChildrenArray[i].guid;
  1142       GetDescendantChildren(aFolderChildrenArray[i].id,
  1143                             guid,
  1144                             aFolderId,
  1145                             aFolderChildrenArray);
  1149   return NS_OK;
  1153 NS_IMETHODIMP
  1154 nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
  1156   PROFILER_LABEL("bookmarks", "RemoveFolderChilder");
  1157   NS_ENSURE_ARG_MIN(aFolderId, 1);
  1158   NS_ENSURE_ARG(aFolderId != mRoot);
  1160   BookmarkData folder;
  1161   nsresult rv = FetchItemInfo(aFolderId, folder);
  1162   NS_ENSURE_SUCCESS(rv, rv);
  1163   NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
  1165   // Fill folder children array recursively.
  1166   nsTArray<BookmarkData> folderChildrenArray;
  1167   rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
  1168                              folderChildrenArray);
  1169   NS_ENSURE_SUCCESS(rv, rv);
  1171   // Build a string of folders whose children will be removed.
  1172   nsCString foldersToRemove;
  1173   for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
  1174     BookmarkData& child = folderChildrenArray[i];
  1176     if (child.type == TYPE_FOLDER) {
  1177       foldersToRemove.AppendLiteral(",");
  1178       foldersToRemove.AppendInt(child.id);
  1181     BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
  1184   // Delete items from the database now.
  1185   mozStorageTransaction transaction(mDB->MainConn(), false);
  1187   nsCOMPtr<mozIStorageStatement> deleteStatement = mDB->GetStatement(
  1188     NS_LITERAL_CSTRING(
  1189       "DELETE FROM moz_bookmarks "
  1190       "WHERE parent IN (:parent") + foldersToRemove + NS_LITERAL_CSTRING(")")
  1191   );
  1192   NS_ENSURE_STATE(deleteStatement);
  1193   mozStorageStatementScoper deleteStatementScoper(deleteStatement);
  1195   rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
  1196   NS_ENSURE_SUCCESS(rv, rv);
  1197   rv = deleteStatement->Execute();
  1198   NS_ENSURE_SUCCESS(rv, rv);
  1200   // Clean up orphan items annotations.
  1201   rv = mDB->MainConn()->ExecuteSimpleSQL(
  1202     NS_LITERAL_CSTRING(
  1203       "DELETE FROM moz_items_annos "
  1204       "WHERE id IN ("
  1205         "SELECT a.id from moz_items_annos a "
  1206         "LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
  1207         "WHERE b.id ISNULL)"));
  1208   NS_ENSURE_SUCCESS(rv, rv);
  1210   // Set the lastModified date.
  1211   rv = SetItemDateInternal(LAST_MODIFIED, folder.id, PR_Now());
  1212   NS_ENSURE_SUCCESS(rv, rv);
  1214   for (uint32_t i = 0; i < folderChildrenArray.Length(); i++) {
  1215     BookmarkData& child = folderChildrenArray[i];
  1216     if (child.type == TYPE_BOOKMARK) {
  1217       // If not a tag, recalculate frecency for this entry, since it changed.
  1218       if (child.grandParentId != mTagsRoot) {
  1219         nsNavHistory* history = nsNavHistory::GetHistoryService();
  1220         NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  1221         rv = history->UpdateFrecency(child.placeId);
  1222         NS_ENSURE_SUCCESS(rv, rv);
  1225       rv = UpdateKeywordsHashForRemovedBookmark(child.id);
  1226       NS_ENSURE_SUCCESS(rv, rv);
  1228     END_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
  1231   rv = transaction.Commit();
  1232   NS_ENSURE_SUCCESS(rv, rv);
  1234   // Call observers in reverse order to serve children before their parent.
  1235   for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
  1236     BookmarkData& child = folderChildrenArray[i];
  1237     nsCOMPtr<nsIURI> uri;
  1238     if (child.type == TYPE_BOOKMARK) {
  1239       // A broken url should not interrupt the removal process.
  1240       (void)NS_NewURI(getter_AddRefs(uri), child.url);
  1243     NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1244                      nsINavBookmarkObserver,
  1245                      OnItemRemoved(child.id,
  1246                                    child.parentId,
  1247                                    child.position,
  1248                                    child.type,
  1249                                    uri,
  1250                                    child.guid,
  1251                                    child.parentGuid));
  1253     if (child.type == TYPE_BOOKMARK && child.grandParentId == mTagsRoot &&
  1254         uri) {
  1255       // If the removed bookmark was a child of a tag container, notify all
  1256       // bookmark-folder result nodes which contain a bookmark for the removed
  1257       // bookmark's url.
  1258       nsTArray<BookmarkData> bookmarks;
  1259       rv = GetBookmarksForURI(uri, bookmarks);
  1260       NS_ENSURE_SUCCESS(rv, rv);
  1262       for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
  1263         NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1264                          nsINavBookmarkObserver,
  1265                          OnItemChanged(bookmarks[i].id,
  1266                                        NS_LITERAL_CSTRING("tags"),
  1267                                        false,
  1268                                        EmptyCString(),
  1269                                        bookmarks[i].lastModified,
  1270                                        TYPE_BOOKMARK,
  1271                                        bookmarks[i].parentId,
  1272                                        bookmarks[i].guid,
  1273                                        bookmarks[i].parentGuid));
  1278   return NS_OK;
  1282 NS_IMETHODIMP
  1283 nsNavBookmarks::MoveItem(int64_t aItemId, int64_t aNewParent, int32_t aIndex)
  1285   NS_ENSURE_ARG(!IsRoot(aItemId));
  1286   NS_ENSURE_ARG_MIN(aItemId, 1);
  1287   NS_ENSURE_ARG_MIN(aNewParent, 1);
  1288   // -1 is append, but no other negative number is allowed.
  1289   NS_ENSURE_ARG_MIN(aIndex, -1);
  1290   // Disallow making an item its own parent.
  1291   NS_ENSURE_ARG(aItemId != aNewParent);
  1293   mozStorageTransaction transaction(mDB->MainConn(), false);
  1295   BookmarkData bookmark;
  1296   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1297   NS_ENSURE_SUCCESS(rv, rv);
  1299   // if parent and index are the same, nothing to do
  1300   if (bookmark.parentId == aNewParent && bookmark.position == aIndex)
  1301     return NS_OK;
  1303   // Make sure aNewParent is not aFolder or a subfolder of aFolder.
  1304   // TODO: make this performant, maybe with a nested tree (bug 408991).
  1305   if (bookmark.type == TYPE_FOLDER) {
  1306     int64_t ancestorId = aNewParent;
  1308     while (ancestorId) {
  1309       if (ancestorId == bookmark.id) {
  1310         return NS_ERROR_INVALID_ARG;
  1312       rv = GetFolderIdForItem(ancestorId, &ancestorId);
  1313       if (NS_FAILED(rv)) {
  1314         break;
  1319   // calculate new index
  1320   int32_t newIndex, folderCount;
  1321   int64_t grandParentId;
  1322   nsAutoCString newParentGuid;
  1323   rv = FetchFolderInfo(aNewParent, &folderCount, newParentGuid, &grandParentId);
  1324   NS_ENSURE_SUCCESS(rv, rv);
  1325   if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
  1326       aIndex >= folderCount) {
  1327     newIndex = folderCount;
  1328     // If the parent remains the same, then the folder is really being moved
  1329     // to count - 1 (since it's being removed from the old position)
  1330     if (bookmark.parentId == aNewParent) {
  1331       --newIndex;
  1333   } else {
  1334     newIndex = aIndex;
  1336     if (bookmark.parentId == aNewParent && newIndex > bookmark.position) {
  1337       // when an item is being moved lower in the same folder, the new index
  1338       // refers to the index before it was removed. Removal causes everything
  1339       // to shift up.
  1340       --newIndex;
  1344   // this is like the previous check, except this covers if
  1345   // the specified index was -1 (append), and the calculated
  1346   // new index is the same as the existing index
  1347   if (aNewParent == bookmark.parentId && newIndex == bookmark.position) {
  1348     // Nothing to do!
  1349     return NS_OK;
  1352   // adjust indices to account for the move
  1353   // do this before we update the parent/index fields
  1354   // or we'll re-adjust the index for the item we are moving
  1355   if (bookmark.parentId == aNewParent) {
  1356     // We can optimize the updates if moving within the same container.
  1357     // We only shift the items between the old and new positions, since the
  1358     // insertion will offset the deletion.
  1359     if (bookmark.position > newIndex) {
  1360       rv = AdjustIndices(bookmark.parentId, newIndex, bookmark.position - 1, 1);
  1362     else {
  1363       rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, newIndex, -1);
  1365     NS_ENSURE_SUCCESS(rv, rv);
  1367   else {
  1368     // We're moving between containers, so this happens in two steps.
  1369     // First, fill the hole from the removal from the old parent.
  1370     rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
  1371     NS_ENSURE_SUCCESS(rv, rv);
  1372     // Now, make room in the new parent for the insertion.
  1373     rv = AdjustIndices(aNewParent, newIndex, INT32_MAX, 1);
  1374     NS_ENSURE_SUCCESS(rv, rv);
  1377   BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  1380     // Update parent and position.
  1381     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1382       "UPDATE moz_bookmarks SET parent = :parent, position = :item_index "
  1383       "WHERE id = :item_id "
  1384     );
  1385     NS_ENSURE_STATE(stmt);
  1386     mozStorageStatementScoper scoper(stmt);
  1388     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aNewParent);
  1389     NS_ENSURE_SUCCESS(rv, rv);
  1390     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), newIndex);
  1391     NS_ENSURE_SUCCESS(rv, rv);
  1392     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
  1393     NS_ENSURE_SUCCESS(rv, rv);
  1394     rv = stmt->Execute();
  1395     NS_ENSURE_SUCCESS(rv, rv);
  1398   PRTime now = PR_Now();
  1399   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId, now);
  1400   NS_ENSURE_SUCCESS(rv, rv);
  1401   rv = SetItemDateInternal(LAST_MODIFIED, aNewParent, now);
  1402   NS_ENSURE_SUCCESS(rv, rv);
  1404   rv = transaction.Commit();
  1405   NS_ENSURE_SUCCESS(rv, rv);
  1407   END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  1409   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1410                    nsINavBookmarkObserver,
  1411                    OnItemMoved(bookmark.id,
  1412                                bookmark.parentId,
  1413                                bookmark.position,
  1414                                aNewParent,
  1415                                newIndex,
  1416                                bookmark.type,
  1417                                bookmark.guid,
  1418                                bookmark.parentGuid,
  1419                                newParentGuid));
  1420   return NS_OK;
  1423 nsresult
  1424 nsNavBookmarks::FetchItemInfo(int64_t aItemId,
  1425                               BookmarkData& _bookmark)
  1427   // Check if the requested id is in the recent cache and avoid the database
  1428   // lookup if so.  Invalidate the cache after getting data if requested.
  1429   BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
  1430   if (key) {
  1431     _bookmark = key->bookmark;
  1432     return NS_OK;
  1435   // LEFT JOIN since not all bookmarks have an associated place.
  1436   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1437     "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
  1438            "b.dateAdded, b.lastModified, b.guid, t.guid, t.parent "
  1439     "FROM moz_bookmarks b "
  1440     "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
  1441     "LEFT JOIN moz_places h ON h.id = b.fk "
  1442     "WHERE b.id = :item_id"
  1443   );
  1444   NS_ENSURE_STATE(stmt);
  1445   mozStorageStatementScoper scoper(stmt);
  1447   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
  1448   NS_ENSURE_SUCCESS(rv, rv);
  1450   bool hasResult;
  1451   rv = stmt->ExecuteStep(&hasResult);
  1452   NS_ENSURE_SUCCESS(rv, rv);
  1453   if (!hasResult) {
  1454     return NS_ERROR_INVALID_ARG;
  1457   _bookmark.id = aItemId;
  1458   rv = stmt->GetUTF8String(1, _bookmark.url);
  1459   NS_ENSURE_SUCCESS(rv, rv);
  1460   bool isNull;
  1461   rv = stmt->GetIsNull(2, &isNull);
  1462   NS_ENSURE_SUCCESS(rv, rv);
  1463   if (isNull) {
  1464     _bookmark.title.SetIsVoid(true);
  1466   else {
  1467     rv = stmt->GetUTF8String(2, _bookmark.title);
  1468     NS_ENSURE_SUCCESS(rv, rv);
  1470   rv = stmt->GetInt32(3, &_bookmark.position);
  1471   NS_ENSURE_SUCCESS(rv, rv);
  1472   rv = stmt->GetInt64(4, &_bookmark.placeId);
  1473   NS_ENSURE_SUCCESS(rv, rv);
  1474   rv = stmt->GetInt64(5, &_bookmark.parentId);
  1475   NS_ENSURE_SUCCESS(rv, rv);
  1476   rv = stmt->GetInt32(6, &_bookmark.type);
  1477   NS_ENSURE_SUCCESS(rv, rv);
  1478   rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
  1479   NS_ENSURE_SUCCESS(rv, rv);
  1480   rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
  1481   NS_ENSURE_SUCCESS(rv, rv);
  1482   rv = stmt->GetUTF8String(9, _bookmark.guid);
  1483   NS_ENSURE_SUCCESS(rv, rv);
  1484   // Getting properties of the root would show no parent.
  1485   rv = stmt->GetIsNull(10, &isNull);
  1486   NS_ENSURE_SUCCESS(rv, rv);
  1487   if (!isNull) {
  1488     rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
  1489     NS_ENSURE_SUCCESS(rv, rv);
  1490     rv = stmt->GetInt64(11, &_bookmark.grandParentId);
  1491     NS_ENSURE_SUCCESS(rv, rv);
  1493   else {
  1494     _bookmark.grandParentId = -1;
  1497   ADD_TO_BOOKMARK_CACHE(aItemId, _bookmark);
  1499   return NS_OK;
  1502 nsresult
  1503 nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
  1504                                     int64_t aItemId,
  1505                                     PRTime aValue)
  1507   nsCOMPtr<mozIStorageStatement> stmt;
  1508   if (aDateType == DATE_ADDED) {
  1509     // lastModified is set to the same value as dateAdded.  We do this for
  1510     // performance reasons, since it will allow us to use an index to sort items
  1511     // by date.
  1512     stmt = mDB->GetStatement(
  1513       "UPDATE moz_bookmarks SET dateAdded = :date, lastModified = :date "
  1514       "WHERE id = :item_id"
  1515     );
  1517   else {
  1518     stmt = mDB->GetStatement(
  1519       "UPDATE moz_bookmarks SET lastModified = :date WHERE id = :item_id"
  1520     );
  1522   NS_ENSURE_STATE(stmt);
  1523   mozStorageStatementScoper scoper(stmt);
  1525   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
  1526   NS_ENSURE_SUCCESS(rv, rv);
  1527   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
  1528   NS_ENSURE_SUCCESS(rv, rv);
  1530   rv = stmt->Execute();
  1531   NS_ENSURE_SUCCESS(rv, rv);
  1533   // Update the cache entry, if needed.
  1534   BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
  1535   if (key) {
  1536     if (aDateType == DATE_ADDED) {
  1537       key->bookmark.dateAdded = aValue;
  1539     // Set lastModified in both cases.
  1540     key->bookmark.lastModified = aValue;
  1543   // note, we are not notifying the observers
  1544   // that the item has changed.
  1546   return NS_OK;
  1550 NS_IMETHODIMP
  1551 nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded)
  1553   NS_ENSURE_ARG_MIN(aItemId, 1);
  1555   BookmarkData bookmark;
  1556   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1557   NS_ENSURE_SUCCESS(rv, rv);
  1558   bookmark.dateAdded = aDateAdded;
  1560   rv = SetItemDateInternal(DATE_ADDED, bookmark.id, bookmark.dateAdded);
  1561   NS_ENSURE_SUCCESS(rv, rv);
  1563   // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
  1564   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1565                    nsINavBookmarkObserver,
  1566                    OnItemChanged(bookmark.id,
  1567                                  NS_LITERAL_CSTRING("dateAdded"),
  1568                                  false,
  1569                                  nsPrintfCString("%lld", bookmark.dateAdded),
  1570                                  bookmark.dateAdded,
  1571                                  bookmark.type,
  1572                                  bookmark.parentId,
  1573                                  bookmark.guid,
  1574                                  bookmark.parentGuid));
  1575   return NS_OK;
  1579 NS_IMETHODIMP
  1580 nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
  1582   NS_ENSURE_ARG_MIN(aItemId, 1);
  1583   NS_ENSURE_ARG_POINTER(_dateAdded);
  1585   BookmarkData bookmark;
  1586   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1587   NS_ENSURE_SUCCESS(rv, rv);
  1589   *_dateAdded = bookmark.dateAdded;
  1590   return NS_OK;
  1594 NS_IMETHODIMP
  1595 nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified)
  1597   NS_ENSURE_ARG_MIN(aItemId, 1);
  1599   BookmarkData bookmark;
  1600   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1601   NS_ENSURE_SUCCESS(rv, rv);
  1602   bookmark.lastModified = aLastModified;
  1604   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.id, bookmark.lastModified);
  1605   NS_ENSURE_SUCCESS(rv, rv);
  1607   // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
  1608   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1609                    nsINavBookmarkObserver,
  1610                    OnItemChanged(bookmark.id,
  1611                                  NS_LITERAL_CSTRING("lastModified"),
  1612                                  false,
  1613                                  nsPrintfCString("%lld", bookmark.lastModified),
  1614                                  bookmark.lastModified,
  1615                                  bookmark.type,
  1616                                  bookmark.parentId,
  1617                                  bookmark.guid,
  1618                                  bookmark.parentGuid));
  1619   return NS_OK;
  1623 NS_IMETHODIMP
  1624 nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
  1626   NS_ENSURE_ARG_MIN(aItemId, 1);
  1627   NS_ENSURE_ARG_POINTER(_lastModified);
  1629   BookmarkData bookmark;
  1630   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1631   NS_ENSURE_SUCCESS(rv, rv);
  1633   *_lastModified = bookmark.lastModified;
  1634   return NS_OK;
  1638 NS_IMETHODIMP
  1639 nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle)
  1641   NS_ENSURE_ARG_MIN(aItemId, 1);
  1643   BookmarkData bookmark;
  1644   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1645   NS_ENSURE_SUCCESS(rv, rv);
  1647   nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
  1648     "UPDATE moz_bookmarks SET title = :item_title, lastModified = :date "
  1649     "WHERE id = :item_id "
  1650   );
  1651   NS_ENSURE_STATE(statement);
  1652   mozStorageStatementScoper scoper(statement);
  1654   nsCString title;
  1655   TruncateTitle(aTitle, title);
  1657   // Support setting a null title, we support this in insertBookmark.
  1658   if (title.IsVoid()) {
  1659     rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
  1661   else {
  1662     rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
  1663                                          title);
  1665   NS_ENSURE_SUCCESS(rv, rv);
  1666   bookmark.lastModified = PR_Now();
  1667   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
  1668                                   bookmark.lastModified);
  1669   NS_ENSURE_SUCCESS(rv, rv);
  1670   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
  1671   NS_ENSURE_SUCCESS(rv, rv);
  1673   rv = statement->Execute();
  1674   NS_ENSURE_SUCCESS(rv, rv);
  1676   // Update the cache entry, if needed.
  1677   BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aItemId);
  1678   if (key) {
  1679     if (title.IsVoid()) {
  1680       key->bookmark.title.SetIsVoid(true);
  1682     else {
  1683       key->bookmark.title.Assign(title);
  1685     key->bookmark.lastModified = bookmark.lastModified;
  1688   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  1689                    nsINavBookmarkObserver,
  1690                    OnItemChanged(bookmark.id,
  1691                                  NS_LITERAL_CSTRING("title"),
  1692                                  false,
  1693                                  title,
  1694                                  bookmark.lastModified,
  1695                                  bookmark.type,
  1696                                  bookmark.parentId,
  1697                                  bookmark.guid,
  1698                                  bookmark.parentGuid));
  1699   return NS_OK;
  1703 NS_IMETHODIMP
  1704 nsNavBookmarks::GetItemTitle(int64_t aItemId,
  1705                              nsACString& _title)
  1707   NS_ENSURE_ARG_MIN(aItemId, 1);
  1709   BookmarkData bookmark;
  1710   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1711   NS_ENSURE_SUCCESS(rv, rv);
  1713   _title = bookmark.title;
  1714   return NS_OK;
  1718 NS_IMETHODIMP
  1719 nsNavBookmarks::GetBookmarkURI(int64_t aItemId,
  1720                                nsIURI** _URI)
  1722   NS_ENSURE_ARG_MIN(aItemId, 1);
  1723   NS_ENSURE_ARG_POINTER(_URI);
  1725   BookmarkData bookmark;
  1726   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1727   NS_ENSURE_SUCCESS(rv, rv);
  1729   rv = NS_NewURI(_URI, bookmark.url);
  1730   NS_ENSURE_SUCCESS(rv, rv);
  1732   return NS_OK;
  1736 NS_IMETHODIMP
  1737 nsNavBookmarks::GetItemType(int64_t aItemId, uint16_t* _type)
  1739   NS_ENSURE_ARG_MIN(aItemId, 1);
  1740   NS_ENSURE_ARG_POINTER(_type);
  1742   BookmarkData bookmark;
  1743   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1744   NS_ENSURE_SUCCESS(rv, rv);
  1746   *_type = static_cast<uint16_t>(bookmark.type);
  1747   return NS_OK;
  1751 nsresult
  1752 nsNavBookmarks::ResultNodeForContainer(int64_t aItemId,
  1753                                        nsNavHistoryQueryOptions* aOptions,
  1754                                        nsNavHistoryResultNode** aNode)
  1756   BookmarkData bookmark;
  1757   nsresult rv = FetchItemInfo(aItemId, bookmark);
  1758   NS_ENSURE_SUCCESS(rv, rv);
  1760   if (bookmark.type == TYPE_FOLDER) { // TYPE_FOLDER
  1761     *aNode = new nsNavHistoryFolderResultNode(bookmark.title,
  1762                                               aOptions,
  1763                                               bookmark.id);
  1765   else {
  1766     return NS_ERROR_INVALID_ARG;
  1769   (*aNode)->mDateAdded = bookmark.dateAdded;
  1770   (*aNode)->mLastModified = bookmark.lastModified;
  1771   (*aNode)->mBookmarkGuid = bookmark.guid;
  1773   NS_ADDREF(*aNode);
  1774   return NS_OK;
  1778 nsresult
  1779 nsNavBookmarks::QueryFolderChildren(
  1780   int64_t aFolderId,
  1781   nsNavHistoryQueryOptions* aOptions,
  1782   nsCOMArray<nsNavHistoryResultNode>* aChildren)
  1784   NS_ENSURE_ARG_POINTER(aOptions);
  1785   NS_ENSURE_ARG_POINTER(aChildren);
  1787   // Select all children of a given folder, sorted by position.
  1788   // This is a LEFT JOIN because not all bookmarks types have a place.
  1789   // We construct a result where the first columns exactly match those returned
  1790   // by mDBGetURLPageInfo, and additionally contains columns for position,
  1791   // item_child, and folder_child from moz_bookmarks.
  1792   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1793     "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
  1794            "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
  1795            "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
  1796            "b.position, b.type, b.fk "
  1797     "FROM moz_bookmarks b "
  1798     "LEFT JOIN moz_places h ON b.fk = h.id "
  1799     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
  1800     "WHERE b.parent = :parent "
  1801     "ORDER BY b.position ASC"
  1802   );
  1803   NS_ENSURE_STATE(stmt);
  1804   mozStorageStatementScoper scoper(stmt);
  1806   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
  1807   NS_ENSURE_SUCCESS(rv, rv);
  1809   nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
  1810   NS_ENSURE_SUCCESS(rv, rv);
  1812   int32_t index = -1;
  1813   bool hasResult;
  1814   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
  1815     rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
  1816     NS_ENSURE_SUCCESS(rv, rv);
  1819   return NS_OK;
  1823 nsresult
  1824 nsNavBookmarks::ProcessFolderNodeRow(
  1825   mozIStorageValueArray* aRow,
  1826   nsNavHistoryQueryOptions* aOptions,
  1827   nsCOMArray<nsNavHistoryResultNode>* aChildren,
  1828   int32_t& aCurrentIndex)
  1830   NS_ENSURE_ARG_POINTER(aRow);
  1831   NS_ENSURE_ARG_POINTER(aOptions);
  1832   NS_ENSURE_ARG_POINTER(aChildren);
  1834   // The results will be in order of aCurrentIndex. Even if we don't add a node
  1835   // because it was excluded, we need to count its index, so do that before
  1836   // doing anything else.
  1837   aCurrentIndex++;
  1839   int32_t itemType;
  1840   nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
  1841   NS_ENSURE_SUCCESS(rv, rv);
  1842   int64_t id;
  1843   rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
  1844   NS_ENSURE_SUCCESS(rv, rv);
  1846   nsRefPtr<nsNavHistoryResultNode> node;
  1848   if (itemType == TYPE_BOOKMARK) {
  1849     nsNavHistory* history = nsNavHistory::GetHistoryService();
  1850     NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  1851     rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
  1852     NS_ENSURE_SUCCESS(rv, rv);
  1854     uint32_t nodeType;
  1855     node->GetType(&nodeType);
  1856     if ((nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
  1857          aOptions->ExcludeQueries()) ||
  1858         (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
  1859          nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT &&
  1860          aOptions->ExcludeItems())) {
  1861       return NS_OK;
  1864   else if (itemType == TYPE_FOLDER) {
  1865     if (aOptions->ExcludeReadOnlyFolders()) {
  1866       // If the folder is read-only, skip it.
  1867       bool readOnly = false;
  1868       GetFolderReadonly(id, &readOnly);
  1869       if (readOnly)
  1870         return NS_OK;
  1873     nsAutoCString title;
  1874     rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
  1875     NS_ENSURE_SUCCESS(rv, rv);
  1877     node = new nsNavHistoryFolderResultNode(title, aOptions, id);
  1879     rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
  1880                         reinterpret_cast<int64_t*>(&node->mDateAdded));
  1881     NS_ENSURE_SUCCESS(rv, rv);
  1882     rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
  1883                         reinterpret_cast<int64_t*>(&node->mLastModified));
  1884     NS_ENSURE_SUCCESS(rv, rv);
  1886   else {
  1887     // This is a separator.
  1888     if (aOptions->ExcludeItems()) {
  1889       return NS_OK;
  1891     node = new nsNavHistorySeparatorResultNode();
  1893     node->mItemId = id;
  1894     rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
  1895                         reinterpret_cast<int64_t*>(&node->mDateAdded));
  1896     NS_ENSURE_SUCCESS(rv, rv);
  1897     rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
  1898                         reinterpret_cast<int64_t*>(&node->mLastModified));
  1899     NS_ENSURE_SUCCESS(rv, rv);
  1902   // Store the index of the node within this container.  Note that this is not
  1903   // moz_bookmarks.position.
  1904   node->mBookmarkIndex = aCurrentIndex;
  1906   rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
  1907   NS_ENSURE_SUCCESS(rv, rv);
  1909   NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
  1911   return NS_OK;
  1915 nsresult
  1916 nsNavBookmarks::QueryFolderChildrenAsync(
  1917   nsNavHistoryFolderResultNode* aNode,
  1918   int64_t aFolderId,
  1919   mozIStoragePendingStatement** _pendingStmt)
  1921   NS_ENSURE_ARG_POINTER(aNode);
  1922   NS_ENSURE_ARG_POINTER(_pendingStmt);
  1924   // Select all children of a given folder, sorted by position.
  1925   // This is a LEFT JOIN because not all bookmarks types have a place.
  1926   // We construct a result where the first columns exactly match those returned
  1927   // by mDBGetURLPageInfo, and additionally contains columns for position,
  1928   // item_child, and folder_child from moz_bookmarks.
  1929   nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
  1930     "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
  1931            "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
  1932            "b.parent, null, h.frecency, h.hidden, h.guid, b.guid, "
  1933            "b.position, b.type, b.fk "
  1934     "FROM moz_bookmarks b "
  1935     "LEFT JOIN moz_places h ON b.fk = h.id "
  1936     "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
  1937     "WHERE b.parent = :parent "
  1938     "ORDER BY b.position ASC"
  1939   );
  1940   NS_ENSURE_STATE(stmt);
  1942   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
  1943   NS_ENSURE_SUCCESS(rv, rv);
  1945   nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
  1946   rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
  1947   NS_ENSURE_SUCCESS(rv, rv);
  1949   NS_IF_ADDREF(*_pendingStmt = pendingStmt);
  1950   return NS_OK;
  1954 nsresult
  1955 nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
  1956                                 int32_t* _folderCount,
  1957                                 nsACString& _guid,
  1958                                 int64_t* _parentId)
  1960   *_folderCount = 0;
  1961   *_parentId = -1;
  1963   // This query has to always return results, so it can't be written as a join,
  1964   // though a left join of 2 subqueries would have the same cost.
  1965   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  1966     "SELECT count(*), "
  1967             "(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
  1968             "(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
  1969     "FROM moz_bookmarks "
  1970     "WHERE parent = :parent"
  1971   );
  1972   NS_ENSURE_STATE(stmt);
  1973   mozStorageStatementScoper scoper(stmt);
  1975   nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
  1976   NS_ENSURE_SUCCESS(rv, rv);
  1978   bool hasResult;
  1979   rv = stmt->ExecuteStep(&hasResult);
  1980   NS_ENSURE_SUCCESS(rv, rv);
  1981   NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
  1983   // Ensure that the folder we are looking for exists.
  1984   // Can't rely only on parent, since the root has parent 0, that doesn't exist.
  1985   bool isNull;
  1986   rv = stmt->GetIsNull(2, &isNull);
  1987   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
  1988                  NS_ERROR_INVALID_ARG);
  1990   rv = stmt->GetInt32(0, _folderCount);
  1991   NS_ENSURE_SUCCESS(rv, rv);
  1992   if (!isNull) {
  1993     rv = stmt->GetUTF8String(1, _guid);
  1994     NS_ENSURE_SUCCESS(rv, rv);
  1995     rv = stmt->GetInt64(2, _parentId);
  1996     NS_ENSURE_SUCCESS(rv, rv);
  1999   return NS_OK;
  2003 NS_IMETHODIMP
  2004 nsNavBookmarks::IsBookmarked(nsIURI* aURI, bool* aBookmarked)
  2006   NS_ENSURE_ARG(aURI);
  2007   NS_ENSURE_ARG_POINTER(aBookmarked);
  2009   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  2010     "SELECT 1 FROM moz_bookmarks b "
  2011     "JOIN moz_places h ON b.fk = h.id "
  2012     "WHERE h.url = :page_url"
  2013   );
  2014   NS_ENSURE_STATE(stmt);
  2015   mozStorageStatementScoper scoper(stmt);
  2017   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
  2018   NS_ENSURE_SUCCESS(rv, rv);
  2019   rv = stmt->ExecuteStep(aBookmarked);
  2020   NS_ENSURE_SUCCESS(rv, rv);
  2022   return NS_OK;
  2026 NS_IMETHODIMP
  2027 nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
  2029   NS_ENSURE_ARG(aURI);
  2030   NS_ENSURE_ARG_POINTER(_retval);
  2032   *_retval = nullptr;
  2034   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2035   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2036   int64_t placeId;
  2037   nsAutoCString placeGuid;
  2038   nsresult rv = history->GetIdForPage(aURI, &placeId, placeGuid);
  2039   NS_ENSURE_SUCCESS(rv, rv);
  2040   if (!placeId) {
  2041     // This URI is unknown, just return null.
  2042     return NS_OK;
  2045   // Check if a bookmark exists in the redirects chain for this URI.
  2046   // The query will also check if the page is directly bookmarked, and return
  2047   // the first found bookmark in case.  The check is directly on moz_bookmarks
  2048   // without special filtering.
  2049   // The next query finds the bookmarked ancestors in a redirects chain.
  2050   // It won't go further than 3 levels of redirects (a->b->c->your_place_id).
  2051   // To make this path 100% correct (up to any level) we would need either:
  2052   //  - A separate hash, build through recursive querying of the database.
  2053   //    This solution was previously implemented, but it had a negative effect
  2054   //    on startup since at each startup we have to recursively query the
  2055   //    database to rebuild a hash that is always the same across sessions.
  2056   //    It must be updated at each visit and bookmarks change too.  The code to
  2057   //    manage it is complex and prone to errors, sometimes causing incorrect
  2058   //    data fetches (for example wrong favicon for a redirected bookmark).
  2059   //  - A better way to track redirects for a visit.
  2060   //    We would need a separate table to track redirects, in the table we would
  2061   //    have visit_id, redirect_session.  To get all sources for
  2062   //    a visit then we could just join this table and get all visit_id that
  2063   //    are in the same redirect_session as our visit.  This has the drawback
  2064   //    that we can't ensure data integrity in the downgrade -> upgrade path,
  2065   //    since an old version would not update the table on new visits.
  2066   //
  2067   // For most cases these levels of redirects should be fine though, it's hard
  2068   // to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
  2069   //
  2070   // As a bonus the query also checks first if place_id is already a bookmark,
  2071   // so you don't have to check that apart.
  2073   nsCString query = nsPrintfCString(
  2074     "SELECT url FROM moz_places WHERE id = ( "
  2075       "SELECT :page_id FROM moz_bookmarks WHERE fk = :page_id "
  2076       "UNION ALL "
  2077       "SELECT COALESCE(grandparent.place_id, parent.place_id) AS r_place_id "
  2078       "FROM moz_historyvisits dest "
  2079       "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
  2080                                         "AND dest.visit_type IN (%d, %d) "
  2081       "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
  2082                                              "AND parent.visit_type IN (%d, %d) "
  2083       "WHERE dest.place_id = :page_id "
  2084       "AND EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = r_place_id) "
  2085       "LIMIT 1 "
  2086     ")",
  2087     nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
  2088     nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
  2089     nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
  2090     nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
  2091   );
  2093   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(query);
  2094   NS_ENSURE_STATE(stmt);
  2095   mozStorageStatementScoper scoper(stmt);
  2097   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), placeId);
  2098   NS_ENSURE_SUCCESS(rv, rv);
  2099   bool hasBookmarkedOrigin;
  2100   if (NS_SUCCEEDED(stmt->ExecuteStep(&hasBookmarkedOrigin)) &&
  2101       hasBookmarkedOrigin) {
  2102     nsAutoCString spec;
  2103     rv = stmt->GetUTF8String(0, spec);
  2104     NS_ENSURE_SUCCESS(rv, rv);
  2105     rv = NS_NewURI(_retval, spec);
  2106     NS_ENSURE_SUCCESS(rv, rv);
  2109   // If there is no bookmarked origin, we will just return null.
  2110   return NS_OK;
  2114 NS_IMETHODIMP
  2115 nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI)
  2117   NS_ENSURE_ARG_MIN(aBookmarkId, 1);
  2118   NS_ENSURE_ARG(aNewURI);
  2120   BookmarkData bookmark;
  2121   nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
  2122   NS_ENSURE_SUCCESS(rv, rv);
  2123   NS_ENSURE_ARG(bookmark.type == TYPE_BOOKMARK);
  2125   mozStorageTransaction transaction(mDB->MainConn(), false);
  2127   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2128   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2129   int64_t newPlaceId;
  2130   nsAutoCString newPlaceGuid;
  2131   rv = history->GetOrCreateIdForPage(aNewURI, &newPlaceId, newPlaceGuid);
  2132   NS_ENSURE_SUCCESS(rv, rv);
  2133   if (!newPlaceId)
  2134     return NS_ERROR_INVALID_ARG;
  2136   BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  2138   nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
  2139     "UPDATE moz_bookmarks SET fk = :page_id, lastModified = :date "
  2140     "WHERE id = :item_id "
  2141   );
  2142   NS_ENSURE_STATE(statement);
  2143   mozStorageStatementScoper scoper(statement);
  2145   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), newPlaceId);
  2146   NS_ENSURE_SUCCESS(rv, rv);
  2147   bookmark.lastModified = PR_Now();
  2148   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
  2149                                   bookmark.lastModified);
  2150   NS_ENSURE_SUCCESS(rv, rv);
  2151   rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
  2152   NS_ENSURE_SUCCESS(rv, rv);
  2153   rv = statement->Execute();
  2154   NS_ENSURE_SUCCESS(rv, rv);
  2156   rv = transaction.Commit();
  2157   NS_ENSURE_SUCCESS(rv, rv);
  2159   END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  2161   rv = history->UpdateFrecency(newPlaceId);
  2162   NS_ENSURE_SUCCESS(rv, rv);
  2164   // Upon changing the URI for a bookmark, update the frecency for the old
  2165   // place as well.
  2166   rv = history->UpdateFrecency(bookmark.placeId);
  2167   NS_ENSURE_SUCCESS(rv, rv);
  2169   nsAutoCString spec;
  2170   rv = aNewURI->GetSpec(spec);
  2171   NS_ENSURE_SUCCESS(rv, rv);
  2173   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2174                    nsINavBookmarkObserver,
  2175                    OnItemChanged(bookmark.id,
  2176                                  NS_LITERAL_CSTRING("uri"),
  2177                                  false,
  2178                                  spec,
  2179                                  bookmark.lastModified,
  2180                                  bookmark.type,
  2181                                  bookmark.parentId,
  2182                                  bookmark.guid,
  2183                                  bookmark.parentGuid));
  2184   return NS_OK;
  2188 NS_IMETHODIMP
  2189 nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
  2191   NS_ENSURE_ARG_MIN(aItemId, 1);
  2192   NS_ENSURE_ARG_POINTER(_parentId);
  2194   BookmarkData bookmark;
  2195   nsresult rv = FetchItemInfo(aItemId, bookmark);
  2196   NS_ENSURE_SUCCESS(rv, rv);
  2198   // this should not happen, but see bug #400448 for details
  2199   NS_ENSURE_TRUE(bookmark.id != bookmark.parentId, NS_ERROR_UNEXPECTED);
  2201   *_parentId = bookmark.parentId;
  2202   return NS_OK;
  2206 nsresult
  2207 nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
  2208                                            nsTArray<int64_t>& aResult,
  2209                                            bool aSkipTags)
  2211   NS_ENSURE_ARG(aURI);
  2213   // Double ordering covers possible lastModified ties, that could happen when
  2214   // importing, syncing or due to extensions.
  2215   // Note: not using a JOIN is cheaper in this case.
  2216   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  2217     "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
  2218     "FROM moz_bookmarks b "
  2219     "JOIN moz_bookmarks t on t.id = b.parent "
  2220     "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
  2221     "ORDER BY b.lastModified DESC, b.id DESC "
  2222   );
  2223   NS_ENSURE_STATE(stmt);
  2224   mozStorageStatementScoper scoper(stmt);
  2226   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
  2227   NS_ENSURE_SUCCESS(rv, rv);
  2229   bool more;
  2230   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
  2231     if (aSkipTags) {
  2232       // Skip tags, for the use-cases of this async getter they are useless.
  2233       int64_t grandParentId;
  2234       nsresult rv = stmt->GetInt64(5, &grandParentId);
  2235       NS_ENSURE_SUCCESS(rv, rv);
  2236       if (grandParentId == mTagsRoot) {
  2237         continue;
  2240     int64_t bookmarkId;
  2241     rv = stmt->GetInt64(0, &bookmarkId);
  2242     NS_ENSURE_SUCCESS(rv, rv);
  2243     NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
  2245   NS_ENSURE_SUCCESS(rv, rv);
  2247   return NS_OK;
  2250 nsresult
  2251 nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
  2252                                    nsTArray<BookmarkData>& aBookmarks)
  2254   NS_ENSURE_ARG(aURI);
  2256   // Double ordering covers possible lastModified ties, that could happen when
  2257   // importing, syncing or due to extensions.
  2258   // Note: not using a JOIN is cheaper in this case.
  2259   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  2260     "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
  2261     "FROM moz_bookmarks b "
  2262     "JOIN moz_bookmarks t on t.id = b.parent "
  2263     "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
  2264     "ORDER BY b.lastModified DESC, b.id DESC "
  2265   );
  2266   NS_ENSURE_STATE(stmt);
  2267   mozStorageStatementScoper scoper(stmt);
  2269   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
  2270   NS_ENSURE_SUCCESS(rv, rv);
  2272   bool more;
  2273   nsAutoString tags;
  2274   while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
  2275     // Skip tags.
  2276     int64_t grandParentId;
  2277     nsresult rv = stmt->GetInt64(5, &grandParentId);
  2278     NS_ENSURE_SUCCESS(rv, rv);
  2279     if (grandParentId == mTagsRoot) {
  2280       continue;
  2283     BookmarkData bookmark;
  2284     bookmark.grandParentId = grandParentId;
  2285     rv = stmt->GetInt64(0, &bookmark.id);
  2286     NS_ENSURE_SUCCESS(rv, rv);
  2287     rv = stmt->GetUTF8String(1, bookmark.guid);
  2288     NS_ENSURE_SUCCESS(rv, rv);
  2289     rv = stmt->GetInt64(2, &bookmark.parentId);
  2290     NS_ENSURE_SUCCESS(rv, rv);
  2291     rv = stmt->GetInt64(3, reinterpret_cast<int64_t*>(&bookmark.lastModified));
  2292     NS_ENSURE_SUCCESS(rv, rv);
  2293     rv = stmt->GetUTF8String(4, bookmark.parentGuid);
  2294     NS_ENSURE_SUCCESS(rv, rv);
  2296     NS_ENSURE_TRUE(aBookmarks.AppendElement(bookmark), NS_ERROR_OUT_OF_MEMORY);
  2299   return NS_OK;
  2302 NS_IMETHODIMP
  2303 nsNavBookmarks::GetBookmarkIdsForURI(nsIURI* aURI, uint32_t* aCount,
  2304                                      int64_t** aBookmarks)
  2306   NS_ENSURE_ARG(aURI);
  2307   NS_ENSURE_ARG_POINTER(aCount);
  2308   NS_ENSURE_ARG_POINTER(aBookmarks);
  2310   *aCount = 0;
  2311   *aBookmarks = nullptr;
  2312   nsTArray<int64_t> bookmarks;
  2314   // Get the information from the DB as a TArray
  2315   // TODO (bug 653816): make this API skip tags by default.
  2316   nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks, false);
  2317   NS_ENSURE_SUCCESS(rv, rv);
  2319   // Copy the results into a new array for output
  2320   if (bookmarks.Length()) {
  2321     *aBookmarks =
  2322       static_cast<int64_t*>(nsMemory::Alloc(sizeof(int64_t) * bookmarks.Length()));
  2323     if (!*aBookmarks)
  2324       return NS_ERROR_OUT_OF_MEMORY;
  2325     for (uint32_t i = 0; i < bookmarks.Length(); i ++)
  2326       (*aBookmarks)[i] = bookmarks[i];
  2329   *aCount = bookmarks.Length();
  2330   return NS_OK;
  2334 NS_IMETHODIMP
  2335 nsNavBookmarks::GetItemIndex(int64_t aItemId, int32_t* _index)
  2337   NS_ENSURE_ARG_MIN(aItemId, 1);
  2338   NS_ENSURE_ARG_POINTER(_index);
  2340   BookmarkData bookmark;
  2341   nsresult rv = FetchItemInfo(aItemId, bookmark);
  2342   // With respect to the API.
  2343   if (NS_FAILED(rv)) {
  2344     *_index = -1;
  2345     return NS_OK;
  2348   *_index = bookmark.position;
  2349   return NS_OK;
  2352 NS_IMETHODIMP
  2353 nsNavBookmarks::SetItemIndex(int64_t aItemId, int32_t aNewIndex)
  2355   NS_ENSURE_ARG_MIN(aItemId, 1);
  2356   NS_ENSURE_ARG_MIN(aNewIndex, 0);
  2358   BookmarkData bookmark;
  2359   nsresult rv = FetchItemInfo(aItemId, bookmark);
  2360   NS_ENSURE_SUCCESS(rv, rv);
  2362   // Ensure we are not going out of range.
  2363   int32_t folderCount;
  2364   int64_t grandParentId;
  2365   nsAutoCString folderGuid;
  2366   rv = FetchFolderInfo(bookmark.parentId, &folderCount, folderGuid, &grandParentId);
  2367   NS_ENSURE_SUCCESS(rv, rv);
  2368   NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG);
  2369   // Check the parent's guid is the expected one.
  2370   MOZ_ASSERT(bookmark.parentGuid == folderGuid);
  2372   BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  2374   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  2375     "UPDATE moz_bookmarks SET position = :item_index WHERE id = :item_id"
  2376   );
  2377   NS_ENSURE_STATE(stmt);
  2378   mozStorageStatementScoper scoper(stmt);
  2380   rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
  2381   NS_ENSURE_SUCCESS(rv, rv);
  2382   rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aNewIndex);
  2383   NS_ENSURE_SUCCESS(rv, rv);
  2385   rv = stmt->Execute();
  2386   NS_ENSURE_SUCCESS(rv, rv);
  2388   END_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
  2390   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2391                    nsINavBookmarkObserver,
  2392                    OnItemMoved(bookmark.id,
  2393                                bookmark.parentId,
  2394                                bookmark.position,
  2395                                bookmark.parentId,
  2396                                aNewIndex,
  2397                                bookmark.type,
  2398                                bookmark.guid,
  2399                                bookmark.parentGuid,
  2400                                bookmark.parentGuid));
  2402   return NS_OK;
  2406 nsresult
  2407 nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(int64_t aItemId)
  2409   nsAutoString keyword;
  2410   if (NS_SUCCEEDED(GetKeywordForBookmark(aItemId, keyword)) &&
  2411       !keyword.IsEmpty()) {
  2412     nsresult rv = EnsureKeywordsHash();
  2413     NS_ENSURE_SUCCESS(rv, rv);
  2414     mBookmarkToKeywordHash.Remove(aItemId);
  2416     // If the keyword is unused, remove it from the database.
  2417     keywordSearchData searchData;
  2418     searchData.keyword.Assign(keyword);
  2419     searchData.itemId = -1;
  2420     mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
  2421     if (searchData.itemId == -1) {
  2422       nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
  2423         "DELETE FROM moz_keywords "
  2424         "WHERE keyword = :keyword "
  2425         "AND NOT EXISTS ( "
  2426           "SELECT id "
  2427           "FROM moz_bookmarks "
  2428           "WHERE keyword_id = moz_keywords.id "
  2429         ")"
  2430       );
  2431       NS_ENSURE_STATE(stmt);
  2433       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
  2434       NS_ENSURE_SUCCESS(rv, rv);
  2435       nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
  2436       rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
  2437       NS_ENSURE_SUCCESS(rv, rv);
  2440   return NS_OK;
  2444 NS_IMETHODIMP
  2445 nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
  2446                                       const nsAString& aUserCasedKeyword)
  2448   NS_ENSURE_ARG_MIN(aBookmarkId, 1);
  2450   // This also ensures the bookmark is valid.
  2451   BookmarkData bookmark;
  2452   nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
  2453   NS_ENSURE_SUCCESS(rv, rv);
  2455   rv = EnsureKeywordsHash();
  2456   NS_ENSURE_SUCCESS(rv, rv);
  2458   // Shortcuts are always lowercased internally.
  2459   nsAutoString keyword(aUserCasedKeyword);
  2460   ToLowerCase(keyword);
  2462   // Check if bookmark was already associated to a keyword.
  2463   nsAutoString oldKeyword;
  2464   rv = GetKeywordForBookmark(bookmark.id, oldKeyword);
  2465   NS_ENSURE_SUCCESS(rv, rv);
  2467   // Trying to set the same value or to remove a nonexistent keyword is a no-op.
  2468   if (keyword.Equals(oldKeyword) || (keyword.IsEmpty() && oldKeyword.IsEmpty()))
  2469     return NS_OK;
  2471   mozStorageTransaction transaction(mDB->MainConn(), false);
  2473   nsCOMPtr<mozIStorageStatement> updateBookmarkStmt = mDB->GetStatement(
  2474     "UPDATE moz_bookmarks "
  2475     "SET keyword_id = (SELECT id FROM moz_keywords WHERE keyword = :keyword), "
  2476         "lastModified = :date "
  2477     "WHERE id = :item_id "
  2478   );
  2479   NS_ENSURE_STATE(updateBookmarkStmt);
  2480   mozStorageStatementScoper updateBookmarkScoper(updateBookmarkStmt);
  2482   if (keyword.IsEmpty()) {
  2483     // Remove keyword association from the hash.
  2484     mBookmarkToKeywordHash.Remove(bookmark.id);
  2485     rv = updateBookmarkStmt->BindNullByName(NS_LITERAL_CSTRING("keyword"));
  2487    else {
  2488     // We are associating bookmark to a new keyword. Create a new keyword
  2489     // record if needed.
  2490     nsCOMPtr<mozIStorageStatement> newKeywordStmt = mDB->GetStatement(
  2491       "INSERT OR IGNORE INTO moz_keywords (keyword) VALUES (:keyword)"
  2492     );
  2493     NS_ENSURE_STATE(newKeywordStmt);
  2494     mozStorageStatementScoper newKeywordScoper(newKeywordStmt);
  2496     rv = newKeywordStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"),
  2497                                           keyword);
  2498     NS_ENSURE_SUCCESS(rv, rv);
  2499     rv = newKeywordStmt->Execute();
  2500     NS_ENSURE_SUCCESS(rv, rv);
  2502     // Add new keyword association to the hash, removing the old one if needed.
  2503     if (!oldKeyword.IsEmpty())
  2504       mBookmarkToKeywordHash.Remove(bookmark.id);
  2505     mBookmarkToKeywordHash.Put(bookmark.id, keyword);
  2506     rv = updateBookmarkStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
  2508   NS_ENSURE_SUCCESS(rv, rv);
  2509   bookmark.lastModified = PR_Now();
  2510   rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"),
  2511                                            bookmark.lastModified);
  2512   NS_ENSURE_SUCCESS(rv, rv);
  2513   rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
  2514                                            bookmark.id);
  2515   NS_ENSURE_SUCCESS(rv, rv);
  2516   rv = updateBookmarkStmt->Execute();
  2517   NS_ENSURE_SUCCESS(rv, rv);
  2519   rv = transaction.Commit();
  2520   NS_ENSURE_SUCCESS(rv, rv);
  2522   // Update the cache entry, if needed.
  2523   BookmarkKeyClass* key = mRecentBookmarksCache.GetEntry(aBookmarkId);
  2524   if (key) {
  2525     key->bookmark.lastModified = bookmark.lastModified;
  2528   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2529                    nsINavBookmarkObserver,
  2530                    OnItemChanged(bookmark.id,
  2531                                  NS_LITERAL_CSTRING("keyword"),
  2532                                  false,
  2533                                  NS_ConvertUTF16toUTF8(keyword),
  2534                                  bookmark.lastModified,
  2535                                  bookmark.type,
  2536                                  bookmark.parentId,
  2537                                  bookmark.guid,
  2538                                  bookmark.parentGuid));
  2540   return NS_OK;
  2544 NS_IMETHODIMP
  2545 nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword)
  2547   NS_ENSURE_ARG(aURI);
  2548   aKeyword.Truncate(0);
  2550   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
  2551     "SELECT k.keyword "
  2552     "FROM moz_places h "
  2553     "JOIN moz_bookmarks b ON b.fk = h.id "
  2554     "JOIN moz_keywords k ON k.id = b.keyword_id "
  2555     "WHERE h.url = :page_url "
  2556   );
  2557   NS_ENSURE_STATE(stmt);
  2558   mozStorageStatementScoper scoper(stmt);
  2560   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
  2561   NS_ENSURE_SUCCESS(rv, rv);
  2563   bool hasMore = false;
  2564   rv = stmt->ExecuteStep(&hasMore);
  2565   if (NS_FAILED(rv) || !hasMore) {
  2566     aKeyword.SetIsVoid(true);
  2567     return NS_OK; // not found: return void keyword string
  2570   // found, get the keyword
  2571   rv = stmt->GetString(0, aKeyword);
  2572   NS_ENSURE_SUCCESS(rv, rv);
  2573   return NS_OK;
  2577 NS_IMETHODIMP
  2578 nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
  2580   NS_ENSURE_ARG_MIN(aBookmarkId, 1);
  2581   aKeyword.Truncate(0);
  2583   nsresult rv = EnsureKeywordsHash();
  2584   NS_ENSURE_SUCCESS(rv, rv);
  2586   nsAutoString keyword;
  2587   if (!mBookmarkToKeywordHash.Get(aBookmarkId, &keyword)) {
  2588     aKeyword.SetIsVoid(true);
  2590   else {
  2591     aKeyword.Assign(keyword);
  2594   return NS_OK;
  2598 NS_IMETHODIMP
  2599 nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
  2600                                  nsIURI** aURI)
  2602   NS_ENSURE_ARG_POINTER(aURI);
  2603   NS_ENSURE_TRUE(!aUserCasedKeyword.IsEmpty(), NS_ERROR_INVALID_ARG);
  2604   *aURI = nullptr;
  2606   // Shortcuts are always lowercased internally.
  2607   nsAutoString keyword(aUserCasedKeyword);
  2608   ToLowerCase(keyword);
  2610   nsresult rv = EnsureKeywordsHash();
  2611   NS_ENSURE_SUCCESS(rv, rv);
  2613   keywordSearchData searchData;
  2614   searchData.keyword.Assign(keyword);
  2615   searchData.itemId = -1;
  2616   mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
  2618   if (searchData.itemId == -1) {
  2619     // Not found.
  2620     return NS_OK;
  2623   rv = GetBookmarkURI(searchData.itemId, aURI);
  2624   NS_ENSURE_SUCCESS(rv, rv);
  2626   return NS_OK;
  2630 nsresult
  2631 nsNavBookmarks::EnsureKeywordsHash() {
  2632   if (mBookmarkToKeywordHashInitialized) {
  2633     return NS_OK;
  2635   mBookmarkToKeywordHashInitialized = true;
  2637   nsCOMPtr<mozIStorageStatement> stmt;
  2638   nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
  2639     "SELECT b.id, k.keyword "
  2640     "FROM moz_bookmarks b "
  2641     "JOIN moz_keywords k ON k.id = b.keyword_id "
  2642   ), getter_AddRefs(stmt));
  2643   NS_ENSURE_SUCCESS(rv, rv);
  2645   bool hasMore;
  2646   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
  2647     int64_t itemId;
  2648     rv = stmt->GetInt64(0, &itemId);
  2649     NS_ENSURE_SUCCESS(rv, rv);
  2650     nsAutoString keyword;
  2651     rv = stmt->GetString(1, keyword);
  2652     NS_ENSURE_SUCCESS(rv, rv);
  2654     mBookmarkToKeywordHash.Put(itemId, keyword);
  2657   return NS_OK;
  2661 NS_IMETHODIMP
  2662 nsNavBookmarks::RunInBatchMode(nsINavHistoryBatchCallback* aCallback,
  2663                                nsISupports* aUserData) {
  2664   PROFILER_LABEL("bookmarks", "RunInBatchMode");
  2665   NS_ENSURE_ARG(aCallback);
  2667   mBatching = true;
  2669   // Just forward the request to history.  History service must exist for
  2670   // bookmarks to work and we are observing it, thus batch notifications will be
  2671   // forwarded to bookmarks observers.
  2672   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2673   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2674   nsresult rv = history->RunInBatchMode(aCallback, aUserData);
  2675   NS_ENSURE_SUCCESS(rv, rv);
  2677   return NS_OK;
  2681 NS_IMETHODIMP
  2682 nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
  2683                             bool aOwnsWeak)
  2685   NS_ENSURE_ARG(aObserver);
  2686   return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
  2690 NS_IMETHODIMP
  2691 nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
  2693   return mObservers.RemoveWeakElement(aObserver);
  2696 void
  2697 nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
  2699   nsCOMPtr<nsIURI> uri;
  2700   (void)NS_NewURI(getter_AddRefs(uri), aData.bookmark.url);
  2701   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2702                    nsINavBookmarkObserver,
  2703                    OnItemVisited(aData.bookmark.id,
  2704                                  aData.visitId,
  2705                                  aData.time,
  2706                                  aData.transitionType,
  2707                                  uri,
  2708                                  aData.bookmark.parentId,
  2709                                  aData.bookmark.guid,
  2710                                  aData.bookmark.parentGuid));
  2713 void
  2714 nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
  2716   // A guid must always be defined.
  2717   MOZ_ASSERT(!aData.bookmark.guid.IsEmpty());
  2718   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2719                    nsINavBookmarkObserver,
  2720                    OnItemChanged(aData.bookmark.id,
  2721                                  aData.property,
  2722                                  aData.isAnnotation,
  2723                                  aData.newValue,
  2724                                  aData.bookmark.lastModified,
  2725                                  aData.bookmark.type,
  2726                                  aData.bookmark.parentId,
  2727                                  aData.bookmark.guid,
  2728                                  aData.bookmark.parentGuid));
  2731 ////////////////////////////////////////////////////////////////////////////////
  2732 //// nsIObserver
  2734 NS_IMETHODIMP
  2735 nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
  2736                         const char16_t *aData)
  2738   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
  2740   if (strcmp(aTopic, TOPIC_PLACES_MAINTENANCE) == 0) {
  2741     // Maintenance can execute direct writes to the database, thus clear all
  2742     // the cached bookmarks.
  2743     mRecentBookmarksCache.Clear();
  2745   else if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
  2746     // Stop Observing annotations.
  2747     nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
  2748     if (annosvc) {
  2749       annosvc->RemoveObserver(this);
  2752   else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
  2753     // Don't even try to notify observers from this point on, the category
  2754     // cache would init services that could try to use our APIs.
  2755     mCanNotify = false;
  2758   return NS_OK;
  2761 ////////////////////////////////////////////////////////////////////////////////
  2762 //// nsINavHistoryObserver
  2764 NS_IMETHODIMP
  2765 nsNavBookmarks::OnBeginUpdateBatch()
  2767   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2768                    nsINavBookmarkObserver, OnBeginUpdateBatch());
  2769   return NS_OK;
  2773 NS_IMETHODIMP
  2774 nsNavBookmarks::OnEndUpdateBatch()
  2776   if (mBatching) {
  2777     mBatching = false;
  2780   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2781                    nsINavBookmarkObserver, OnEndUpdateBatch());
  2782   return NS_OK;
  2786 NS_IMETHODIMP
  2787 nsNavBookmarks::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
  2788                         int64_t aSessionID, int64_t aReferringID,
  2789                         uint32_t aTransitionType, const nsACString& aGUID,
  2790                         bool aHidden)
  2792   // If the page is bookmarked, notify observers for each associated bookmark.
  2793   ItemVisitData visitData;
  2794   nsresult rv = aURI->GetSpec(visitData.bookmark.url);
  2795   NS_ENSURE_SUCCESS(rv, rv);
  2796   visitData.visitId = aVisitId;
  2797   visitData.time = aTime;
  2798   visitData.transitionType = aTransitionType;
  2800   nsRefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
  2801     new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
  2802   notifier->Init();
  2803   return NS_OK;
  2807 NS_IMETHODIMP
  2808 nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
  2809                             const nsACString& aGUID,
  2810                             uint16_t aReason)
  2812 #ifdef DEBUG
  2813   nsNavHistory* history = nsNavHistory::GetHistoryService();
  2814   int64_t placeId;
  2815   nsAutoCString placeGuid;
  2816   NS_ABORT_IF_FALSE(
  2817     history && NS_SUCCEEDED(history->GetIdForPage(aURI, &placeId, placeGuid)) && !placeId,
  2818     "OnDeleteURI was notified for a page that still exists?"
  2819   );
  2820 #endif
  2821   return NS_OK;
  2825 NS_IMETHODIMP
  2826 nsNavBookmarks::OnClearHistory()
  2828   // TODO(bryner): we should notify on visited-time change for all URIs
  2829   return NS_OK;
  2833 NS_IMETHODIMP
  2834 nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
  2835                                const nsAString& aPageTitle,
  2836                                const nsACString& aGUID)
  2838   // NOOP. We don't consume page titles from moz_places anymore.
  2839   // Title-change notifications are sent from SetItemTitle.
  2840   return NS_OK;
  2844 NS_IMETHODIMP
  2845 nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
  2846                                   int32_t aNewFrecency,
  2847                                   const nsACString& aGUID,
  2848                                   bool aHidden,
  2849                                   PRTime aLastVisitDate)
  2851   return NS_OK;
  2855 NS_IMETHODIMP
  2856 nsNavBookmarks::OnManyFrecenciesChanged()
  2858   return NS_OK;
  2862 NS_IMETHODIMP
  2863 nsNavBookmarks::OnPageChanged(nsIURI* aURI,
  2864                               uint32_t aChangedAttribute,
  2865                               const nsAString& aNewValue,
  2866                               const nsACString& aGUID)
  2868   nsresult rv;
  2869   if (aChangedAttribute == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
  2870     ItemChangeData changeData;
  2871     rv = aURI->GetSpec(changeData.bookmark.url);
  2872     NS_ENSURE_SUCCESS(rv, rv);
  2873     changeData.property = NS_LITERAL_CSTRING("favicon");
  2874     changeData.isAnnotation = false;
  2875     changeData.newValue = NS_ConvertUTF16toUTF8(aNewValue);
  2876     changeData.bookmark.lastModified = 0;
  2877     changeData.bookmark.type = TYPE_BOOKMARK;
  2879     // Favicons may be set to either pure URIs or to folder URIs
  2880     bool isPlaceURI;
  2881     rv = aURI->SchemeIs("place", &isPlaceURI);
  2882     NS_ENSURE_SUCCESS(rv, rv);
  2883     if (isPlaceURI) {
  2884       nsNavHistory* history = nsNavHistory::GetHistoryService();
  2885       NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
  2887       nsCOMArray<nsNavHistoryQuery> queries;
  2888       nsCOMPtr<nsNavHistoryQueryOptions> options;
  2889       rv = history->QueryStringToQueryArray(changeData.bookmark.url,
  2890                                             &queries, getter_AddRefs(options));
  2891       NS_ENSURE_SUCCESS(rv, rv);
  2893       if (queries.Count() == 1 && queries[0]->Folders().Length() == 1) {
  2894         // Fetch missing data.
  2895         rv = FetchItemInfo(queries[0]->Folders()[0], changeData.bookmark);
  2896         NS_ENSURE_SUCCESS(rv, rv);
  2897         NotifyItemChanged(changeData);
  2900     else {
  2901       nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
  2902         new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
  2903       notifier->Init();
  2906   return NS_OK;
  2910 NS_IMETHODIMP
  2911 nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime,
  2912                                const nsACString& aGUID,
  2913                                uint16_t aReason, uint32_t aTransitionType)
  2915   // Notify "cleartime" only if all visits to the page have been removed.
  2916   if (!aVisitTime) {
  2917     // If the page is bookmarked, notify observers for each associated bookmark.
  2918     ItemChangeData changeData;
  2919     nsresult rv = aURI->GetSpec(changeData.bookmark.url);
  2920     NS_ENSURE_SUCCESS(rv, rv);
  2921     changeData.property = NS_LITERAL_CSTRING("cleartime");
  2922     changeData.isAnnotation = false;
  2923     changeData.bookmark.lastModified = 0;
  2924     changeData.bookmark.type = TYPE_BOOKMARK;
  2926     nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
  2927       new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
  2928     notifier->Init();
  2930   return NS_OK;
  2934 // nsIAnnotationObserver
  2936 NS_IMETHODIMP
  2937 nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
  2939   return NS_OK;
  2943 NS_IMETHODIMP
  2944 nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName)
  2946   BookmarkData bookmark;
  2947   nsresult rv = FetchItemInfo(aItemId, bookmark);
  2948   NS_ENSURE_SUCCESS(rv, rv);
  2950   bookmark.lastModified = PR_Now();
  2951   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.id, bookmark.lastModified);
  2952   NS_ENSURE_SUCCESS(rv, rv);
  2954   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
  2955                    nsINavBookmarkObserver,
  2956                    OnItemChanged(bookmark.id,
  2957                                  aName,
  2958                                  true,
  2959                                  EmptyCString(),
  2960                                  bookmark.lastModified,
  2961                                  bookmark.type,
  2962                                  bookmark.parentId,
  2963                                  bookmark.guid,
  2964                                  bookmark.parentGuid));
  2965   return NS_OK;
  2969 NS_IMETHODIMP
  2970 nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
  2972   return NS_OK;
  2976 NS_IMETHODIMP
  2977 nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName)
  2979   // As of now this is doing the same as OnItemAnnotationSet, so just forward
  2980   // the call.
  2981   nsresult rv = OnItemAnnotationSet(aItemId, aName);
  2982   NS_ENSURE_SUCCESS(rv, rv);
  2983   return NS_OK;

mercurial