Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/places/nsNavBookmarks.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
5
6
#include "nsNavBookmarks.h"
7
8
#include "nsNavHistory.h"
9
#include "nsAnnotationService.h"
10
#include "nsPlacesMacros.h"
11
#include "Helpers.h"
12
13
#include "nsAppDirectoryServiceDefs.h"
14
#include "nsNetUtil.h"
15
#include "nsUnicharUtils.h"
16
#include "nsPrintfCString.h"
17
#include "nsQueryObject.h"
18
#include "mozilla/Preferences.h"
19
#include "mozilla/storage.h"
20
#include "mozilla/dom/PlacesObservers.h"
21
#include "mozilla/dom/PlacesVisit.h"
22
23
#include "GeckoProfiler.h"
24
25
using namespace mozilla;
26
27
// These columns sit to the right of the kGetInfoIndex_* columns.
28
const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
29
const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
30
const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
31
const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
32
const int32_t nsNavBookmarks::kGetChildrenIndex_SyncStatus = 22;
33
34
using namespace mozilla::places;
35
36
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
37
38
#define BOOKMARKS_ANNO_PREFIX "bookmarks/"
39
#define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
40
0
#define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
41
#define SYNC_PARENT_ANNO "sync/parent"
42
0
#define SQLITE_MAX_VARIABLE_NUMBER 999
43
44
45
namespace {
46
47
#define SKIP_TAGS(condition) ((condition) ? SkipTags : DontSkip)
48
49
0
bool DontSkip(nsCOMPtr<nsINavBookmarkObserver> obs) { return false; }
50
0
bool SkipTags(nsCOMPtr<nsINavBookmarkObserver> obs) {
51
0
  bool skipTags = false;
52
0
  (void) obs->GetSkipTags(&skipTags);
53
0
  return skipTags;
54
0
}
55
0
bool SkipDescendants(nsCOMPtr<nsINavBookmarkObserver> obs) {
56
0
  bool skipDescendantsOnItemRemoval = false;
57
0
  (void) obs->GetSkipDescendantsOnItemRemoval(&skipDescendantsOnItemRemoval);
58
0
  return skipDescendantsOnItemRemoval;
59
0
}
60
61
template<typename Method, typename DataType>
62
class AsyncGetBookmarksForURI : public AsyncStatementCallback
63
{
64
public:
65
  AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
66
                          Method aCallback,
67
                          const DataType& aData)
68
  : mBookmarksSvc(aBookmarksSvc)
69
  , mCallback(aCallback)
70
  , mData(aData)
71
0
  {
72
0
  }
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemVisitData const&), mozilla::places::ItemVisitData>::AsyncGetBookmarksForURI(nsNavBookmarks*, void (nsNavBookmarks::*)(mozilla::places::ItemVisitData const&), mozilla::places::ItemVisitData const&)
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemChangeData const&), mozilla::places::ItemChangeData>::AsyncGetBookmarksForURI(nsNavBookmarks*, void (nsNavBookmarks::*)(mozilla::places::ItemChangeData const&), mozilla::places::ItemChangeData const&)
73
74
  void Init()
75
0
  {
76
0
    RefPtr<Database> DB = Database::GetDatabase();
77
0
    if (DB) {
78
0
      nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
79
0
        "/* do not warn (bug 1175249) */ "
80
0
        "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
81
0
        "FROM moz_bookmarks b "
82
0
        "JOIN moz_bookmarks t on t.id = b.parent "
83
0
        "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
84
0
        "ORDER BY b.lastModified DESC, b.id DESC "
85
0
      );
86
0
      if (stmt) {
87
0
        (void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
88
0
                              mData.bookmark.url);
89
0
        nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
90
0
        (void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
91
0
      }
92
0
    }
93
0
  }
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemVisitData const&), mozilla::places::ItemVisitData>::Init()
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemChangeData const&), mozilla::places::ItemChangeData>::Init()
94
95
  NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override
96
0
  {
97
0
    nsCOMPtr<mozIStorageRow> row;
98
0
    while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
99
0
      // Skip tags, for the use-cases of this async getter they are useless.
100
0
      int64_t grandParentId = -1, tagsFolderId = -1;
101
0
      nsresult rv = row->GetInt64(5, &grandParentId);
102
0
      NS_ENSURE_SUCCESS(rv, rv);
103
0
      rv = mBookmarksSvc->GetTagsFolder(&tagsFolderId);
104
0
      NS_ENSURE_SUCCESS(rv, rv);
105
0
      if (grandParentId == tagsFolderId) {
106
0
        continue;
107
0
      }
108
0
109
0
      mData.bookmark.grandParentId = grandParentId;
110
0
      rv = row->GetInt64(0, &mData.bookmark.id);
111
0
      NS_ENSURE_SUCCESS(rv, rv);
112
0
      rv = row->GetUTF8String(1, mData.bookmark.guid);
113
0
      NS_ENSURE_SUCCESS(rv, rv);
114
0
      rv = row->GetInt64(2, &mData.bookmark.parentId);
115
0
      NS_ENSURE_SUCCESS(rv, rv);
116
0
      // lastModified (3) should not be set for the use-cases of this getter.
117
0
      rv = row->GetUTF8String(4, mData.bookmark.parentGuid);
118
0
      NS_ENSURE_SUCCESS(rv, rv);
119
0
120
0
      if (mCallback) {
121
0
        ((*mBookmarksSvc).*mCallback)(mData);
122
0
      }
123
0
    }
124
0
    return NS_OK;
125
0
  }
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemVisitData const&), mozilla::places::ItemVisitData>::HandleResult(mozIStorageResultSet*)
Unexecuted instantiation: Unified_cpp_components_places0.cpp:(anonymous namespace)::AsyncGetBookmarksForURI<void (nsNavBookmarks::*)(mozilla::places::ItemChangeData const&), mozilla::places::ItemChangeData>::HandleResult(mozIStorageResultSet*)
126
127
private:
128
  RefPtr<nsNavBookmarks> mBookmarksSvc;
129
  Method mCallback;
130
  DataType mData;
131
};
132
133
// Returns the sync change counter increment for a change source constant.
134
inline int64_t
135
0
DetermineSyncChangeDelta(uint16_t aSource) {
136
0
  return aSource == nsINavBookmarksService::SOURCE_SYNC ? 0 : 1;
137
0
}
138
139
// Returns the sync status for a new item inserted by a change source.
140
inline int32_t
141
0
DetermineInitialSyncStatus(uint16_t aSource) {
142
0
  if (aSource == nsINavBookmarksService::SOURCE_SYNC) {
143
0
    return nsINavBookmarksService::SYNC_STATUS_NORMAL;
144
0
  }
145
0
  if (aSource == nsINavBookmarksService::SOURCE_RESTORE_ON_STARTUP) {
146
0
    return nsINavBookmarksService::SYNC_STATUS_UNKNOWN;
147
0
  }
148
0
  return nsINavBookmarksService::SYNC_STATUS_NEW;
149
0
}
150
151
// Indicates whether an item has been uploaded to the server and
152
// needs a tombstone on deletion.
153
inline bool
154
0
NeedsTombstone(const BookmarkData& aBookmark) {
155
0
  return aBookmark.syncStatus == nsINavBookmarksService::SYNC_STATUS_NORMAL;
156
0
}
157
158
} // namespace
159
160
161
nsNavBookmarks::nsNavBookmarks()
162
  : mCanNotify(false)
163
0
{
164
0
  NS_ASSERTION(!gBookmarksService,
165
0
               "Attempting to create two instances of the service!");
166
0
  gBookmarksService = this;
167
0
}
168
169
170
nsNavBookmarks::~nsNavBookmarks()
171
0
{
172
0
  NS_ASSERTION(gBookmarksService == this,
173
0
               "Deleting a non-singleton instance of the service");
174
0
  if (gBookmarksService == this)
175
0
    gBookmarksService = nullptr;
176
0
}
177
178
179
NS_IMPL_ISUPPORTS(nsNavBookmarks
180
, nsINavBookmarksService
181
, nsINavHistoryObserver
182
, nsIObserver
183
, nsISupportsWeakReference
184
)
185
186
187
Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
188
189
190
void // static
191
nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
192
0
                                    const int64_t aLastInsertedId) {
193
0
  MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
194
0
  sLastInsertedItemId = aLastInsertedId;
195
0
}
196
197
198
Atomic<int64_t> nsNavBookmarks::sTotalSyncChanges(0);
199
200
201
void // static
202
nsNavBookmarks::NoteSyncChange()
203
0
{
204
0
  sTotalSyncChanges++;
205
0
}
206
207
208
nsresult
209
nsNavBookmarks::Init()
210
0
{
211
0
  mDB = Database::GetDatabase();
212
0
  NS_ENSURE_STATE(mDB);
213
0
214
0
  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
215
0
  if (os) {
216
0
    (void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
217
0
  }
218
0
219
0
  mCanNotify = true;
220
0
221
0
  // Allows us to notify on title changes. MUST BE LAST so it is impossible
222
0
  // to fail after this call, or the history service will have a reference to
223
0
  // us and we won't go away.
224
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
225
0
  NS_ENSURE_STATE(history);
226
0
  history->AddObserver(this, true);
227
0
  AutoTArray<PlacesEventType, 1> events;
228
0
  events.AppendElement(PlacesEventType::Page_visited);
229
0
  PlacesObservers::AddListener(events, this);
230
0
231
0
  // DO NOT PUT STUFF HERE that can fail. See observer comment above.
232
0
233
0
  return NS_OK;
234
0
}
235
236
nsresult
237
nsNavBookmarks::AdjustIndices(int64_t aFolderId,
238
                              int32_t aStartIndex,
239
                              int32_t aEndIndex,
240
                              int32_t aDelta)
241
0
{
242
0
  NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= INT32_MAX &&
243
0
               aStartIndex <= aEndIndex, "Bad indices");
244
0
245
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
246
0
    "UPDATE moz_bookmarks SET position = position + :delta "
247
0
      "WHERE parent = :parent "
248
0
        "AND position BETWEEN :from_index AND :to_index"
249
0
  );
250
0
  NS_ENSURE_STATE(stmt);
251
0
  mozStorageStatementScoper scoper(stmt);
252
0
253
0
  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
254
0
  NS_ENSURE_SUCCESS(rv, rv);
255
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
256
0
  NS_ENSURE_SUCCESS(rv, rv);
257
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
258
0
  NS_ENSURE_SUCCESS(rv, rv);
259
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
260
0
  NS_ENSURE_SUCCESS(rv, rv);
261
0
262
0
  rv = stmt->Execute();
263
0
  NS_ENSURE_SUCCESS(rv, rv);
264
0
265
0
  return NS_OK;
266
0
}
267
268
269
nsresult
270
nsNavBookmarks::AdjustSeparatorsSyncCounter(int64_t aFolderId,
271
                                            int32_t aStartIndex,
272
                                            int64_t aSyncChangeDelta)
273
0
{
274
0
  MOZ_ASSERT(aStartIndex >= 0, "Bad start position");
275
0
  if (!aSyncChangeDelta) {
276
0
    return NS_OK;
277
0
  }
278
0
279
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
280
0
    "UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + :delta "
281
0
      "WHERE parent = :parent AND position >= :start_index "
282
0
      "AND type = :item_type "
283
0
  );
284
0
  NS_ENSURE_STATE(stmt);
285
0
  mozStorageStatementScoper scoper(stmt);
286
0
287
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
288
0
  NS_ENSURE_SUCCESS(rv, rv);
289
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
290
0
  NS_ENSURE_SUCCESS(rv, rv);
291
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("start_index"), aStartIndex);
292
0
  NS_ENSURE_SUCCESS(rv, rv);
293
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_SEPARATOR);
294
0
  NS_ENSURE_SUCCESS(rv, rv);
295
0
296
0
  rv = stmt->Execute();
297
0
  NS_ENSURE_SUCCESS(rv, rv);
298
0
299
0
  return NS_OK;
300
0
}
301
302
303
NS_IMETHODIMP
304
nsNavBookmarks::GetPlacesRoot(int64_t* aRoot)
305
0
{
306
0
  int64_t id = mDB->GetRootFolderId();
307
0
  NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
308
0
  *aRoot = id;
309
0
  return NS_OK;
310
0
}
311
312
313
NS_IMETHODIMP
314
nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot)
315
0
{
316
0
  int64_t id = mDB->GetMenuFolderId();
317
0
  NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
318
0
  *aRoot = id;
319
0
  return NS_OK;
320
0
}
321
322
323
NS_IMETHODIMP
324
nsNavBookmarks::GetToolbarFolder(int64_t* aRoot)
325
0
{
326
0
  int64_t id = mDB->GetToolbarFolderId();
327
0
  NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
328
0
  *aRoot = id;
329
0
  return NS_OK;
330
0
}
331
332
333
NS_IMETHODIMP
334
nsNavBookmarks::GetTagsFolder(int64_t* aRoot)
335
0
{
336
0
  int64_t id = mDB->GetTagsFolderId();
337
0
  NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
338
0
  *aRoot = id;
339
0
  return NS_OK;
340
0
}
341
342
343
NS_IMETHODIMP
344
nsNavBookmarks::GetTotalSyncChanges(int64_t* aTotalSyncChanges)
345
0
{
346
0
  *aTotalSyncChanges = sTotalSyncChanges;
347
0
  return NS_OK;
348
0
}
349
350
351
nsresult
352
nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
353
                                   enum ItemType aItemType,
354
                                   int64_t aParentId,
355
                                   int32_t aIndex,
356
                                   const nsACString& aTitle,
357
                                   PRTime aDateAdded,
358
                                   PRTime aLastModified,
359
                                   const nsACString& aParentGuid,
360
                                   int64_t aGrandParentId,
361
                                   nsIURI* aURI,
362
                                   uint16_t aSource,
363
                                   int64_t* _itemId,
364
                                   nsACString& _guid)
365
0
{
366
0
  // Check for a valid itemId.
367
0
  MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
368
0
  // Check for a valid placeId.
369
0
  MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
370
0
371
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
372
0
    "INSERT INTO moz_bookmarks "
373
0
      "(id, fk, type, parent, position, title, "
374
0
       "dateAdded, lastModified, guid, syncStatus, syncChangeCounter) "
375
0
    "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
376
0
            ":item_title, :date_added, :last_modified, "
377
0
            ":item_guid, :sync_status, :change_counter)"
378
0
  );
379
0
  NS_ENSURE_STATE(stmt);
380
0
  mozStorageStatementScoper scoper(stmt);
381
0
382
0
  nsresult rv;
383
0
  if (*_itemId != -1)
384
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
385
0
  else
386
0
    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
387
0
  NS_ENSURE_SUCCESS(rv, rv);
388
0
389
0
  if (aPlaceId != -1)
390
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
391
0
  else
392
0
    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
393
0
  NS_ENSURE_SUCCESS(rv, rv);
394
0
395
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
396
0
  NS_ENSURE_SUCCESS(rv, rv);
397
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
398
0
  NS_ENSURE_SUCCESS(rv, rv);
399
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
400
0
  NS_ENSURE_SUCCESS(rv, rv);
401
0
402
0
  if (aTitle.IsEmpty())
403
0
    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
404
0
  else
405
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
406
0
  NS_ENSURE_SUCCESS(rv, rv);
407
0
408
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
409
0
  NS_ENSURE_SUCCESS(rv, rv);
410
0
411
0
  if (aLastModified) {
412
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
413
0
                               aLastModified);
414
0
  }
415
0
  else {
416
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
417
0
  }
418
0
  NS_ENSURE_SUCCESS(rv, rv);
419
0
420
0
  // Could use IsEmpty because our callers check for GUID validity,
421
0
  // but it doesn't hurt.
422
0
  bool hasExistingGuid = _guid.Length() == 12;
423
0
  if (hasExistingGuid) {
424
0
    MOZ_ASSERT(IsValidGUID(_guid));
425
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
426
0
    NS_ENSURE_SUCCESS(rv, rv);
427
0
  }
428
0
  else {
429
0
    nsAutoCString guid;
430
0
    rv = GenerateGUID(guid);
431
0
    NS_ENSURE_SUCCESS(rv, rv);
432
0
    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), guid);
433
0
    NS_ENSURE_SUCCESS(rv, rv);
434
0
    _guid.Assign(guid);
435
0
  }
436
0
437
0
  int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
438
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("change_counter"),
439
0
                             syncChangeDelta);
440
0
  NS_ENSURE_SUCCESS(rv, rv);
441
0
442
0
  uint16_t syncStatus = DetermineInitialSyncStatus(aSource);
443
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"),
444
0
                             syncStatus);
445
0
  NS_ENSURE_SUCCESS(rv, rv);
446
0
447
0
  rv = stmt->Execute();
448
0
  NS_ENSURE_SUCCESS(rv, rv);
449
0
450
0
  // Remove stale tombstones if we're reinserting an item.
451
0
  if (hasExistingGuid) {
452
0
    rv = RemoveTombstone(_guid);
453
0
    NS_ENSURE_SUCCESS(rv, rv);
454
0
  }
455
0
456
0
  if (*_itemId == -1) {
457
0
    *_itemId = sLastInsertedItemId;
458
0
  }
459
0
460
0
  if (aParentId > 0) {
461
0
    // Update last modified date of the ancestors.
462
0
    // TODO (bug 408991): Doing this for all ancestors would be slow without a
463
0
    //                    nested tree, so for now update only the parent.
464
0
    rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, aParentId,
465
0
                             aDateAdded);
466
0
    NS_ENSURE_SUCCESS(rv, rv);
467
0
  }
468
0
469
0
  int64_t tagsRootId = TagsRootId();
470
0
  bool isTagging = aGrandParentId == tagsRootId;
471
0
  if (isTagging) {
472
0
    // If we're tagging a bookmark, increment the change counter for all
473
0
    // bookmarks with the URI.
474
0
    rv = AddSyncChangesForBookmarksWithURI(aURI, syncChangeDelta);
475
0
    NS_ENSURE_SUCCESS(rv, rv);
476
0
  }
477
0
478
0
  // Mark all affected separators as changed
479
0
  rv = AdjustSeparatorsSyncCounter(aParentId, aIndex + 1, syncChangeDelta);
480
0
  NS_ENSURE_SUCCESS(rv, rv);
481
0
482
0
  // Add a cache entry since we know everything about this bookmark.
483
0
  BookmarkData bookmark;
484
0
  bookmark.id = *_itemId;
485
0
  bookmark.guid.Assign(_guid);
486
0
  if (!aTitle.IsEmpty()) {
487
0
    bookmark.title.Assign(aTitle);
488
0
  }
489
0
  bookmark.position = aIndex;
490
0
  bookmark.placeId = aPlaceId;
491
0
  bookmark.parentId = aParentId;
492
0
  bookmark.type = aItemType;
493
0
  bookmark.dateAdded = aDateAdded;
494
0
  if (aLastModified)
495
0
    bookmark.lastModified = aLastModified;
496
0
  else
497
0
    bookmark.lastModified = aDateAdded;
498
0
  if (aURI) {
499
0
    rv = aURI->GetSpec(bookmark.url);
500
0
    NS_ENSURE_SUCCESS(rv, rv);
501
0
  }
502
0
  bookmark.parentGuid = aParentGuid;
503
0
  bookmark.grandParentId = aGrandParentId;
504
0
  bookmark.syncStatus = syncStatus;
505
0
506
0
  return NS_OK;
507
0
}
508
509
NS_IMETHODIMP
510
nsNavBookmarks::InsertBookmark(int64_t aFolder,
511
                               nsIURI* aURI,
512
                               int32_t aIndex,
513
                               const nsACString& aTitle,
514
                               const nsACString& aGUID,
515
                               uint16_t aSource,
516
                               int64_t* aNewBookmarkId)
517
0
{
518
0
  NS_ENSURE_ARG(aURI);
519
0
  NS_ENSURE_ARG_POINTER(aNewBookmarkId);
520
0
  NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
521
0
522
0
  if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
523
0
    return NS_ERROR_INVALID_ARG;
524
0
525
0
  mozStorageTransaction transaction(mDB->MainConn(), false);
526
0
527
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
528
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
529
0
  int64_t placeId;
530
0
  nsAutoCString placeGuid;
531
0
  nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
532
0
  NS_ENSURE_SUCCESS(rv, rv);
533
0
534
0
  // Get the correct index for insertion.  This also ensures the parent exists.
535
0
  int32_t index, folderCount;
536
0
  int64_t grandParentId;
537
0
  nsAutoCString folderGuid;
538
0
  rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
539
0
  NS_ENSURE_SUCCESS(rv, rv);
540
0
  if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
541
0
      aIndex >= folderCount) {
542
0
    index = folderCount;
543
0
  }
544
0
  else {
545
0
    index = aIndex;
546
0
    // Create space for the insertion.
547
0
    rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
548
0
    NS_ENSURE_SUCCESS(rv, rv);
549
0
  }
550
0
551
0
  *aNewBookmarkId = -1;
552
0
  PRTime dateAdded = RoundedPRNow();
553
0
  nsAutoCString guid(aGUID);
554
0
  nsCString title;
555
0
  TruncateTitle(aTitle, title);
556
0
557
0
  rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
558
0
                          0, folderGuid, grandParentId, aURI, aSource,
559
0
                          aNewBookmarkId, guid);
560
0
  NS_ENSURE_SUCCESS(rv, rv);
561
0
562
0
  // If not a tag, recalculate frecency for this entry, since it changed.
563
0
  int64_t tagsRootId = TagsRootId();
564
0
  if (grandParentId != tagsRootId) {
565
0
    rv = history->UpdateFrecency(placeId);
566
0
    NS_ENSURE_SUCCESS(rv, rv);
567
0
  }
568
0
569
0
  rv = transaction.Commit();
570
0
  NS_ENSURE_SUCCESS(rv, rv);
571
0
572
0
  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
573
0
                             SKIP_TAGS(grandParentId == mDB->GetTagsFolderId()),
574
0
                             OnItemAdded(*aNewBookmarkId, aFolder, index,
575
0
                                         TYPE_BOOKMARK, aURI, title, dateAdded,
576
0
                                         guid, folderGuid, aSource));
577
0
578
0
  // If the bookmark has been added to a tag container, notify all
579
0
  // bookmark-folder result nodes which contain a bookmark for the new
580
0
  // bookmark's url.
581
0
  if (grandParentId == tagsRootId) {
582
0
    // Notify a tags change to all bookmarks for this URI.
583
0
    nsTArray<BookmarkData> bookmarks;
584
0
    rv = GetBookmarksForURI(aURI, bookmarks);
585
0
    NS_ENSURE_SUCCESS(rv, rv);
586
0
587
0
    for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
588
0
      // Check that bookmarks doesn't include the current tag itemId.
589
0
      MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
590
0
591
0
      NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers, DontSkip,
592
0
                                 OnItemChanged(bookmarks[i].id,
593
0
                                               NS_LITERAL_CSTRING("tags"),
594
0
                                               false,
595
0
                                               EmptyCString(),
596
0
                                               bookmarks[i].lastModified,
597
0
                                               TYPE_BOOKMARK,
598
0
                                               bookmarks[i].parentId,
599
0
                                               bookmarks[i].guid,
600
0
                                               bookmarks[i].parentGuid,
601
0
                                               EmptyCString(),
602
0
                                               aSource));
603
0
    }
604
0
  }
605
0
606
0
  return NS_OK;
607
0
}
608
609
610
NS_IMETHODIMP
611
nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource)
612
0
{
613
0
  AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveItem", OTHER);
614
0
615
0
  NS_ENSURE_ARG(!IsRoot(aItemId));
616
0
617
0
  BookmarkData bookmark;
618
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
619
0
  NS_ENSURE_SUCCESS(rv, rv);
620
0
621
0
  mozStorageTransaction transaction(mDB->MainConn(), false);
622
0
623
0
  // First, if not a tag, remove item annotations.
624
0
  int64_t tagsRootId = TagsRootId();
625
0
  bool isUntagging = bookmark.grandParentId == tagsRootId;
626
0
  if (bookmark.parentId != tagsRootId && !isUntagging) {
627
0
    nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
628
0
    NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
629
0
    rv = annosvc->RemoveItemAnnotations(bookmark.id);
630
0
    NS_ENSURE_SUCCESS(rv, rv);
631
0
  }
632
0
633
0
  if (bookmark.type == TYPE_FOLDER) {
634
0
    // Remove all of the folder's children.
635
0
    rv = RemoveFolderChildren(bookmark.id, aSource);
636
0
    NS_ENSURE_SUCCESS(rv, rv);
637
0
  }
638
0
639
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
640
0
    "DELETE FROM moz_bookmarks WHERE id = :item_id"
641
0
  );
642
0
  NS_ENSURE_STATE(stmt);
643
0
  mozStorageStatementScoper scoper(stmt);
644
0
645
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
646
0
  NS_ENSURE_SUCCESS(rv, rv);
647
0
  rv = stmt->Execute();
648
0
  NS_ENSURE_SUCCESS(rv, rv);
649
0
650
0
  // Fix indices in the parent.
651
0
  if (bookmark.position != DEFAULT_INDEX) {
652
0
    rv = AdjustIndices(bookmark.parentId,
653
0
                       bookmark.position + 1, INT32_MAX, -1);
654
0
    NS_ENSURE_SUCCESS(rv, rv);
655
0
  }
656
0
657
0
  int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
658
0
659
0
  // Add a tombstone for synced items.
660
0
  if (syncChangeDelta) {
661
0
    rv = InsertTombstone(bookmark);
662
0
    NS_ENSURE_SUCCESS(rv, rv);
663
0
  }
664
0
665
0
  bookmark.lastModified = RoundedPRNow();
666
0
  rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta,
667
0
                           bookmark.parentId, bookmark.lastModified);
668
0
  NS_ENSURE_SUCCESS(rv, rv);
669
0
670
0
  // Mark all affected separators as changed
671
0
  rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position, syncChangeDelta);
672
0
  NS_ENSURE_SUCCESS(rv, rv);
673
0
674
0
  if (isUntagging) {
675
0
    // If we're removing a tag, increment the change counter for all bookmarks
676
0
    // with the URI.
677
0
    rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
678
0
    NS_ENSURE_SUCCESS(rv, rv);
679
0
  }
680
0
681
0
  rv = transaction.Commit();
682
0
  NS_ENSURE_SUCCESS(rv, rv);
683
0
684
0
  nsCOMPtr<nsIURI> uri;
685
0
  if (bookmark.type == TYPE_BOOKMARK) {
686
0
    // If not a tag, recalculate frecency for this entry, since it changed.
687
0
    if (bookmark.grandParentId != tagsRootId) {
688
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
689
0
      NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
690
0
      rv = history->UpdateFrecency(bookmark.placeId);
691
0
      NS_ENSURE_SUCCESS(rv, rv);
692
0
    }
693
0
    // A broken url should not interrupt the removal process.
694
0
    (void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
695
0
    // We cannot assert since some automated tests are checking this path.
696
0
    NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
697
0
  }
698
0
699
0
  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
700
0
                             SKIP_TAGS(bookmark.parentId == tagsRootId ||
701
0
                                       bookmark.grandParentId == tagsRootId),
702
0
                             OnItemRemoved(bookmark.id,
703
0
                                           bookmark.parentId,
704
0
                                           bookmark.position,
705
0
                                           bookmark.type,
706
0
                                           uri,
707
0
                                           bookmark.guid,
708
0
                                           bookmark.parentGuid,
709
0
                                           aSource));
710
0
711
0
  if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == tagsRootId &&
712
0
      uri) {
713
0
    // If the removed bookmark was child of a tag container, notify a tags
714
0
    // change to all bookmarks for this URI.
715
0
    nsTArray<BookmarkData> bookmarks;
716
0
    rv = GetBookmarksForURI(uri, bookmarks);
717
0
    NS_ENSURE_SUCCESS(rv, rv);
718
0
719
0
    for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
720
0
      NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers, DontSkip,
721
0
                                 OnItemChanged(bookmarks[i].id,
722
0
                                               NS_LITERAL_CSTRING("tags"),
723
0
                                               false,
724
0
                                               EmptyCString(),
725
0
                                               bookmarks[i].lastModified,
726
0
                                               TYPE_BOOKMARK,
727
0
                                               bookmarks[i].parentId,
728
0
                                               bookmarks[i].guid,
729
0
                                               bookmarks[i].parentGuid,
730
0
                                               EmptyCString(),
731
0
                                               aSource));
732
0
    }
733
0
734
0
  }
735
0
736
0
  return NS_OK;
737
0
}
738
739
740
NS_IMETHODIMP
741
nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aTitle,
742
                             int32_t aIndex, const nsACString& aGUID,
743
                             uint16_t aSource, int64_t* aNewFolderId)
744
0
{
745
0
  // NOTE: aParent can be null for root creation, so not checked
746
0
  NS_ENSURE_ARG_POINTER(aNewFolderId);
747
0
  NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
748
0
  if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
749
0
    return NS_ERROR_INVALID_ARG;
750
0
751
0
  // Get the correct index for insertion.  This also ensures the parent exists.
752
0
  int32_t index = aIndex, folderCount;
753
0
  int64_t grandParentId;
754
0
  nsAutoCString folderGuid;
755
0
  nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
756
0
  NS_ENSURE_SUCCESS(rv, rv);
757
0
758
0
  mozStorageTransaction transaction(mDB->MainConn(), false);
759
0
760
0
  if (aIndex == nsINavBookmarksService::DEFAULT_INDEX || aIndex >= folderCount) {
761
0
    index = folderCount;
762
0
  } else {
763
0
    // Create space for the insertion.
764
0
    rv = AdjustIndices(aParent, index, INT32_MAX, 1);
765
0
    NS_ENSURE_SUCCESS(rv, rv);
766
0
  }
767
0
768
0
  *aNewFolderId = -1;
769
0
  PRTime dateAdded = RoundedPRNow();
770
0
  nsAutoCString guid(aGUID);
771
0
  nsCString title;
772
0
  TruncateTitle(aTitle, title);
773
0
774
0
  rv = InsertBookmarkInDB(-1, FOLDER, aParent, index,
775
0
                          title, dateAdded, 0, folderGuid, grandParentId,
776
0
                          nullptr, aSource, aNewFolderId, guid);
777
0
  NS_ENSURE_SUCCESS(rv, rv);
778
0
779
0
  rv = transaction.Commit();
780
0
  NS_ENSURE_SUCCESS(rv, rv);
781
0
782
0
  int64_t tagsRootId = TagsRootId();
783
0
784
0
  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
785
0
                             SKIP_TAGS(aParent == tagsRootId),
786
0
                             OnItemAdded(*aNewFolderId, aParent, index, FOLDER,
787
0
                                         nullptr, title, dateAdded, guid,
788
0
                                         folderGuid, aSource));
789
0
790
0
  return NS_OK;
791
0
}
792
793
bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
794
0
{
795
0
  nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
796
0
  NS_ENSURE_TRUE(annosvc, false);
797
0
  bool isLivemark;
798
0
  nsresult rv = annosvc->ItemHasAnnotation(aFolderId,
799
0
                                           FEED_URI_ANNO,
800
0
                                           &isLivemark);
801
0
  NS_ENSURE_SUCCESS(rv, false);
802
0
  return isLivemark;
803
0
}
804
805
nsresult
806
nsNavBookmarks::GetDescendantChildren(int64_t aFolderId,
807
                                      const nsACString& aFolderGuid,
808
                                      int64_t aGrandParentId,
809
0
                                      nsTArray<BookmarkData>& aFolderChildrenArray) {
810
0
  // New children will be added from this index on.
811
0
  uint32_t startIndex = aFolderChildrenArray.Length();
812
0
  nsresult rv;
813
0
  {
814
0
    // Collect children informations.
815
0
    // Select all children of a given folder, sorted by position.
816
0
    // This is a LEFT JOIN because not all bookmarks types have a place.
817
0
    // We construct a result where the first columns exactly match
818
0
    // kGetInfoIndex_* order, and additionally contains columns for position,
819
0
    // item_child, and folder_child from moz_bookmarks.
820
0
    nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
821
0
      "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
822
0
             "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
823
0
             "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
824
0
             "b.guid, b.position, b.type, b.fk, b.syncStatus "
825
0
      "FROM moz_bookmarks b "
826
0
      "LEFT JOIN moz_places h ON b.fk = h.id "
827
0
      "WHERE b.parent = :parent "
828
0
      "ORDER BY b.position ASC"
829
0
    );
830
0
    NS_ENSURE_STATE(stmt);
831
0
    mozStorageStatementScoper scoper(stmt);
832
0
833
0
    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
834
0
    NS_ENSURE_SUCCESS(rv, rv);
835
0
836
0
    bool hasMore;
837
0
    while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
838
0
      BookmarkData child;
839
0
      rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
840
0
      NS_ENSURE_SUCCESS(rv, rv);
841
0
      child.parentId = aFolderId;
842
0
      child.grandParentId = aGrandParentId;
843
0
      child.parentGuid = aFolderGuid;
844
0
      rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
845
0
      NS_ENSURE_SUCCESS(rv, rv);
846
0
      rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
847
0
      NS_ENSURE_SUCCESS(rv, rv);
848
0
      rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
849
0
      NS_ENSURE_SUCCESS(rv, rv);
850
0
      rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
851
0
      NS_ENSURE_SUCCESS(rv, rv);
852
0
      rv = stmt->GetInt32(kGetChildrenIndex_SyncStatus, &child.syncStatus);
853
0
      NS_ENSURE_SUCCESS(rv, rv);
854
0
855
0
      if (child.type == TYPE_BOOKMARK) {
856
0
        rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
857
0
        NS_ENSURE_SUCCESS(rv, rv);
858
0
      }
859
0
860
0
      // Append item to children's array.
861
0
      aFolderChildrenArray.AppendElement(child);
862
0
    }
863
0
  }
864
0
865
0
  // Recursively call GetDescendantChildren for added folders.
866
0
  // We start at startIndex since previous folders are checked
867
0
  // by previous calls to this method.
868
0
  uint32_t childCount = aFolderChildrenArray.Length();
869
0
  for (uint32_t i = startIndex; i < childCount; ++i) {
870
0
    if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
871
0
      // nsTarray assumes that all children can be memmove()d, thus we can't
872
0
      // just pass aFolderChildrenArray[i].guid to a method that will change
873
0
      // the array itself.  Otherwise, since it's passed by reference, after a
874
0
      // memmove() it could point to garbage and cause intermittent crashes.
875
0
      nsCString guid = aFolderChildrenArray[i].guid;
876
0
      GetDescendantChildren(aFolderChildrenArray[i].id,
877
0
                            guid,
878
0
                            aFolderId,
879
0
                            aFolderChildrenArray);
880
0
    }
881
0
  }
882
0
883
0
  return NS_OK;
884
0
}
885
886
887
nsresult
888
nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId, uint16_t aSource)
889
0
{
890
0
  AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveFolderChilder", OTHER);
891
0
892
0
  NS_ENSURE_ARG_MIN(aFolderId, 1);
893
0
  int64_t rootId = -1;
894
0
  nsresult rv = GetPlacesRoot(&rootId);
895
0
  NS_ENSURE_SUCCESS(rv, rv);
896
0
  NS_ENSURE_ARG(aFolderId != rootId);
897
0
898
0
  BookmarkData folder;
899
0
  rv = FetchItemInfo(aFolderId, folder);
900
0
  NS_ENSURE_SUCCESS(rv, rv);
901
0
  NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
902
0
903
0
  // Fill folder children array recursively.
904
0
  nsTArray<BookmarkData> folderChildrenArray;
905
0
  rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
906
0
                             folderChildrenArray);
907
0
  NS_ENSURE_SUCCESS(rv, rv);
908
0
909
0
  // Build a string of folders whose children will be removed.
910
0
  nsCString foldersToRemove;
911
0
  for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
912
0
    BookmarkData& child = folderChildrenArray[i];
913
0
914
0
    if (child.type == TYPE_FOLDER) {
915
0
      foldersToRemove.Append(',');
916
0
      foldersToRemove.AppendInt(child.id);
917
0
    }
918
0
  }
919
0
920
0
  int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
921
0
922
0
  // Delete items from the database now.
923
0
  mozStorageTransaction transaction(mDB->MainConn(), false);
924
0
925
0
  nsCOMPtr<mozIStorageStatement> deleteStatement = mDB->GetStatement(
926
0
    NS_LITERAL_CSTRING(
927
0
      "DELETE FROM moz_bookmarks "
928
0
      "WHERE parent IN (:parent") + foldersToRemove + NS_LITERAL_CSTRING(")")
929
0
  );
930
0
  NS_ENSURE_STATE(deleteStatement);
931
0
  mozStorageStatementScoper deleteStatementScoper(deleteStatement);
932
0
933
0
  rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
934
0
  NS_ENSURE_SUCCESS(rv, rv);
935
0
  rv = deleteStatement->Execute();
936
0
  NS_ENSURE_SUCCESS(rv, rv);
937
0
938
0
  // Clean up orphan items annotations.
939
0
  nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
940
0
  if (!conn) {
941
0
    return NS_ERROR_UNEXPECTED;
942
0
  }
943
0
  rv = conn->ExecuteSimpleSQL(
944
0
    NS_LITERAL_CSTRING(
945
0
      "DELETE FROM moz_items_annos "
946
0
      "WHERE id IN ("
947
0
        "SELECT a.id from moz_items_annos a "
948
0
        "LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
949
0
        "WHERE b.id ISNULL)"));
950
0
  NS_ENSURE_SUCCESS(rv, rv);
951
0
952
0
  // Set the lastModified date.
953
0
  rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, folder.id,
954
0
                           RoundedPRNow());
955
0
  NS_ENSURE_SUCCESS(rv, rv);
956
0
957
0
  int64_t tagsRootId = TagsRootId();
958
0
959
0
  if (syncChangeDelta) {
960
0
    nsTArray<TombstoneData> tombstones(folderChildrenArray.Length());
961
0
    PRTime dateRemoved = RoundedPRNow();
962
0
963
0
    for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
964
0
      BookmarkData& child = folderChildrenArray[i];
965
0
      if (NeedsTombstone(child)) {
966
0
        // Write tombstones for synced children.
967
0
        TombstoneData childTombstone = {child.guid, dateRemoved};
968
0
        tombstones.AppendElement(childTombstone);
969
0
      }
970
0
      bool isUntagging = child.grandParentId == tagsRootId;
971
0
      if (isUntagging) {
972
0
        // Bump the change counter for all tagged bookmarks when removing a tag
973
0
        // folder.
974
0
        rv = AddSyncChangesForBookmarksWithURL(child.url, syncChangeDelta);
975
0
        NS_ENSURE_SUCCESS(rv, rv);
976
0
      }
977
0
    }
978
0
979
0
    rv = InsertTombstones(tombstones);
980
0
    NS_ENSURE_SUCCESS(rv, rv);
981
0
  }
982
0
983
0
  rv = transaction.Commit();
984
0
  NS_ENSURE_SUCCESS(rv, rv);
985
0
986
0
  // Call observers in reverse order to serve children before their parent.
987
0
  for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
988
0
    BookmarkData& child = folderChildrenArray[i];
989
0
990
0
    nsCOMPtr<nsIURI> uri;
991
0
    if (child.type == TYPE_BOOKMARK) {
992
0
      // If not a tag, recalculate frecency for this entry, since it changed.
993
0
      if (child.grandParentId != tagsRootId) {
994
0
        nsNavHistory* history = nsNavHistory::GetHistoryService();
995
0
        NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
996
0
        rv = history->UpdateFrecency(child.placeId);
997
0
        NS_ENSURE_SUCCESS(rv, rv);
998
0
      }
999
0
      // A broken url should not interrupt the removal process.
1000
0
      (void)NS_NewURI(getter_AddRefs(uri), child.url);
1001
0
      // We cannot assert since some automated tests are checking this path.
1002
0
      NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
1003
0
    }
1004
0
1005
0
    NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
1006
0
                               ((child.grandParentId == tagsRootId) ? SkipTags : SkipDescendants),
1007
0
                               OnItemRemoved(child.id,
1008
0
                                             child.parentId,
1009
0
                                             child.position,
1010
0
                                             child.type,
1011
0
                                             uri,
1012
0
                                             child.guid,
1013
0
                                             child.parentGuid,
1014
0
                                             aSource));
1015
0
1016
0
    if (child.type == TYPE_BOOKMARK && child.grandParentId == tagsRootId &&
1017
0
        uri) {
1018
0
      // If the removed bookmark was a child of a tag container, notify all
1019
0
      // bookmark-folder result nodes which contain a bookmark for the removed
1020
0
      // bookmark's url.
1021
0
      nsTArray<BookmarkData> bookmarks;
1022
0
      rv = GetBookmarksForURI(uri, bookmarks);
1023
0
      NS_ENSURE_SUCCESS(rv, rv);
1024
0
1025
0
      for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
1026
0
        NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers, DontSkip,
1027
0
                                   OnItemChanged(bookmarks[i].id,
1028
0
                                                 NS_LITERAL_CSTRING("tags"),
1029
0
                                                 false,
1030
0
                                                 EmptyCString(),
1031
0
                                                 bookmarks[i].lastModified,
1032
0
                                                 TYPE_BOOKMARK,
1033
0
                                                 bookmarks[i].parentId,
1034
0
                                                 bookmarks[i].guid,
1035
0
                                                 bookmarks[i].parentGuid,
1036
0
                                                 EmptyCString(),
1037
0
                                                 aSource));
1038
0
      }
1039
0
    }
1040
0
  }
1041
0
1042
0
  return NS_OK;
1043
0
}
1044
1045
1046
nsresult
1047
nsNavBookmarks::FetchItemInfo(int64_t aItemId,
1048
                              BookmarkData& _bookmark)
1049
0
{
1050
0
  // LEFT JOIN since not all bookmarks have an associated place.
1051
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1052
0
    "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
1053
0
           "b.dateAdded, b.lastModified, b.guid, t.guid, t.parent, "
1054
0
           "b.syncStatus "
1055
0
    "FROM moz_bookmarks b "
1056
0
    "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
1057
0
    "LEFT JOIN moz_places h ON h.id = b.fk "
1058
0
    "WHERE b.id = :item_id"
1059
0
  );
1060
0
  NS_ENSURE_STATE(stmt);
1061
0
  mozStorageStatementScoper scoper(stmt);
1062
0
1063
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1064
0
  NS_ENSURE_SUCCESS(rv, rv);
1065
0
1066
0
  bool hasResult;
1067
0
  rv = stmt->ExecuteStep(&hasResult);
1068
0
  NS_ENSURE_SUCCESS(rv, rv);
1069
0
  if (!hasResult) {
1070
0
    return NS_ERROR_INVALID_ARG;
1071
0
  }
1072
0
1073
0
  _bookmark.id = aItemId;
1074
0
  rv = stmt->GetUTF8String(1, _bookmark.url);
1075
0
  NS_ENSURE_SUCCESS(rv, rv);
1076
0
1077
0
  bool isNull;
1078
0
  rv = stmt->GetIsNull(2, &isNull);
1079
0
  NS_ENSURE_SUCCESS(rv, rv);
1080
0
  if (!isNull) {
1081
0
    rv = stmt->GetUTF8String(2, _bookmark.title);
1082
0
    NS_ENSURE_SUCCESS(rv, rv);
1083
0
  }
1084
0
  rv = stmt->GetInt32(3, &_bookmark.position);
1085
0
  NS_ENSURE_SUCCESS(rv, rv);
1086
0
  rv = stmt->GetInt64(4, &_bookmark.placeId);
1087
0
  NS_ENSURE_SUCCESS(rv, rv);
1088
0
  rv = stmt->GetInt64(5, &_bookmark.parentId);
1089
0
  NS_ENSURE_SUCCESS(rv, rv);
1090
0
  rv = stmt->GetInt32(6, &_bookmark.type);
1091
0
  NS_ENSURE_SUCCESS(rv, rv);
1092
0
  rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
1093
0
  NS_ENSURE_SUCCESS(rv, rv);
1094
0
  rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
1095
0
  NS_ENSURE_SUCCESS(rv, rv);
1096
0
  rv = stmt->GetUTF8String(9, _bookmark.guid);
1097
0
  NS_ENSURE_SUCCESS(rv, rv);
1098
0
  // Getting properties of the root would show no parent.
1099
0
  rv = stmt->GetIsNull(10, &isNull);
1100
0
  NS_ENSURE_SUCCESS(rv, rv);
1101
0
  if (!isNull) {
1102
0
    rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
1103
0
    NS_ENSURE_SUCCESS(rv, rv);
1104
0
    rv = stmt->GetInt64(11, &_bookmark.grandParentId);
1105
0
    NS_ENSURE_SUCCESS(rv, rv);
1106
0
  }
1107
0
  else {
1108
0
    _bookmark.grandParentId = -1;
1109
0
  }
1110
0
  rv = stmt->GetInt32(12, &_bookmark.syncStatus);
1111
0
  NS_ENSURE_SUCCESS(rv, rv);
1112
0
1113
0
  return NS_OK;
1114
0
}
1115
1116
1117
nsresult
1118
nsNavBookmarks::FetchItemInfo(const nsCString& aGUID,
1119
                              BookmarkData& _bookmark)
1120
0
{
1121
0
  // LEFT JOIN since not all bookmarks have an associated place.
1122
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1123
0
    "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
1124
0
           "b.dateAdded, b.lastModified, t.guid, t.parent, "
1125
0
           "b.syncStatus "
1126
0
    "FROM moz_bookmarks b "
1127
0
    "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
1128
0
    "LEFT JOIN moz_places h ON h.id = b.fk "
1129
0
    "WHERE b.guid = :item_guid"
1130
0
  );
1131
0
  NS_ENSURE_STATE(stmt);
1132
0
  mozStorageStatementScoper scoper(stmt);
1133
0
1134
0
  nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), aGUID);
1135
0
  NS_ENSURE_SUCCESS(rv, rv);
1136
0
1137
0
  _bookmark.guid = aGUID;
1138
0
1139
0
  bool hasResult;
1140
0
  rv = stmt->ExecuteStep(&hasResult);
1141
0
  NS_ENSURE_SUCCESS(rv, rv);
1142
0
  if (!hasResult) {
1143
0
    return NS_ERROR_INVALID_ARG;
1144
0
  }
1145
0
1146
0
  rv = stmt->GetInt64(0, &_bookmark.id);
1147
0
  NS_ENSURE_SUCCESS(rv, rv);
1148
0
1149
0
  rv = stmt->GetUTF8String(1, _bookmark.url);
1150
0
  NS_ENSURE_SUCCESS(rv, rv);
1151
0
1152
0
  bool isNull;
1153
0
  rv = stmt->GetIsNull(2, &isNull);
1154
0
  NS_ENSURE_SUCCESS(rv, rv);
1155
0
  if (!isNull) {
1156
0
    rv = stmt->GetUTF8String(2, _bookmark.title);
1157
0
    NS_ENSURE_SUCCESS(rv, rv);
1158
0
  }
1159
0
  rv = stmt->GetInt32(3, &_bookmark.position);
1160
0
  NS_ENSURE_SUCCESS(rv, rv);
1161
0
  rv = stmt->GetInt64(4, &_bookmark.placeId);
1162
0
  NS_ENSURE_SUCCESS(rv, rv);
1163
0
  rv = stmt->GetInt64(5, &_bookmark.parentId);
1164
0
  NS_ENSURE_SUCCESS(rv, rv);
1165
0
  rv = stmt->GetInt32(6, &_bookmark.type);
1166
0
  NS_ENSURE_SUCCESS(rv, rv);
1167
0
  rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
1168
0
  NS_ENSURE_SUCCESS(rv, rv);
1169
0
  rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
1170
0
  NS_ENSURE_SUCCESS(rv, rv);
1171
0
  // Getting properties of the root would show no parent.
1172
0
  rv = stmt->GetIsNull(9, &isNull);
1173
0
  NS_ENSURE_SUCCESS(rv, rv);
1174
0
  if (!isNull) {
1175
0
    rv = stmt->GetUTF8String(9, _bookmark.parentGuid);
1176
0
    NS_ENSURE_SUCCESS(rv, rv);
1177
0
    rv = stmt->GetInt64(10, &_bookmark.grandParentId);
1178
0
    NS_ENSURE_SUCCESS(rv, rv);
1179
0
  }
1180
0
  else {
1181
0
    _bookmark.grandParentId = -1;
1182
0
  }
1183
0
  rv = stmt->GetInt32(11, &_bookmark.syncStatus);
1184
0
  NS_ENSURE_SUCCESS(rv, rv);
1185
0
1186
0
  return NS_OK;
1187
0
}
1188
1189
1190
nsresult
1191
nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
1192
                                    int64_t aSyncChangeDelta,
1193
                                    int64_t aItemId,
1194
                                    PRTime aValue)
1195
0
{
1196
0
  aValue = RoundToMilliseconds(aValue);
1197
0
1198
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1199
0
    "UPDATE moz_bookmarks SET lastModified = :date, "
1200
0
      "syncChangeCounter = syncChangeCounter + :delta "
1201
0
    "WHERE id = :item_id"
1202
0
  );
1203
0
  NS_ENSURE_STATE(stmt);
1204
0
  mozStorageStatementScoper scoper(stmt);
1205
0
1206
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
1207
0
  NS_ENSURE_SUCCESS(rv, rv);
1208
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1209
0
  NS_ENSURE_SUCCESS(rv, rv);
1210
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1211
0
  NS_ENSURE_SUCCESS(rv, rv);
1212
0
1213
0
  rv = stmt->Execute();
1214
0
  NS_ENSURE_SUCCESS(rv, rv);
1215
0
1216
0
  // note, we are not notifying the observers
1217
0
  // that the item has changed.
1218
0
1219
0
  return NS_OK;
1220
0
}
1221
1222
NS_IMETHODIMP
1223
nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
1224
                                    uint16_t aSource)
1225
0
{
1226
0
  NS_ENSURE_ARG_MIN(aItemId, 1);
1227
0
1228
0
  BookmarkData bookmark;
1229
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
1230
0
  NS_ENSURE_SUCCESS(rv, rv);
1231
0
1232
0
  int64_t tagsRootId = TagsRootId();
1233
0
  bool isTagging = bookmark.grandParentId == tagsRootId;
1234
0
  int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1235
0
1236
0
  // Round here so that we notify with the right value.
1237
0
  bookmark.lastModified = RoundToMilliseconds(aLastModified);
1238
0
1239
0
  if (isTagging) {
1240
0
    // If we're changing a tag, bump the change counter for all tagged
1241
0
    // bookmarks. We use a separate code path to avoid a transaction for
1242
0
    // non-tags.
1243
0
    mozStorageTransaction transaction(mDB->MainConn(), false);
1244
0
1245
0
    rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1246
0
                             bookmark.lastModified);
1247
0
    NS_ENSURE_SUCCESS(rv, rv);
1248
0
1249
0
    rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
1250
0
    NS_ENSURE_SUCCESS(rv, rv);
1251
0
1252
0
    rv = transaction.Commit();
1253
0
    NS_ENSURE_SUCCESS(rv, rv);
1254
0
  } else {
1255
0
    rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1256
0
                             bookmark.lastModified);
1257
0
    NS_ENSURE_SUCCESS(rv, rv);
1258
0
  }
1259
0
1260
0
  // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
1261
0
  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
1262
0
                   SKIP_TAGS(isTagging || bookmark.parentId == tagsRootId),
1263
0
                   OnItemChanged(bookmark.id,
1264
0
                                 NS_LITERAL_CSTRING("lastModified"),
1265
0
                                 false,
1266
0
                                 nsPrintfCString("%" PRId64, bookmark.lastModified),
1267
0
                                 bookmark.lastModified,
1268
0
                                 bookmark.type,
1269
0
                                 bookmark.parentId,
1270
0
                                 bookmark.guid,
1271
0
                                 bookmark.parentGuid,
1272
0
                                 EmptyCString(),
1273
0
                                 aSource));
1274
0
  return NS_OK;
1275
0
}
1276
1277
1278
nsresult
1279
nsNavBookmarks::AddSyncChangesForBookmarksWithURL(const nsACString& aURL,
1280
                                                  int64_t aSyncChangeDelta)
1281
0
{
1282
0
  if (!aSyncChangeDelta) {
1283
0
    return NS_OK;
1284
0
  }
1285
0
  nsCOMPtr<nsIURI> uri;
1286
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
1287
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1288
0
    // Ignore sync changes for invalid URLs.
1289
0
    return NS_OK;
1290
0
  }
1291
0
  return AddSyncChangesForBookmarksWithURI(uri, aSyncChangeDelta);
1292
0
}
1293
1294
1295
nsresult
1296
nsNavBookmarks::AddSyncChangesForBookmarksWithURI(nsIURI* aURI,
1297
                                                  int64_t aSyncChangeDelta)
1298
0
{
1299
0
  if (NS_WARN_IF(!aURI) || !aSyncChangeDelta) {
1300
0
    // Ignore sync changes for invalid URIs.
1301
0
    return NS_OK;
1302
0
  }
1303
0
1304
0
  nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1305
0
   "UPDATE moz_bookmarks SET "
1306
0
    "syncChangeCounter = syncChangeCounter + :delta "
1307
0
   "WHERE type = :type AND "
1308
0
         "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND "
1309
0
               "url = :url)"
1310
0
  );
1311
0
  NS_ENSURE_STATE(statement);
1312
0
  mozStorageStatementScoper scoper(statement);
1313
0
1314
0
  nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
1315
0
                                           aSyncChangeDelta);
1316
0
  NS_ENSURE_SUCCESS(rv, rv);
1317
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1318
0
                                  nsINavBookmarksService::TYPE_BOOKMARK);
1319
0
  NS_ENSURE_SUCCESS(rv, rv);
1320
0
  rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("url"), aURI);
1321
0
  NS_ENSURE_SUCCESS(rv, rv);
1322
0
1323
0
  return statement->Execute();
1324
0
}
1325
1326
1327
nsresult
1328
nsNavBookmarks::AddSyncChangesForBookmarksInFolder(int64_t aFolderId,
1329
                                                   int64_t aSyncChangeDelta)
1330
0
{
1331
0
  if (!aSyncChangeDelta) {
1332
0
    return NS_OK;
1333
0
  }
1334
0
1335
0
  nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1336
0
   "UPDATE moz_bookmarks SET "
1337
0
    "syncChangeCounter = syncChangeCounter + :delta "
1338
0
    "WHERE type = :type AND "
1339
0
          "fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)"
1340
0
  );
1341
0
  NS_ENSURE_STATE(statement);
1342
0
  mozStorageStatementScoper scoper(statement);
1343
0
1344
0
  nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
1345
0
                                           aSyncChangeDelta);
1346
0
  NS_ENSURE_SUCCESS(rv, rv);
1347
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1348
0
                                  nsINavBookmarksService::TYPE_BOOKMARK);
1349
0
  NS_ENSURE_SUCCESS(rv, rv);
1350
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1351
0
  NS_ENSURE_SUCCESS(rv, rv);
1352
0
1353
0
  rv = statement->Execute();
1354
0
  NS_ENSURE_SUCCESS(rv, rv);
1355
0
1356
0
  return NS_OK;
1357
0
}
1358
1359
1360
nsresult
1361
nsNavBookmarks::InsertTombstone(const BookmarkData& aBookmark)
1362
0
{
1363
0
  if (!NeedsTombstone(aBookmark)) {
1364
0
    return NS_OK;
1365
0
  }
1366
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1367
0
    "INSERT INTO moz_bookmarks_deleted (guid, dateRemoved) "
1368
0
    "VALUES (:guid, :date_removed)"
1369
0
  );
1370
0
  NS_ENSURE_STATE(stmt);
1371
0
  mozStorageStatementScoper scoper(stmt);
1372
0
1373
0
  nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
1374
0
                                           aBookmark.guid);
1375
0
  NS_ENSURE_SUCCESS(rv, rv);
1376
0
  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_removed"),
1377
0
                             RoundedPRNow());
1378
0
  NS_ENSURE_SUCCESS(rv, rv);
1379
0
1380
0
  rv = stmt->Execute();
1381
0
  NS_ENSURE_SUCCESS(rv, rv);
1382
0
1383
0
  return NS_OK;
1384
0
}
1385
1386
1387
nsresult
1388
nsNavBookmarks::InsertTombstones(const nsTArray<TombstoneData>& aTombstones)
1389
0
{
1390
0
  if (aTombstones.IsEmpty()) {
1391
0
    return NS_OK;
1392
0
  }
1393
0
1394
0
  size_t maxRowsPerChunk = SQLITE_MAX_VARIABLE_NUMBER / 2;
1395
0
  for (uint32_t startIndex = 0; startIndex < aTombstones.Length(); startIndex += maxRowsPerChunk) {
1396
0
    size_t rowsPerChunk = std::min(maxRowsPerChunk, aTombstones.Length() - startIndex);
1397
0
1398
0
    // Build a query to insert all tombstones in a single statement, chunking to
1399
0
    // avoid the SQLite bound parameter limit.
1400
0
    nsAutoCString tombstonesToInsert;
1401
0
    tombstonesToInsert.AppendLiteral("VALUES (?, ?)");
1402
0
    for (uint32_t i = 1; i < rowsPerChunk; ++i) {
1403
0
      tombstonesToInsert.AppendLiteral(", (?, ?)");
1404
0
    }
1405
#ifdef DEBUG
1406
    MOZ_ASSERT(tombstonesToInsert.CountChar('?') == rowsPerChunk * 2,
1407
               "Expected one binding param per column for each tombstone");
1408
#endif
1409
1410
0
    nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1411
0
      NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks_deleted "
1412
0
        "(guid, dateRemoved) ") +
1413
0
      tombstonesToInsert
1414
0
    );
1415
0
    NS_ENSURE_STATE(stmt);
1416
0
    mozStorageStatementScoper scoper(stmt);
1417
0
1418
0
    uint32_t paramIndex = 0;
1419
0
    nsresult rv;
1420
0
    for (uint32_t i = 0; i < rowsPerChunk; ++i) {
1421
0
      const TombstoneData& tombstone = aTombstones[startIndex + i];
1422
0
      rv = stmt->BindUTF8StringByIndex(paramIndex++, tombstone.guid);
1423
0
      NS_ENSURE_SUCCESS(rv, rv);
1424
0
      rv = stmt->BindInt64ByIndex(paramIndex++, tombstone.dateRemoved);
1425
0
      NS_ENSURE_SUCCESS(rv, rv);
1426
0
    }
1427
0
1428
0
    rv = stmt->Execute();
1429
0
    NS_ENSURE_SUCCESS(rv, rv);
1430
0
  }
1431
0
1432
0
  return NS_OK;
1433
0
}
1434
1435
1436
nsresult
1437
nsNavBookmarks::RemoveTombstone(const nsACString& aGUID)
1438
0
{
1439
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1440
0
    "DELETE FROM moz_bookmarks_deleted WHERE guid = :guid"
1441
0
  );
1442
0
  NS_ENSURE_STATE(stmt);
1443
0
  mozStorageStatementScoper scoper(stmt);
1444
0
1445
0
  nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1446
0
  NS_ENSURE_SUCCESS(rv, rv);
1447
0
1448
0
  return stmt->Execute();
1449
0
}
1450
1451
1452
NS_IMETHODIMP
1453
nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
1454
                             uint16_t aSource)
1455
0
{
1456
0
  NS_ENSURE_ARG_MIN(aItemId, 1);
1457
0
1458
0
  BookmarkData bookmark;
1459
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
1460
0
  NS_ENSURE_SUCCESS(rv, rv);
1461
0
1462
0
  int64_t tagsRootId = TagsRootId();
1463
0
  bool isChangingTagFolder = bookmark.parentId == tagsRootId;
1464
0
  int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1465
0
1466
0
  nsAutoCString title;
1467
0
  TruncateTitle(aTitle, title);
1468
0
1469
0
  if (isChangingTagFolder) {
1470
0
    // If we're changing the title of a tag folder, bump the change counter
1471
0
    // for all tagged bookmarks. We use a separate code path to avoid a
1472
0
    // transaction for non-tags.
1473
0
    mozStorageTransaction transaction(mDB->MainConn(), false);
1474
0
1475
0
    rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
1476
0
    NS_ENSURE_SUCCESS(rv, rv);
1477
0
1478
0
    rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
1479
0
    NS_ENSURE_SUCCESS(rv, rv);
1480
0
1481
0
    rv = transaction.Commit();
1482
0
    NS_ENSURE_SUCCESS(rv, rv);
1483
0
  } else {
1484
0
    rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
1485
0
    NS_ENSURE_SUCCESS(rv, rv);
1486
0
  }
1487
0
1488
0
  NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers, SKIP_TAGS(isChangingTagFolder),
1489
0
                             OnItemChanged(bookmark.id,
1490
0
                                           NS_LITERAL_CSTRING("title"),
1491
0
                                           false,
1492
0
                                           title,
1493
0
                                           bookmark.lastModified,
1494
0
                                           bookmark.type,
1495
0
                                           bookmark.parentId,
1496
0
                                           bookmark.guid,
1497
0
                                           bookmark.parentGuid,
1498
0
                                           EmptyCString(),
1499
0
                                           aSource));
1500
0
  return NS_OK;
1501
0
}
1502
1503
1504
nsresult
1505
nsNavBookmarks::SetItemTitleInternal(BookmarkData& aBookmark,
1506
                                     const nsACString& aTitle,
1507
                                     int64_t aSyncChangeDelta)
1508
0
{
1509
0
  nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1510
0
    "UPDATE moz_bookmarks SET "
1511
0
     "title = :item_title, lastModified = :date, "
1512
0
     "syncChangeCounter = syncChangeCounter + :delta "
1513
0
    "WHERE id = :item_id"
1514
0
  );
1515
0
  NS_ENSURE_STATE(statement);
1516
0
  mozStorageStatementScoper scoper(statement);
1517
0
1518
0
  nsresult rv;
1519
0
  if (aTitle.IsEmpty()) {
1520
0
    rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
1521
0
  }
1522
0
  else {
1523
0
    rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
1524
0
                                         aTitle);
1525
0
  }
1526
0
  NS_ENSURE_SUCCESS(rv, rv);
1527
0
  aBookmark.lastModified = RoundToMilliseconds(RoundedPRNow());
1528
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
1529
0
                                  aBookmark.lastModified);
1530
0
  NS_ENSURE_SUCCESS(rv, rv);
1531
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmark.id);
1532
0
  NS_ENSURE_SUCCESS(rv, rv);
1533
0
  rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"),
1534
0
                                  aSyncChangeDelta);
1535
0
  NS_ENSURE_SUCCESS(rv, rv);
1536
0
1537
0
  rv = statement->Execute();
1538
0
  NS_ENSURE_SUCCESS(rv, rv);
1539
0
1540
0
  return NS_OK;
1541
0
}
1542
1543
1544
NS_IMETHODIMP
1545
nsNavBookmarks::GetItemTitle(int64_t aItemId,
1546
                             nsACString& _title)
1547
0
{
1548
0
  NS_ENSURE_ARG_MIN(aItemId, 1);
1549
0
1550
0
  BookmarkData bookmark;
1551
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
1552
0
  NS_ENSURE_SUCCESS(rv, rv);
1553
0
1554
0
  _title = bookmark.title;
1555
0
  return NS_OK;
1556
0
}
1557
1558
1559
nsresult
1560
nsNavBookmarks::GetBookmarkURI(int64_t aItemId,
1561
                               nsIURI** _URI)
1562
0
{
1563
0
  NS_ENSURE_ARG_MIN(aItemId, 1);
1564
0
  NS_ENSURE_ARG_POINTER(_URI);
1565
0
1566
0
  BookmarkData bookmark;
1567
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
1568
0
  NS_ENSURE_SUCCESS(rv, rv);
1569
0
1570
0
  rv = NS_NewURI(_URI, bookmark.url);
1571
0
  NS_ENSURE_SUCCESS(rv, rv);
1572
0
1573
0
  return NS_OK;
1574
0
}
1575
1576
1577
nsresult
1578
nsNavBookmarks::ResultNodeForContainer(const nsCString& aGUID,
1579
                                       nsNavHistoryQueryOptions* aOptions,
1580
                                       nsNavHistoryResultNode** aNode)
1581
0
{
1582
0
  BookmarkData bookmark;
1583
0
  nsresult rv = FetchItemInfo(aGUID, bookmark);
1584
0
  NS_ENSURE_SUCCESS(rv, rv);
1585
0
1586
0
  if (bookmark.type == TYPE_FOLDER) { // TYPE_FOLDER
1587
0
    *aNode = new nsNavHistoryFolderResultNode(bookmark.title,
1588
0
                                              aOptions,
1589
0
                                              bookmark.id);
1590
0
  }
1591
0
  else {
1592
0
    return NS_ERROR_INVALID_ARG;
1593
0
  }
1594
0
1595
0
  (*aNode)->mDateAdded = bookmark.dateAdded;
1596
0
  (*aNode)->mLastModified = bookmark.lastModified;
1597
0
  (*aNode)->mBookmarkGuid = bookmark.guid;
1598
0
  (*aNode)->GetAsFolder()->mTargetFolderGuid = bookmark.guid;
1599
0
1600
0
  NS_ADDREF(*aNode);
1601
0
  return NS_OK;
1602
0
}
1603
1604
1605
nsresult
1606
nsNavBookmarks::QueryFolderChildren(
1607
  int64_t aFolderId,
1608
  nsNavHistoryQueryOptions* aOptions,
1609
  nsCOMArray<nsNavHistoryResultNode>* aChildren)
1610
0
{
1611
0
  NS_ENSURE_ARG_POINTER(aOptions);
1612
0
  NS_ENSURE_ARG_POINTER(aChildren);
1613
0
1614
0
  // Select all children of a given folder, sorted by position.
1615
0
  // This is a LEFT JOIN because not all bookmarks types have a place.
1616
0
  // We construct a result where the first columns exactly match those returned
1617
0
  // by mDBGetURLPageInfo, and additionally contains columns for position,
1618
0
  // item_child, and folder_child from moz_bookmarks.
1619
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1620
0
    "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
1621
0
           "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
1622
0
           "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
1623
0
           "b.guid, b.position, b.type, b.fk "
1624
0
    "FROM moz_bookmarks b "
1625
0
    "LEFT JOIN moz_places h ON b.fk = h.id "
1626
0
    "WHERE b.parent = :parent "
1627
0
      "AND (NOT :excludeItems OR "
1628
0
           "b.type = :folder OR "
1629
0
           "h.url_hash BETWEEN hash('place', 'prefix_lo') AND hash('place', 'prefix_hi')) "
1630
0
    "ORDER BY b.position ASC"
1631
0
  );
1632
0
  NS_ENSURE_STATE(stmt);
1633
0
  mozStorageStatementScoper scoper(stmt);
1634
0
1635
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1636
0
  NS_ENSURE_SUCCESS(rv, rv);
1637
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("folder"), TYPE_FOLDER);
1638
0
  NS_ENSURE_SUCCESS(rv, rv);
1639
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("excludeItems"), aOptions->ExcludeItems());
1640
0
  NS_ENSURE_SUCCESS(rv, rv);
1641
0
1642
0
  nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
1643
0
  NS_ENSURE_SUCCESS(rv, rv);
1644
0
1645
0
  int32_t index = -1;
1646
0
  bool hasResult;
1647
0
  while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1648
0
    rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
1649
0
    NS_ENSURE_SUCCESS(rv, rv);
1650
0
  }
1651
0
1652
0
  return NS_OK;
1653
0
}
1654
1655
1656
nsresult
1657
nsNavBookmarks::ProcessFolderNodeRow(
1658
  mozIStorageValueArray* aRow,
1659
  nsNavHistoryQueryOptions* aOptions,
1660
  nsCOMArray<nsNavHistoryResultNode>* aChildren,
1661
  int32_t& aCurrentIndex)
1662
0
{
1663
0
  NS_ENSURE_ARG_POINTER(aRow);
1664
0
  NS_ENSURE_ARG_POINTER(aOptions);
1665
0
  NS_ENSURE_ARG_POINTER(aChildren);
1666
0
1667
0
  // The results will be in order of aCurrentIndex. Even if we don't add a node
1668
0
  // because it was excluded, we need to count its index, so do that before
1669
0
  // doing anything else.
1670
0
  aCurrentIndex++;
1671
0
1672
0
  int32_t itemType;
1673
0
  nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
1674
0
  NS_ENSURE_SUCCESS(rv, rv);
1675
0
  int64_t id;
1676
0
  rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
1677
0
  NS_ENSURE_SUCCESS(rv, rv);
1678
0
1679
0
  RefPtr<nsNavHistoryResultNode> node;
1680
0
1681
0
  if (itemType == TYPE_BOOKMARK) {
1682
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
1683
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1684
0
    rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
1685
0
    NS_ENSURE_SUCCESS(rv, rv);
1686
0
    uint32_t nodeType;
1687
0
    node->GetType(&nodeType);
1688
0
    if (nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
1689
0
        aOptions->ExcludeQueries()) {
1690
0
      return NS_OK;
1691
0
    }
1692
0
  }
1693
0
  else if (itemType == TYPE_FOLDER) {
1694
0
    // ExcludeReadOnlyFolders currently means "ExcludeLivemarks" (to be fixed in
1695
0
    // bug 1072833)
1696
0
    if (aOptions->ExcludeReadOnlyFolders()) {
1697
0
      if (IsLivemark(id))
1698
0
        return NS_OK;
1699
0
    }
1700
0
1701
0
    nsAutoCString title;
1702
0
    bool isNull;
1703
0
    rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
1704
0
    NS_ENSURE_SUCCESS(rv, rv);
1705
0
    if (!isNull) {
1706
0
      rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
1707
0
      NS_ENSURE_SUCCESS(rv, rv);
1708
0
    }
1709
0
1710
0
    // Don't use options from the parent to build the new folder node, it will
1711
0
    // inherit those later when it's inserted in the result.
1712
0
    node = new nsNavHistoryFolderResultNode(title, new nsNavHistoryQueryOptions(), id);
1713
0
1714
0
    rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
1715
0
    NS_ENSURE_SUCCESS(rv, rv);
1716
0
    node->GetAsFolder()->mTargetFolderGuid = node->mBookmarkGuid;
1717
0
1718
0
    rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
1719
0
                        reinterpret_cast<int64_t*>(&node->mDateAdded));
1720
0
    NS_ENSURE_SUCCESS(rv, rv);
1721
0
    rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
1722
0
                        reinterpret_cast<int64_t*>(&node->mLastModified));
1723
0
    NS_ENSURE_SUCCESS(rv, rv);
1724
0
  }
1725
0
  else {
1726
0
    // This is a separator.
1727
0
    node = new nsNavHistorySeparatorResultNode();
1728
0
1729
0
    node->mItemId = id;
1730
0
    rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
1731
0
    NS_ENSURE_SUCCESS(rv, rv);
1732
0
    rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
1733
0
                        reinterpret_cast<int64_t*>(&node->mDateAdded));
1734
0
    NS_ENSURE_SUCCESS(rv, rv);
1735
0
    rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
1736
0
                        reinterpret_cast<int64_t*>(&node->mLastModified));
1737
0
    NS_ENSURE_SUCCESS(rv, rv);
1738
0
  }
1739
0
1740
0
  // Store the index of the node within this container.  Note that this is not
1741
0
  // moz_bookmarks.position.
1742
0
  node->mBookmarkIndex = aCurrentIndex;
1743
0
1744
0
  NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
1745
0
  return NS_OK;
1746
0
}
1747
1748
1749
nsresult
1750
nsNavBookmarks::QueryFolderChildrenAsync(
1751
  nsNavHistoryFolderResultNode* aNode,
1752
  mozIStoragePendingStatement** _pendingStmt)
1753
0
{
1754
0
  NS_ENSURE_ARG_POINTER(aNode);
1755
0
  NS_ENSURE_ARG_POINTER(_pendingStmt);
1756
0
1757
0
  // Select all children of a given folder, sorted by position.
1758
0
  // This is a LEFT JOIN because not all bookmarks types have a place.
1759
0
  // We construct a result where the first columns exactly match those returned
1760
0
  // by mDBGetURLPageInfo, and additionally contains columns for position,
1761
0
  // item_child, and folder_child from moz_bookmarks.
1762
0
  nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
1763
0
    "SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
1764
0
           "h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
1765
0
           "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
1766
0
           "b.guid, b.position, b.type, b.fk "
1767
0
    "FROM moz_bookmarks b "
1768
0
    "LEFT JOIN moz_places h ON b.fk = h.id "
1769
0
    "WHERE b.parent = :parent "
1770
0
      "AND (NOT :excludeItems OR "
1771
0
           "b.type = :folder OR "
1772
0
           "h.url_hash BETWEEN hash('place', 'prefix_lo') AND hash('place', 'prefix_hi')) "
1773
0
    "ORDER BY b.position ASC"
1774
0
  );
1775
0
  NS_ENSURE_STATE(stmt);
1776
0
1777
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aNode->mTargetFolderItemId);
1778
0
  NS_ENSURE_SUCCESS(rv, rv);
1779
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("folder"), TYPE_FOLDER);
1780
0
  NS_ENSURE_SUCCESS(rv, rv);
1781
0
  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("excludeItems"), aNode->mOptions->ExcludeItems());
1782
0
  NS_ENSURE_SUCCESS(rv, rv);
1783
0
1784
0
  nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
1785
0
  rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
1786
0
  NS_ENSURE_SUCCESS(rv, rv);
1787
0
1788
0
  NS_IF_ADDREF(*_pendingStmt = pendingStmt);
1789
0
  return NS_OK;
1790
0
}
1791
1792
1793
nsresult
1794
nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
1795
                                int32_t* _folderCount,
1796
                                nsACString& _guid,
1797
                                int64_t* _parentId)
1798
0
{
1799
0
  *_folderCount = 0;
1800
0
  *_parentId = -1;
1801
0
1802
0
  // This query has to always return results, so it can't be written as a join,
1803
0
  // though a left join of 2 subqueries would have the same cost.
1804
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1805
0
    "SELECT count(*), "
1806
0
            "(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
1807
0
            "(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
1808
0
    "FROM moz_bookmarks "
1809
0
    "WHERE parent = :parent"
1810
0
  );
1811
0
  NS_ENSURE_STATE(stmt);
1812
0
  mozStorageStatementScoper scoper(stmt);
1813
0
1814
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1815
0
  NS_ENSURE_SUCCESS(rv, rv);
1816
0
1817
0
  bool hasResult;
1818
0
  rv = stmt->ExecuteStep(&hasResult);
1819
0
  NS_ENSURE_SUCCESS(rv, rv);
1820
0
  NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
1821
0
1822
0
  // Ensure that the folder we are looking for exists.
1823
0
  // Can't rely only on parent, since the root has parent 0, that doesn't exist.
1824
0
  bool isNull;
1825
0
  rv = stmt->GetIsNull(2, &isNull);
1826
0
  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
1827
0
                 NS_ERROR_INVALID_ARG);
1828
0
1829
0
  rv = stmt->GetInt32(0, _folderCount);
1830
0
  NS_ENSURE_SUCCESS(rv, rv);
1831
0
  if (!isNull) {
1832
0
    rv = stmt->GetUTF8String(1, _guid);
1833
0
    NS_ENSURE_SUCCESS(rv, rv);
1834
0
    rv = stmt->GetInt64(2, _parentId);
1835
0
    NS_ENSURE_SUCCESS(rv, rv);
1836
0
  }
1837
0
1838
0
  return NS_OK;
1839
0
}
1840
1841
1842
NS_IMETHODIMP
1843
nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
1844
0
{
1845
0
  NS_ENSURE_ARG_MIN(aItemId, 1);
1846
0
  NS_ENSURE_ARG_POINTER(_parentId);
1847
0
1848
0
  BookmarkData bookmark;
1849
0
  nsresult rv = FetchItemInfo(aItemId, bookmark);
1850
0
  NS_ENSURE_SUCCESS(rv, rv);
1851
0
1852
0
  // this should not happen, but see bug #400448 for details
1853
0
  NS_ENSURE_TRUE(bookmark.id != bookmark.parentId, NS_ERROR_UNEXPECTED);
1854
0
1855
0
  *_parentId = bookmark.parentId;
1856
0
  return NS_OK;
1857
0
}
1858
1859
nsresult
1860
nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
1861
                                   nsTArray<BookmarkData>& aBookmarks)
1862
0
{
1863
0
  NS_ENSURE_ARG(aURI);
1864
0
1865
0
  // Double ordering covers possible lastModified ties, that could happen when
1866
0
  // importing, syncing or due to extensions.
1867
0
  // Note: not using a JOIN is cheaper in this case.
1868
0
  nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1869
0
    "/* do not warn (bug 1175249) */ "
1870
0
    "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent, b.syncStatus "
1871
0
    "FROM moz_bookmarks b "
1872
0
    "JOIN moz_bookmarks t on t.id = b.parent "
1873
0
    "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
1874
0
    "ORDER BY b.lastModified DESC, b.id DESC "
1875
0
  );
1876
0
  NS_ENSURE_STATE(stmt);
1877
0
  mozStorageStatementScoper scoper(stmt);
1878
0
1879
0
  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
1880
0
  NS_ENSURE_SUCCESS(rv, rv);
1881
0
1882
0
  int64_t tagsRootId = TagsRootId();
1883
0
1884
0
  bool more;
1885
0
  nsAutoString tags;
1886
0
  while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
1887
0
    // Skip tags.
1888
0
    int64_t grandParentId;
1889
0
    nsresult rv = stmt->GetInt64(5, &grandParentId);
1890
0
    NS_ENSURE_SUCCESS(rv, rv);
1891
0
    if (grandParentId == tagsRootId) {
1892
0
      continue;
1893
0
    }
1894
0
1895
0
    BookmarkData bookmark;
1896
0
    bookmark.grandParentId = grandParentId;
1897
0
    rv = stmt->GetInt64(0, &bookmark.id);
1898
0
    NS_ENSURE_SUCCESS(rv, rv);
1899
0
    rv = stmt->GetUTF8String(1, bookmark.guid);
1900
0
    NS_ENSURE_SUCCESS(rv, rv);
1901
0
    rv = stmt->GetInt64(2, &bookmark.parentId);
1902
0
    NS_ENSURE_SUCCESS(rv, rv);
1903
0
    rv = stmt->GetInt64(3, reinterpret_cast<int64_t*>(&bookmark.lastModified));
1904
0
    NS_ENSURE_SUCCESS(rv, rv);
1905
0
    rv = stmt->GetUTF8String(4, bookmark.parentGuid);
1906
0
    NS_ENSURE_SUCCESS(rv, rv);
1907
0
    rv = stmt->GetInt32(6, &bookmark.syncStatus);
1908
0
    NS_ENSURE_SUCCESS(rv, rv);
1909
0
1910
0
    NS_ENSURE_TRUE(aBookmarks.AppendElement(bookmark), NS_ERROR_OUT_OF_MEMORY);
1911
0
  }
1912
0
1913
0
  return NS_OK;
1914
0
}
1915
1916
1917
1918
NS_IMETHODIMP
1919
nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
1920
                            bool aOwnsWeak)
1921
0
{
1922
0
  NS_ENSURE_ARG(aObserver);
1923
0
1924
0
  if (NS_WARN_IF(!mCanNotify))
1925
0
    return NS_ERROR_UNEXPECTED;
1926
0
1927
0
  return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
1928
0
}
1929
1930
1931
NS_IMETHODIMP
1932
nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
1933
0
{
1934
0
  return mObservers.RemoveWeakElement(aObserver);
1935
0
}
1936
1937
NS_IMETHODIMP
1938
nsNavBookmarks::GetObservers(uint32_t* _count,
1939
                             nsINavBookmarkObserver*** _observers)
1940
0
{
1941
0
  NS_ENSURE_ARG_POINTER(_count);
1942
0
  NS_ENSURE_ARG_POINTER(_observers);
1943
0
1944
0
  *_count = 0;
1945
0
  *_observers = nullptr;
1946
0
1947
0
  if (!mCanNotify)
1948
0
    return NS_OK;
1949
0
1950
0
  nsCOMArray<nsINavBookmarkObserver> observers;
1951
0
1952
0
  for (uint32_t i = 0; i < mObservers.Length(); ++i) {
1953
0
    const nsCOMPtr<nsINavBookmarkObserver> &observer = mObservers.ElementAt(i).GetValue();
1954
0
    // Skip nullified weak observers.
1955
0
    if (observer)
1956
0
      observers.AppendElement(observer);
1957
0
  }
1958
0
1959
0
  if (observers.Count() == 0)
1960
0
    return NS_OK;
1961
0
1962
0
  *_count = observers.Count();
1963
0
  observers.Forget(_observers);
1964
0
1965
0
  return NS_OK;
1966
0
}
1967
1968
void
1969
nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
1970
0
{
1971
0
  nsCOMPtr<nsIURI> uri;
1972
0
  MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aData.bookmark.url));
1973
0
  // Notify the visit only if we have a valid uri, otherwise the observer
1974
0
  // couldn't gather enough data from the notification.
1975
0
  // This should be false only if there's a bug in the code preceding us.
1976
0
  if (uri) {
1977
0
    NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavBookmarkObserver,
1978
0
                     OnItemVisited(aData.bookmark.id,
1979
0
                                   aData.visitId,
1980
0
                                   aData.time,
1981
0
                                   aData.transitionType,
1982
0
                                   uri,
1983
0
                                   aData.bookmark.parentId,
1984
0
                                   aData.bookmark.guid,
1985
0
                                   aData.bookmark.parentGuid));
1986
0
  }
1987
0
}
1988
1989
void
1990
nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
1991
0
{
1992
0
  // A guid must always be defined.
1993
0
  MOZ_ASSERT(!aData.bookmark.guid.IsEmpty());
1994
0
1995
0
  PRTime lastModified = aData.bookmark.lastModified;
1996
0
  if (aData.updateLastModified) {
1997
0
    lastModified = RoundedPRNow();
1998
0
    MOZ_ALWAYS_SUCCEEDS(SetItemDateInternal(
1999
0
      LAST_MODIFIED, DetermineSyncChangeDelta(aData.source),
2000
0
      aData.bookmark.id, lastModified));
2001
0
  }
2002
0
2003
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavBookmarkObserver,
2004
0
                   OnItemChanged(aData.bookmark.id,
2005
0
                                 aData.property,
2006
0
                                 aData.isAnnotation,
2007
0
                                 aData.newValue,
2008
0
                                 lastModified,
2009
0
                                 aData.bookmark.type,
2010
0
                                 aData.bookmark.parentId,
2011
0
                                 aData.bookmark.guid,
2012
0
                                 aData.bookmark.parentGuid,
2013
0
                                 aData.oldValue,
2014
0
                                 aData.source));
2015
0
}
2016
2017
////////////////////////////////////////////////////////////////////////////////
2018
//// nsIObserver
2019
2020
NS_IMETHODIMP
2021
nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
2022
                        const char16_t *aData)
2023
0
{
2024
0
  NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2025
0
2026
0
  if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
2027
0
    // Don't even try to notify observers from this point on, the category
2028
0
    // cache would init services that could try to use our APIs.
2029
0
    mCanNotify = false;
2030
0
    mObservers.Clear();
2031
0
  }
2032
0
2033
0
  return NS_OK;
2034
0
}
2035
2036
////////////////////////////////////////////////////////////////////////////////
2037
//// nsINavHistoryObserver
2038
2039
NS_IMETHODIMP
2040
nsNavBookmarks::OnBeginUpdateBatch()
2041
0
{
2042
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers,
2043
0
                   nsINavBookmarkObserver, OnBeginUpdateBatch());
2044
0
  return NS_OK;
2045
0
}
2046
2047
2048
NS_IMETHODIMP
2049
nsNavBookmarks::OnEndUpdateBatch()
2050
0
{
2051
0
  NOTIFY_OBSERVERS(mCanNotify, mObservers,
2052
0
                   nsINavBookmarkObserver, OnEndUpdateBatch());
2053
0
  return NS_OK;
2054
0
}
2055
2056
2057
void
2058
nsNavBookmarks::HandlePlacesEvent(const PlacesEventSequence& aEvents)
2059
0
{
2060
0
  for (const auto& event : aEvents) {
2061
0
    if (NS_WARN_IF(event->Type() != PlacesEventType::Page_visited)) {
2062
0
      continue;
2063
0
    }
2064
0
2065
0
    const dom::PlacesVisit* visit = event->AsPlacesVisit();
2066
0
    if (NS_WARN_IF(!visit)) {
2067
0
      continue;
2068
0
    }
2069
0
2070
0
    ItemVisitData visitData;
2071
0
    visitData.visitId = visit->mVisitId;
2072
0
    visitData.bookmark.url = NS_ConvertUTF16toUTF8(visit->mUrl);
2073
0
    visitData.time = visit->mVisitTime * 1000;
2074
0
    visitData.transitionType = visit->mTransitionType;
2075
0
    RefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
2076
0
      new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
2077
0
    notifier->Init();
2078
0
  }
2079
0
}
2080
2081
2082
NS_IMETHODIMP
2083
nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
2084
                            const nsACString& aGUID,
2085
                            uint16_t aReason)
2086
0
{
2087
0
  return NS_OK;
2088
0
}
2089
2090
2091
NS_IMETHODIMP
2092
nsNavBookmarks::OnClearHistory()
2093
0
{
2094
0
  // TODO(bryner): we should notify on visited-time change for all URIs
2095
0
  return NS_OK;
2096
0
}
2097
2098
2099
NS_IMETHODIMP
2100
nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
2101
                               const nsAString& aPageTitle,
2102
                               const nsACString& aGUID)
2103
0
{
2104
0
  // NOOP. We don't consume page titles from moz_places anymore.
2105
0
  // Title-change notifications are sent from SetItemTitle.
2106
0
  return NS_OK;
2107
0
}
2108
2109
2110
NS_IMETHODIMP
2111
nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
2112
                                  int32_t aNewFrecency,
2113
                                  const nsACString& aGUID,
2114
                                  bool aHidden,
2115
                                  PRTime aLastVisitDate)
2116
0
{
2117
0
  return NS_OK;
2118
0
}
2119
2120
2121
NS_IMETHODIMP
2122
nsNavBookmarks::OnManyFrecenciesChanged()
2123
0
{
2124
0
  return NS_OK;
2125
0
}
2126
2127
2128
NS_IMETHODIMP
2129
nsNavBookmarks::OnPageChanged(nsIURI* aURI,
2130
                              uint32_t aChangedAttribute,
2131
                              const nsAString& aNewValue,
2132
                              const nsACString& aGUID)
2133
0
{
2134
0
  NS_ENSURE_ARG(aURI);
2135
0
2136
0
  nsresult rv;
2137
0
  if (aChangedAttribute == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
2138
0
    ItemChangeData changeData;
2139
0
    rv = aURI->GetSpec(changeData.bookmark.url);
2140
0
    NS_ENSURE_SUCCESS(rv, rv);
2141
0
    changeData.property = NS_LITERAL_CSTRING("favicon");
2142
0
    changeData.isAnnotation = false;
2143
0
    changeData.newValue = NS_ConvertUTF16toUTF8(aNewValue);
2144
0
    changeData.bookmark.lastModified = 0;
2145
0
    changeData.bookmark.type = TYPE_BOOKMARK;
2146
0
2147
0
    // Favicons may be set to either pure URIs or to folder URIs
2148
0
    bool isPlaceURI;
2149
0
    rv = aURI->SchemeIs("place", &isPlaceURI);
2150
0
    NS_ENSURE_SUCCESS(rv, rv);
2151
0
    if (isPlaceURI) {
2152
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
2153
0
      NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2154
0
2155
0
      nsCOMPtr<nsINavHistoryQuery> query;
2156
0
      nsCOMPtr<nsINavHistoryQueryOptions> options;
2157
0
      rv = history->QueryStringToQuery(changeData.bookmark.url,
2158
0
                                       getter_AddRefs(query),
2159
0
                                       getter_AddRefs(options));
2160
0
      NS_ENSURE_SUCCESS(rv, rv);
2161
0
2162
0
      RefPtr<nsNavHistoryQuery> queryObj = do_QueryObject(query);
2163
0
      if (queryObj->Parents().Length() == 1) {
2164
0
        // Fetch missing data.
2165
0
        rv = FetchItemInfo(queryObj->Parents()[0], changeData.bookmark);
2166
0
        NS_ENSURE_SUCCESS(rv, rv);
2167
0
        NotifyItemChanged(changeData);
2168
0
      }
2169
0
    }
2170
0
    else {
2171
0
      RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
2172
0
        new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
2173
0
      notifier->Init();
2174
0
    }
2175
0
  }
2176
0
  return NS_OK;
2177
0
}
2178
2179
2180
NS_IMETHODIMP
2181
nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, bool aPartialRemoval,
2182
                               const nsACString& aGUID,
2183
                               uint16_t aReason, uint32_t aTransitionType)
2184
0
{
2185
0
  NS_ENSURE_ARG(aURI);
2186
0
2187
0
  // Notify "cleartime" only if all visits to the page have been removed.
2188
0
  if (!aPartialRemoval) {
2189
0
    // If the page is bookmarked, notify observers for each associated bookmark.
2190
0
    ItemChangeData changeData;
2191
0
    nsresult rv = aURI->GetSpec(changeData.bookmark.url);
2192
0
    NS_ENSURE_SUCCESS(rv, rv);
2193
0
    changeData.property = NS_LITERAL_CSTRING("cleartime");
2194
0
    changeData.isAnnotation = false;
2195
0
    changeData.bookmark.lastModified = 0;
2196
0
    changeData.bookmark.type = TYPE_BOOKMARK;
2197
0
2198
0
    RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
2199
0
      new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
2200
0
    notifier->Init();
2201
0
  }
2202
0
  return NS_OK;
2203
0
}