Sat, 03 Jan 2015 20:18:00 +0100
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);
1004 }
1005 }
1006 return NS_OK;
1007 }
1009 NS_IMPL_ISUPPORTS(nsNavBookmarks::RemoveFolderTransaction, nsITransaction)
1011 NS_IMETHODIMP
1012 nsNavBookmarks::GetRemoveFolderTransaction(int64_t aFolderId, nsITransaction** aResult)
1013 {
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;
1027 }
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();
1036 {
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);
1057 }
1058 }
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);
1066 }
1068 return NS_OK;
1069 }
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;
1080 {
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);
1124 }
1126 // Append item to children's array.
1127 aFolderChildrenArray.AppendElement(child);
1128 }
1129 }
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);
1146 }
1147 }
1149 return NS_OK;
1150 }
1153 NS_IMETHODIMP
1154 nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
1155 {
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);
1179 }
1181 BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
1182 }
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);
1223 }
1225 rv = UpdateKeywordsHashForRemovedBookmark(child.id);
1226 NS_ENSURE_SUCCESS(rv, rv);
1227 }
1228 END_CRITICAL_BOOKMARK_CACHE_SECTION(child.id);
1229 }
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);
1241 }
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));
1274 }
1275 }
1276 }
1278 return NS_OK;
1279 }
1282 NS_IMETHODIMP
1283 nsNavBookmarks::MoveItem(int64_t aItemId, int64_t aNewParent, int32_t aIndex)
1284 {
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;
1311 }
1312 rv = GetFolderIdForItem(ancestorId, &ancestorId);
1313 if (NS_FAILED(rv)) {
1314 break;
1315 }
1316 }
1317 }
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;
1332 }
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;
1341 }
1342 }
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;
1350 }
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);
1361 }
1362 else {
1363 rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, newIndex, -1);
1364 }
1365 NS_ENSURE_SUCCESS(rv, rv);
1366 }
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);
1375 }
1377 BEGIN_CRITICAL_BOOKMARK_CACHE_SECTION(bookmark.id);
1379 {
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);
1396 }
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;
1421 }
1423 nsresult
1424 nsNavBookmarks::FetchItemInfo(int64_t aItemId,
1425 BookmarkData& _bookmark)
1426 {
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;
1433 }
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;
1455 }
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);
1465 }
1466 else {
1467 rv = stmt->GetUTF8String(2, _bookmark.title);
1468 NS_ENSURE_SUCCESS(rv, rv);
1469 }
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);
1492 }
1493 else {
1494 _bookmark.grandParentId = -1;
1495 }
1497 ADD_TO_BOOKMARK_CACHE(aItemId, _bookmark);
1499 return NS_OK;
1500 }
1502 nsresult
1503 nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
1504 int64_t aItemId,
1505 PRTime aValue)
1506 {
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 );
1516 }
1517 else {
1518 stmt = mDB->GetStatement(
1519 "UPDATE moz_bookmarks SET lastModified = :date WHERE id = :item_id"
1520 );
1521 }
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;
1538 }
1539 // Set lastModified in both cases.
1540 key->bookmark.lastModified = aValue;
1541 }
1543 // note, we are not notifying the observers
1544 // that the item has changed.
1546 return NS_OK;
1547 }
1550 NS_IMETHODIMP
1551 nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded)
1552 {
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;
1576 }
1579 NS_IMETHODIMP
1580 nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
1581 {
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;
1591 }
1594 NS_IMETHODIMP
1595 nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified)
1596 {
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;
1620 }
1623 NS_IMETHODIMP
1624 nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
1625 {
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;
1635 }
1638 NS_IMETHODIMP
1639 nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle)
1640 {
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"));
1660 }
1661 else {
1662 rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
1663 title);
1664 }
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);
1681 }
1682 else {
1683 key->bookmark.title.Assign(title);
1684 }
1685 key->bookmark.lastModified = bookmark.lastModified;
1686 }
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;
1700 }
1703 NS_IMETHODIMP
1704 nsNavBookmarks::GetItemTitle(int64_t aItemId,
1705 nsACString& _title)
1706 {
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;
1715 }
1718 NS_IMETHODIMP
1719 nsNavBookmarks::GetBookmarkURI(int64_t aItemId,
1720 nsIURI** _URI)
1721 {
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;
1733 }
1736 NS_IMETHODIMP
1737 nsNavBookmarks::GetItemType(int64_t aItemId, uint16_t* _type)
1738 {
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;
1748 }
1751 nsresult
1752 nsNavBookmarks::ResultNodeForContainer(int64_t aItemId,
1753 nsNavHistoryQueryOptions* aOptions,
1754 nsNavHistoryResultNode** aNode)
1755 {
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);
1764 }
1765 else {
1766 return NS_ERROR_INVALID_ARG;
1767 }
1769 (*aNode)->mDateAdded = bookmark.dateAdded;
1770 (*aNode)->mLastModified = bookmark.lastModified;
1771 (*aNode)->mBookmarkGuid = bookmark.guid;
1773 NS_ADDREF(*aNode);
1774 return NS_OK;
1775 }
1778 nsresult
1779 nsNavBookmarks::QueryFolderChildren(
1780 int64_t aFolderId,
1781 nsNavHistoryQueryOptions* aOptions,
1782 nsCOMArray<nsNavHistoryResultNode>* aChildren)
1783 {
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);
1817 }
1819 return NS_OK;
1820 }
1823 nsresult
1824 nsNavBookmarks::ProcessFolderNodeRow(
1825 mozIStorageValueArray* aRow,
1826 nsNavHistoryQueryOptions* aOptions,
1827 nsCOMArray<nsNavHistoryResultNode>* aChildren,
1828 int32_t& aCurrentIndex)
1829 {
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;
1862 }
1863 }
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;
1871 }
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);
1885 }
1886 else {
1887 // This is a separator.
1888 if (aOptions->ExcludeItems()) {
1889 return NS_OK;
1890 }
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);
1900 }
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;
1912 }
1915 nsresult
1916 nsNavBookmarks::QueryFolderChildrenAsync(
1917 nsNavHistoryFolderResultNode* aNode,
1918 int64_t aFolderId,
1919 mozIStoragePendingStatement** _pendingStmt)
1920 {
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;
1951 }
1954 nsresult
1955 nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
1956 int32_t* _folderCount,
1957 nsACString& _guid,
1958 int64_t* _parentId)
1959 {
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);
1997 }
1999 return NS_OK;
2000 }
2003 NS_IMETHODIMP
2004 nsNavBookmarks::IsBookmarked(nsIURI* aURI, bool* aBookmarked)
2005 {
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;
2023 }
2026 NS_IMETHODIMP
2027 nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
2028 {
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;
2043 }
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);
2107 }
2109 // If there is no bookmarked origin, we will just return null.
2110 return NS_OK;
2111 }
2114 NS_IMETHODIMP
2115 nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI)
2116 {
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;
2185 }
2188 NS_IMETHODIMP
2189 nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
2190 {
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;
2203 }
2206 nsresult
2207 nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
2208 nsTArray<int64_t>& aResult,
2209 bool aSkipTags)
2210 {
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;
2238 }
2239 }
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);
2244 }
2245 NS_ENSURE_SUCCESS(rv, rv);
2247 return NS_OK;
2248 }
2250 nsresult
2251 nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
2252 nsTArray<BookmarkData>& aBookmarks)
2253 {
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;
2281 }
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);
2297 }
2299 return NS_OK;
2300 }
2302 NS_IMETHODIMP
2303 nsNavBookmarks::GetBookmarkIdsForURI(nsIURI* aURI, uint32_t* aCount,
2304 int64_t** aBookmarks)
2305 {
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];
2327 }
2329 *aCount = bookmarks.Length();
2330 return NS_OK;
2331 }
2334 NS_IMETHODIMP
2335 nsNavBookmarks::GetItemIndex(int64_t aItemId, int32_t* _index)
2336 {
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;
2346 }
2348 *_index = bookmark.position;
2349 return NS_OK;
2350 }
2352 NS_IMETHODIMP
2353 nsNavBookmarks::SetItemIndex(int64_t aItemId, int32_t aNewIndex)
2354 {
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;
2403 }
2406 nsresult
2407 nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(int64_t aItemId)
2408 {
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);
2438 }
2439 }
2440 return NS_OK;
2441 }
2444 NS_IMETHODIMP
2445 nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
2446 const nsAString& aUserCasedKeyword)
2447 {
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"));
2486 }
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);
2507 }
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;
2526 }
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;
2541 }
2544 NS_IMETHODIMP
2545 nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword)
2546 {
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
2568 }
2570 // found, get the keyword
2571 rv = stmt->GetString(0, aKeyword);
2572 NS_ENSURE_SUCCESS(rv, rv);
2573 return NS_OK;
2574 }
2577 NS_IMETHODIMP
2578 nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
2579 {
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);
2589 }
2590 else {
2591 aKeyword.Assign(keyword);
2592 }
2594 return NS_OK;
2595 }
2598 NS_IMETHODIMP
2599 nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
2600 nsIURI** aURI)
2601 {
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;
2621 }
2623 rv = GetBookmarkURI(searchData.itemId, aURI);
2624 NS_ENSURE_SUCCESS(rv, rv);
2626 return NS_OK;
2627 }
2630 nsresult
2631 nsNavBookmarks::EnsureKeywordsHash() {
2632 if (mBookmarkToKeywordHashInitialized) {
2633 return NS_OK;
2634 }
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);
2655 }
2657 return NS_OK;
2658 }
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;
2678 }
2681 NS_IMETHODIMP
2682 nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
2683 bool aOwnsWeak)
2684 {
2685 NS_ENSURE_ARG(aObserver);
2686 return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
2687 }
2690 NS_IMETHODIMP
2691 nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
2692 {
2693 return mObservers.RemoveWeakElement(aObserver);
2694 }
2696 void
2697 nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
2698 {
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));
2711 }
2713 void
2714 nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
2715 {
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));
2729 }
2731 ////////////////////////////////////////////////////////////////////////////////
2732 //// nsIObserver
2734 NS_IMETHODIMP
2735 nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
2736 const char16_t *aData)
2737 {
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();
2744 }
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);
2750 }
2751 }
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;
2756 }
2758 return NS_OK;
2759 }
2761 ////////////////////////////////////////////////////////////////////////////////
2762 //// nsINavHistoryObserver
2764 NS_IMETHODIMP
2765 nsNavBookmarks::OnBeginUpdateBatch()
2766 {
2767 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2768 nsINavBookmarkObserver, OnBeginUpdateBatch());
2769 return NS_OK;
2770 }
2773 NS_IMETHODIMP
2774 nsNavBookmarks::OnEndUpdateBatch()
2775 {
2776 if (mBatching) {
2777 mBatching = false;
2778 }
2780 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
2781 nsINavBookmarkObserver, OnEndUpdateBatch());
2782 return NS_OK;
2783 }
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)
2791 {
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;
2804 }
2807 NS_IMETHODIMP
2808 nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
2809 const nsACString& aGUID,
2810 uint16_t aReason)
2811 {
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;
2822 }
2825 NS_IMETHODIMP
2826 nsNavBookmarks::OnClearHistory()
2827 {
2828 // TODO(bryner): we should notify on visited-time change for all URIs
2829 return NS_OK;
2830 }
2833 NS_IMETHODIMP
2834 nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
2835 const nsAString& aPageTitle,
2836 const nsACString& aGUID)
2837 {
2838 // NOOP. We don't consume page titles from moz_places anymore.
2839 // Title-change notifications are sent from SetItemTitle.
2840 return NS_OK;
2841 }
2844 NS_IMETHODIMP
2845 nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
2846 int32_t aNewFrecency,
2847 const nsACString& aGUID,
2848 bool aHidden,
2849 PRTime aLastVisitDate)
2850 {
2851 return NS_OK;
2852 }
2855 NS_IMETHODIMP
2856 nsNavBookmarks::OnManyFrecenciesChanged()
2857 {
2858 return NS_OK;
2859 }
2862 NS_IMETHODIMP
2863 nsNavBookmarks::OnPageChanged(nsIURI* aURI,
2864 uint32_t aChangedAttribute,
2865 const nsAString& aNewValue,
2866 const nsACString& aGUID)
2867 {
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);
2898 }
2899 }
2900 else {
2901 nsRefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
2902 new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
2903 notifier->Init();
2904 }
2905 }
2906 return NS_OK;
2907 }
2910 NS_IMETHODIMP
2911 nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime,
2912 const nsACString& aGUID,
2913 uint16_t aReason, uint32_t aTransitionType)
2914 {
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();
2929 }
2930 return NS_OK;
2931 }
2934 // nsIAnnotationObserver
2936 NS_IMETHODIMP
2937 nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
2938 {
2939 return NS_OK;
2940 }
2943 NS_IMETHODIMP
2944 nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName)
2945 {
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;
2966 }
2969 NS_IMETHODIMP
2970 nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
2971 {
2972 return NS_OK;
2973 }
2976 NS_IMETHODIMP
2977 nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName)
2978 {
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;
2984 }