Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/places/nsNavHistoryResult.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include <stdio.h>
8
#include "nsNavHistory.h"
9
#include "nsNavBookmarks.h"
10
#include "nsFaviconService.h"
11
#include "nsITaggingService.h"
12
#include "nsAnnotationService.h"
13
#include "Helpers.h"
14
#include "mozilla/DebugOnly.h"
15
#include "nsDebug.h"
16
#include "nsNetUtil.h"
17
#include "nsString.h"
18
#include "nsReadableUtils.h"
19
#include "nsUnicharUtils.h"
20
#include "prtime.h"
21
#include "nsQueryObject.h"
22
#include "mozilla/dom/PlacesObservers.h"
23
#include "mozilla/dom/PlacesVisit.h"
24
25
#include "nsCycleCollectionParticipant.h"
26
27
// Thanks, Windows.h :(
28
#undef CompareString
29
30
#define TO_ICONTAINER(_node)                                                  \
31
    static_cast<nsINavHistoryContainerResultNode*>(_node)
32
33
#define TO_CONTAINER(_node)                                                   \
34
0
    static_cast<nsNavHistoryContainerResultNode*>(_node)
35
36
#define NOTIFY_RESULT_OBSERVERS_RET(_result, _method, _ret)                   \
37
0
  PR_BEGIN_MACRO                                                              \
38
0
  NS_ENSURE_TRUE(_result, _ret);                                              \
39
0
  if (!_result->mSuppressNotifications) {                                     \
40
0
    ENUMERATE_WEAKARRAY(_result->mObservers, nsINavHistoryResultObserver,     \
41
0
                        _method)                                              \
42
0
  }                                                                           \
43
0
  PR_END_MACRO
44
45
#define NOTIFY_RESULT_OBSERVERS(_result, _method)                             \
46
0
  NOTIFY_RESULT_OBSERVERS_RET(_result, _method, NS_ERROR_UNEXPECTED)
47
48
// What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors,
49
// but some of our classes (like nsNavHistoryResult) have an ambiguous base
50
// class of nsISupports which prevents this from working (the default macro
51
// converts it to nsISupports, then addrefs it, then returns it). Therefore, we
52
// expand the macro here and change it so that it works. Yuck.
53
#define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \
54
0
  if (aIID.Equals(NS_GET_IID(_class))) { \
55
0
    NS_ADDREF(this); \
56
0
    *aInstancePtr = this; \
57
0
    return NS_OK; \
58
0
  } else
59
60
// Number of changes to handle separately in a batch.  If more changes are
61
// requested the node will switch to full refresh mode.
62
0
#define MAX_BATCH_CHANGES_BEFORE_REFRESH 5
63
64
using namespace mozilla;
65
using namespace mozilla::places;
66
67
namespace {
68
69
/**
70
 * Returns conditions for query update.
71
 *  QUERYUPDATE_TIME:
72
 *    This query is only limited by an inclusive time range on the first
73
 *    query object. The caller can quickly evaluate the time itself if it
74
 *    chooses. This is even simpler than "simple" below.
75
 *  QUERYUPDATE_SIMPLE:
76
 *    This query is evaluatable using evaluateQueryForNode to do live
77
 *    updating.
78
 *  QUERYUPDATE_COMPLEX:
79
 *    This query is not evaluatable using evaluateQueryForNode. When something
80
 *    happens that this query updates, you will need to re-run the query.
81
 *  QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
82
 *    A complex query that additionally has dependence on bookmarks. All
83
 *    bookmark-dependent queries fall under this category.
84
 *  QUERYUPDATE_MOBILEPREF:
85
 *    A complex query but only updates when the mobile preference changes.
86
 *  QUERYUPDATE_NONE:
87
 *    A query that never updates, e.g. the left-pane root query.
88
 *
89
 *  aHasSearchTerms will be set to true if the query has any dependence on
90
 *  keywords. When there is no dependence on keywords, we can handle title
91
 *  change operations as simple instead of complex.
92
 */
93
uint32_t
94
getUpdateRequirements(const RefPtr<nsNavHistoryQuery>& aQuery,
95
                      const RefPtr<nsNavHistoryQueryOptions>& aOptions,
96
                      bool* aHasSearchTerms)
97
0
{
98
0
  // first check if there are search terms
99
0
  bool hasSearchTerms = *aHasSearchTerms = !aQuery->SearchTerms().IsEmpty();
100
0
101
0
  bool nonTimeBasedItems = false;
102
0
  bool domainBasedItems = false;
103
0
104
0
  if (aQuery->Parents().Length() > 0 ||
105
0
      aQuery->OnlyBookmarked() ||
106
0
      aQuery->Tags().Length() > 0 ||
107
0
      (aOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
108
0
        hasSearchTerms)) {
109
0
    return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
110
0
  }
111
0
112
0
  // Note: we don't currently have any complex non-bookmarked items, but these
113
0
  // are expected to be added. Put detection of these items here.
114
0
  if (hasSearchTerms ||
115
0
      !aQuery->Domain().IsVoid() ||
116
0
      aQuery->Uri() != nullptr)
117
0
    nonTimeBasedItems = true;
118
0
119
0
  if (!aQuery->Domain().IsVoid())
120
0
    domainBasedItems = true;
121
0
122
0
  if (aOptions->ResultType() ==
123
0
        nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT)
124
0
      return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
125
0
126
0
  if (aOptions->ResultType() ==
127
0
        nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
128
0
      return QUERYUPDATE_MOBILEPREF;
129
0
130
0
  if (aOptions->ResultType() ==
131
0
        nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY)
132
0
      return QUERYUPDATE_NONE;
133
0
134
0
  // Whenever there is a maximum number of results,
135
0
  // and we are not a bookmark query we must requery. This
136
0
  // is because we can't generally know if any given addition/change causes
137
0
  // the item to be in the top N items in the database.
138
0
  uint16_t sortingMode = aOptions->SortingMode();
139
0
  if (aOptions->MaxResults() > 0 &&
140
0
      sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
141
0
      sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
142
0
    return QUERYUPDATE_COMPLEX;
143
0
144
0
  if (domainBasedItems)
145
0
    return QUERYUPDATE_HOST;
146
0
  if (!nonTimeBasedItems)
147
0
    return QUERYUPDATE_TIME;
148
0
149
0
  return QUERYUPDATE_SIMPLE;
150
0
}
151
152
/**
153
 * We might have interesting encodings and different case in the host name.
154
 * This will convert that host name into an ASCII host name by sending it
155
 * through the URI canonicalization. The result can be used for comparison
156
 * with other ASCII host name strings.
157
 */
158
nsresult
159
asciiHostNameFromHostString(const nsACString& aHostName,
160
                                          nsACString& aAscii)
161
0
{
162
0
  aAscii.Truncate();
163
0
  if (aHostName.IsEmpty()) {
164
0
    return NS_OK;
165
0
  }
166
0
  // To properly generate a uri we must provide a protocol.
167
0
  nsAutoCString fakeURL("http://");
168
0
  fakeURL.Append(aHostName);
169
0
  nsCOMPtr<nsIURI> uri;
170
0
  nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL);
171
0
  NS_ENSURE_SUCCESS(rv, rv);
172
0
  rv = uri->GetAsciiHost(aAscii);
173
0
  NS_ENSURE_SUCCESS(rv, rv);
174
0
  return NS_OK;
175
0
}
176
177
/**
178
 * This runs the node through the given query to see if satisfies the
179
 * query conditions. Not every query parameters are handled by this code,
180
 * but we handle the most common ones so that performance is better.
181
 * We assume that the time on the node is the time that we want to compare.
182
 * This is not necessarily true because URL nodes have the last access time,
183
 * which is not necessarily the same. However, since this is being called
184
 * to update the list, we assume that the last access time is the current
185
 * access time that we are being asked to compare so it works out.
186
 * Returns true if node matches the query, false if not.
187
 */
188
bool
189
evaluateQueryForNode(const RefPtr<nsNavHistoryQuery>& aQuery,
190
                     const RefPtr<nsNavHistoryQueryOptions>& aOptions,
191
                     const RefPtr<nsNavHistoryResultNode>& aNode)
192
0
{
193
0
  // Hidden
194
0
  if (aNode->mHidden && !aOptions->IncludeHidden())
195
0
    return false;
196
0
197
0
  bool hasIt;
198
0
  // Begin time
199
0
  aQuery->GetHasBeginTime(&hasIt);
200
0
  if (hasIt) {
201
0
    PRTime beginTime = nsNavHistory::NormalizeTime(aQuery->BeginTimeReference(),
202
0
                                                   aQuery->BeginTime());
203
0
    if (aNode->mTime < beginTime)
204
0
      return false;
205
0
  }
206
0
207
0
  // End time
208
0
  aQuery->GetHasEndTime(&hasIt);
209
0
  if (hasIt) {
210
0
    PRTime endTime = nsNavHistory::NormalizeTime(aQuery->EndTimeReference(),
211
0
                                                 aQuery->EndTime());
212
0
    if (aNode->mTime > endTime)
213
0
      return false;
214
0
  }
215
0
216
0
  // Search terms
217
0
  if (!aQuery->SearchTerms().IsEmpty()) {
218
0
    // we can use the existing filtering code, just give it our one object in
219
0
    // an array.
220
0
    nsCOMArray<nsNavHistoryResultNode> inputSet;
221
0
    inputSet.AppendObject(aNode);
222
0
    nsCOMArray<nsNavHistoryResultNode> filteredSet;
223
0
    nsresult rv = nsNavHistory::FilterResultSet(nullptr, inputSet, &filteredSet, aQuery, aOptions);
224
0
    if (NS_FAILED(rv))
225
0
      return false;
226
0
    if (!filteredSet.Count())
227
0
      return false;
228
0
  }
229
0
230
0
  // Domain/host
231
0
  if (!aQuery->Domain().IsVoid()) {
232
0
    nsCOMPtr<nsIURI> nodeUri;
233
0
    if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
234
0
      return false;
235
0
    nsAutoCString asciiRequest;
236
0
    if (NS_FAILED(asciiHostNameFromHostString(aQuery->Domain(), asciiRequest)))
237
0
      return false;
238
0
    if (aQuery->DomainIsHost()) {
239
0
      nsAutoCString host;
240
0
      if (NS_FAILED(nodeUri->GetAsciiHost(host)))
241
0
        return false;
242
0
243
0
      if (!asciiRequest.Equals(host))
244
0
        return false;
245
0
    }
246
0
    // check domain names.
247
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
248
0
    if (!history)
249
0
      return false;
250
0
    nsAutoCString domain;
251
0
    history->DomainNameFromURI(nodeUri, domain);
252
0
    if (!asciiRequest.Equals(domain))
253
0
      return false;
254
0
  }
255
0
256
0
  // URI
257
0
  if (aQuery->Uri()) {
258
0
    nsCOMPtr<nsIURI> nodeUri;
259
0
    if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
260
0
      return false;
261
0
     bool equals;
262
0
    nsresult rv = aQuery->Uri()->Equals(nodeUri, &equals);
263
0
    NS_ENSURE_SUCCESS(rv, false);
264
0
    if (!equals)
265
0
      return false;
266
0
  }
267
0
268
0
  // Transitions
269
0
  const nsTArray<uint32_t>& transitions = aQuery->Transitions();
270
0
  if (aNode->mTransitionType > 0 &&
271
0
      transitions.Length() &&
272
0
      !transitions.Contains(aNode->mTransitionType)) {
273
0
    return false;
274
0
  }
275
0
276
0
  // If we ever make it to the bottom, that means it passed all the tests for
277
0
  // the given query.
278
0
  return true;
279
0
}
280
281
// Emulate string comparison (used for sorting) for PRTime and int.
282
inline int32_t ComparePRTime(PRTime a, PRTime b)
283
0
{
284
0
  if (a < b)
285
0
    return -1;
286
0
  else if (a > b)
287
0
    return 1;
288
0
  return 0;
289
0
}
290
inline int32_t CompareIntegers(uint32_t a, uint32_t b)
291
0
{
292
0
  return a - b;
293
0
}
294
295
} // anonymous namespace
296
297
NS_IMPL_CYCLE_COLLECTION(nsNavHistoryResultNode, mParent)
298
299
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResultNode)
300
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultNode)
301
0
  NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
302
0
NS_INTERFACE_MAP_END
303
304
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
305
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
306
307
nsNavHistoryResultNode::nsNavHistoryResultNode(
308
    const nsACString& aURI, const nsACString& aTitle, uint32_t aAccessCount,
309
    PRTime aTime) :
310
  mParent(nullptr),
311
  mURI(aURI),
312
  mTitle(aTitle),
313
  mAreTagsSorted(false),
314
  mAccessCount(aAccessCount),
315
  mTime(aTime),
316
  mBookmarkIndex(-1),
317
  mItemId(-1),
318
  mFolderId(-1),
319
  mVisitId(-1),
320
  mFromVisitId(-1),
321
  mDateAdded(0),
322
  mLastModified(0),
323
  mIndentLevel(-1),
324
  mFrecency(0),
325
  mHidden(false),
326
  mTransitionType(0)
327
0
{
328
0
  mTags.SetIsVoid(true);
329
0
}
330
331
332
NS_IMETHODIMP
333
nsNavHistoryResultNode::GetIcon(nsACString& aIcon)
334
0
{
335
0
  if (this->IsContainer() || mURI.IsEmpty()) {
336
0
    return NS_OK;
337
0
  }
338
0
339
0
  aIcon.AppendLiteral("page-icon:");
340
0
  aIcon.Append(mURI);
341
0
342
0
  return NS_OK;
343
0
}
344
345
346
NS_IMETHODIMP
347
nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent)
348
0
{
349
0
  NS_IF_ADDREF(*aParent = mParent);
350
0
  return NS_OK;
351
0
}
352
353
354
NS_IMETHODIMP
355
nsNavHistoryResultNode::GetParentResult(nsINavHistoryResult** aResult)
356
0
{
357
0
  *aResult = nullptr;
358
0
  if (IsContainer())
359
0
    NS_IF_ADDREF(*aResult = GetAsContainer()->mResult);
360
0
  else if (mParent)
361
0
    NS_IF_ADDREF(*aResult = mParent->mResult);
362
0
363
0
  NS_ENSURE_STATE(*aResult);
364
0
  return NS_OK;
365
0
}
366
367
368
NS_IMETHODIMP
369
0
nsNavHistoryResultNode::GetTags(nsAString& aTags) {
370
0
  // Only URI-nodes may be associated with tags
371
0
  if (!IsURI()) {
372
0
    aTags.Truncate();
373
0
    return NS_OK;
374
0
  }
375
0
376
0
  // Initially, the tags string is set to a void string (see constructor).  We
377
0
  // then build it the first time this method called is called (and by that,
378
0
  // implicitly unset the void flag). Result observers may re-set the void flag
379
0
  // in order to force rebuilding of the tags string.
380
0
  if (!mTags.IsVoid()) {
381
0
    // If mTags is assigned by a history query it is unsorted for performance
382
0
    // reasons, it must be sorted by name on first read access.
383
0
    if (!mAreTagsSorted) {
384
0
      nsTArray<nsCString> tags;
385
0
      ParseString(NS_ConvertUTF16toUTF8(mTags), ',', tags);
386
0
      tags.Sort();
387
0
      mTags.SetIsVoid(true);
388
0
      for (nsTArray<nsCString>::index_type i = 0; i < tags.Length(); ++i) {
389
0
        AppendUTF8toUTF16(tags[i], mTags);
390
0
        if (i < tags.Length() - 1 )
391
0
          mTags.AppendLiteral(", ");
392
0
      }
393
0
      mAreTagsSorted = true;
394
0
    }
395
0
    aTags.Assign(mTags);
396
0
    return NS_OK;
397
0
  }
398
0
399
0
  // Fetch the tags
400
0
  RefPtr<Database> DB = Database::GetDatabase();
401
0
  NS_ENSURE_STATE(DB);
402
0
  nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
403
0
    "/* do not warn (bug 487594) */ "
404
0
    "SELECT GROUP_CONCAT(tag_title, ', ') "
405
0
    "FROM ( "
406
0
      "SELECT t.title AS tag_title "
407
0
      "FROM moz_bookmarks b "
408
0
      "JOIN moz_bookmarks t ON t.id = +b.parent "
409
0
      "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
410
0
        "AND t.parent = :tags_folder "
411
0
      "ORDER BY t.title COLLATE NOCASE ASC "
412
0
    ") "
413
0
  );
414
0
  NS_ENSURE_STATE(stmt);
415
0
  mozStorageStatementScoper scoper(stmt);
416
0
417
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
418
0
  NS_ENSURE_STATE(history);
419
0
  nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
420
0
                                      history->GetTagsFolder());
421
0
  NS_ENSURE_SUCCESS(rv, rv);
422
0
  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
423
0
  NS_ENSURE_SUCCESS(rv, rv);
424
0
425
0
  bool hasTags = false;
426
0
  if (NS_SUCCEEDED(stmt->ExecuteStep(&hasTags)) && hasTags) {
427
0
    rv = stmt->GetString(0, mTags);
428
0
    NS_ENSURE_SUCCESS(rv, rv);
429
0
    aTags.Assign(mTags);
430
0
    mAreTagsSorted = true;
431
0
  }
432
0
433
0
  // If this node is a child of a history query, we need to make sure changes
434
0
  // to tags are properly live-updated.
435
0
  if (mParent && mParent->IsQuery() &&
436
0
      mParent->mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
437
0
    nsNavHistoryQueryResultNode* query = mParent->GetAsQuery();
438
0
    nsNavHistoryResult* result = query->GetResult();
439
0
    NS_ENSURE_STATE(result);
440
0
    result->AddAllBookmarksObserver(query);
441
0
  }
442
0
443
0
  return NS_OK;
444
0
}
445
446
NS_IMETHODIMP
447
0
nsNavHistoryResultNode::GetPageGuid(nsACString& aPageGuid) {
448
0
  aPageGuid = mPageGuid;
449
0
  return NS_OK;
450
0
}
451
452
453
NS_IMETHODIMP
454
0
nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
455
0
  aBookmarkGuid = mBookmarkGuid;
456
0
  return NS_OK;
457
0
}
458
459
460
NS_IMETHODIMP
461
0
nsNavHistoryResultNode::GetVisitId(int64_t* aVisitId) {
462
0
  *aVisitId = mVisitId;
463
0
  return NS_OK;
464
0
}
465
466
467
NS_IMETHODIMP
468
0
nsNavHistoryResultNode::GetFromVisitId(int64_t* aFromVisitId) {
469
0
  *aFromVisitId = mFromVisitId;
470
0
  return NS_OK;
471
0
}
472
473
474
NS_IMETHODIMP
475
0
nsNavHistoryResultNode::GetVisitType(uint32_t* aVisitType) {
476
0
  *aVisitType = mTransitionType;
477
0
  return NS_OK;
478
0
}
479
480
481
void
482
nsNavHistoryResultNode::OnRemoving()
483
0
{
484
0
  mParent = nullptr;
485
0
}
486
487
488
/**
489
 * This will find the result for this node.  We can ask the nearest container
490
 * for this value (either ourselves or our parents should be a container,
491
 * and all containers have result pointers).
492
 *
493
 * @note The result may be null, if the container is detached from the result
494
 *       who owns it.
495
 */
496
nsNavHistoryResult*
497
nsNavHistoryResultNode::GetResult()
498
0
{
499
0
  nsNavHistoryResultNode* node = this;
500
0
  do {
501
0
    if (node->IsContainer()) {
502
0
      nsNavHistoryContainerResultNode* container = TO_CONTAINER(node);
503
0
      return container->mResult;
504
0
    }
505
0
    node = node->mParent;
506
0
  } while (node);
507
0
  MOZ_ASSERT(false, "No container node found in hierarchy!");
508
0
  return nullptr;
509
0
}
510
511
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode,
512
                                   mResult,
513
                                   mChildren)
514
515
NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
516
NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
517
518
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryContainerResultNode)
519
0
  NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
520
0
  NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
521
0
NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
522
523
nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
524
    const nsACString& aURI, const nsACString& aTitle,
525
    PRTime aTime, uint32_t aContainerType,
526
    nsNavHistoryQueryOptions* aOptions) :
527
  nsNavHistoryResultNode(aURI, aTitle, 0, aTime),
528
  mResult(nullptr),
529
  mContainerType(aContainerType),
530
  mExpanded(false),
531
  mOptions(aOptions),
532
  mAsyncCanceledState(NOT_CANCELED)
533
0
{
534
0
  MOZ_ASSERT(mOptions);
535
0
  MOZ_ALWAYS_SUCCEEDS(mOptions->Clone(getter_AddRefs(mOriginalOptions)));
536
0
}
537
538
539
nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
540
0
{
541
0
  // Explicitly clean up array of children of this container.  We must ensure
542
0
  // all references are gone and all of their destructors are called.
543
0
  mChildren.Clear();
544
0
}
545
546
547
/**
548
 * Containers should notify their children that they are being removed when the
549
 * container is being removed.
550
 */
551
void
552
nsNavHistoryContainerResultNode::OnRemoving()
553
0
{
554
0
  nsNavHistoryResultNode::OnRemoving();
555
0
  for (int32_t i = 0; i < mChildren.Count(); ++i)
556
0
    mChildren[i]->OnRemoving();
557
0
  mChildren.Clear();
558
0
  mResult = nullptr;
559
0
}
560
561
562
bool
563
nsNavHistoryContainerResultNode::AreChildrenVisible()
564
0
{
565
0
  nsNavHistoryResult* result = GetResult();
566
0
  if (!result) {
567
0
    MOZ_ASSERT_UNREACHABLE("Invalid result");
568
0
    return false;
569
0
  }
570
0
571
0
  if (!mExpanded)
572
0
    return false;
573
0
574
0
  // Now check if any ancestor is closed.
575
0
  nsNavHistoryContainerResultNode* ancestor = mParent;
576
0
  while (ancestor) {
577
0
    if (!ancestor->mExpanded)
578
0
      return false;
579
0
580
0
    ancestor = ancestor->mParent;
581
0
  }
582
0
583
0
  return true;
584
0
}
585
586
587
NS_IMETHODIMP
588
nsNavHistoryContainerResultNode::GetContainerOpen(bool *aContainerOpen)
589
0
{
590
0
  *aContainerOpen = mExpanded;
591
0
  return NS_OK;
592
0
}
593
594
595
NS_IMETHODIMP
596
nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen)
597
0
{
598
0
  if (aContainerOpen) {
599
0
    if (!mExpanded) {
600
0
      if (mOptions->AsyncEnabled())
601
0
        OpenContainerAsync();
602
0
      else
603
0
        OpenContainer();
604
0
    }
605
0
  }
606
0
  else {
607
0
    if (mExpanded)
608
0
      CloseContainer();
609
0
    else if (mAsyncPendingStmt)
610
0
      CancelAsyncOpen(false);
611
0
  }
612
0
613
0
  return NS_OK;
614
0
}
615
616
617
/**
618
 * Notifies the result's observers of a change in the container's state.  The
619
 * notification includes both the old and new states:  The old is aOldState, and
620
 * the new is the container's current state.
621
 *
622
 * @param aOldState
623
 *        The state being transitioned out of.
624
 */
625
nsresult
626
nsNavHistoryContainerResultNode::NotifyOnStateChange(uint16_t aOldState)
627
0
{
628
0
  nsNavHistoryResult* result = GetResult();
629
0
  NS_ENSURE_STATE(result);
630
0
631
0
  nsresult rv;
632
0
  uint16_t currState;
633
0
  rv = GetState(&currState);
634
0
  NS_ENSURE_SUCCESS(rv, rv);
635
0
636
0
  // Notify via the new ContainerStateChanged observer method.
637
0
  NOTIFY_RESULT_OBSERVERS(result,
638
0
                          ContainerStateChanged(this, aOldState, currState));
639
0
  return NS_OK;
640
0
}
641
642
643
NS_IMETHODIMP
644
nsNavHistoryContainerResultNode::GetState(uint16_t* _state)
645
0
{
646
0
  NS_ENSURE_ARG_POINTER(_state);
647
0
648
0
  *_state = mExpanded ? (uint16_t)STATE_OPENED
649
0
                      : mAsyncPendingStmt ? (uint16_t)STATE_LOADING
650
0
                                          : (uint16_t)STATE_CLOSED;
651
0
652
0
  return NS_OK;
653
0
}
654
655
656
/**
657
 * This handles the generic container case.  Other container types should
658
 * override this to do their own handling.
659
 */
660
nsresult
661
nsNavHistoryContainerResultNode::OpenContainer()
662
0
{
663
0
  NS_ASSERTION(!mExpanded, "Container must not be expanded to open it");
664
0
  mExpanded = true;
665
0
666
0
  nsresult rv = NotifyOnStateChange(STATE_CLOSED);
667
0
  NS_ENSURE_SUCCESS(rv, rv);
668
0
669
0
  return NS_OK;
670
0
}
671
672
673
/**
674
 * Unset aSuppressNotifications to notify observers on this change.  That is
675
 * the normal operation.  This is set to false for the recursive calls since the
676
 * root container that is being closed will handle recomputation of the visible
677
 * elements for its entire subtree.
678
 */
679
nsresult
680
nsNavHistoryContainerResultNode::CloseContainer(bool aSuppressNotifications)
681
0
{
682
0
  NS_ASSERTION((mExpanded && !mAsyncPendingStmt) ||
683
0
               (!mExpanded && mAsyncPendingStmt),
684
0
               "Container must be expanded or loading to close it");
685
0
686
0
  nsresult rv;
687
0
  uint16_t oldState;
688
0
  rv = GetState(&oldState);
689
0
  NS_ENSURE_SUCCESS(rv, rv);
690
0
691
0
  if (mExpanded) {
692
0
    // Recursively close all child containers.
693
0
    for (int32_t i = 0; i < mChildren.Count(); ++i) {
694
0
      if (mChildren[i]->IsContainer() &&
695
0
          mChildren[i]->GetAsContainer()->mExpanded)
696
0
        mChildren[i]->GetAsContainer()->CloseContainer(true);
697
0
    }
698
0
699
0
    mExpanded = false;
700
0
  }
701
0
702
0
  // Be sure to set this to null before notifying observers.  It signifies that
703
0
  // the container is no longer loading (if it was in the first place).
704
0
  mAsyncPendingStmt = nullptr;
705
0
706
0
  if (!aSuppressNotifications) {
707
0
    rv = NotifyOnStateChange(oldState);
708
0
    NS_ENSURE_SUCCESS(rv, rv);
709
0
  }
710
0
711
0
  // If this is the root container of a result, we can tell the result to stop
712
0
  // observing changes, otherwise the result will stay in memory and updates
713
0
  // itself till it is cycle collected.
714
0
  nsNavHistoryResult* result = GetResult();
715
0
  NS_ENSURE_STATE(result);
716
0
  if (result->mRootNode == this) {
717
0
    result->StopObserving();
718
0
    // When reopening this node its result will be out of sync.
719
0
    // We must clear our children to ensure we will call FillChildren
720
0
    // again in such a case.
721
0
    if (this->IsQuery())
722
0
      this->GetAsQuery()->ClearChildren(true);
723
0
    else if (this->IsFolder())
724
0
      this->GetAsFolder()->ClearChildren(true);
725
0
  }
726
0
727
0
  return NS_OK;
728
0
}
729
730
731
/**
732
 * The async version of OpenContainer.
733
 */
734
nsresult
735
nsNavHistoryContainerResultNode::OpenContainerAsync()
736
0
{
737
0
  return NS_ERROR_NOT_IMPLEMENTED;
738
0
}
739
740
741
/**
742
 * Cancels the pending asynchronous Storage execution triggered by
743
 * FillChildrenAsync, if it exists.  This method doesn't do much, because after
744
 * cancelation Storage will call this node's HandleCompletion callback, where
745
 * the real work is done.
746
 *
747
 * @param aRestart
748
 *        If true, async execution will be restarted by HandleCompletion.
749
 */
750
void
751
nsNavHistoryContainerResultNode::CancelAsyncOpen(bool aRestart)
752
0
{
753
0
  NS_ASSERTION(mAsyncPendingStmt, "Async execution canceled but not pending");
754
0
755
0
  mAsyncCanceledState = aRestart ? CANCELED_RESTART_NEEDED : CANCELED;
756
0
757
0
  // Cancel will fail if the pending statement has already been canceled.
758
0
  // That's OK since this method may be called multiple times, and multiple
759
0
  // cancels don't harm anything.
760
0
  (void)mAsyncPendingStmt->Cancel();
761
0
}
762
763
764
/**
765
 * This builds up tree statistics from the bottom up.  Call with a container
766
 * and the indent level of that container.  To init the full tree, call with
767
 * the root container.  The default indent level is -1, which is appropriate
768
 * for the root level.
769
 *
770
 * CALL THIS AFTER FILLING ANY CONTAINER to update the parent and result node
771
 * pointers, even if you don't care about visit counts and last visit dates.
772
 */
773
void
774
nsNavHistoryContainerResultNode::FillStats()
775
0
{
776
0
  uint32_t accessCount = 0;
777
0
  PRTime newTime = 0;
778
0
779
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
780
0
    nsNavHistoryResultNode* node = mChildren[i];
781
0
    SetAsParentOfNode(node);
782
0
    accessCount += node->mAccessCount;
783
0
    // this is how container nodes get sorted by date
784
0
    // The container gets the most recent time of the child nodes.
785
0
    if (node->mTime > newTime)
786
0
      newTime = node->mTime;
787
0
  }
788
0
789
0
  if (mExpanded) {
790
0
    mAccessCount = accessCount;
791
0
    if (!IsQuery() || newTime > mTime)
792
0
      mTime = newTime;
793
0
  }
794
0
}
795
796
void
797
0
nsNavHistoryContainerResultNode::SetAsParentOfNode(nsNavHistoryResultNode* aNode) {
798
0
  aNode->mParent = this;
799
0
  aNode->mIndentLevel = mIndentLevel + 1;
800
0
  if (aNode->IsContainer()) {
801
0
    nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
802
0
    // Propagate some of the parent's options to this container.
803
0
    if (mOptions->ExcludeItems()) {
804
0
      container->mOptions->SetExcludeItems(true);
805
0
    }
806
0
    if (mOptions->ExcludeQueries()) {
807
0
      container->mOptions->SetExcludeQueries(true);
808
0
    }
809
0
    if (mOptions->ExcludeReadOnlyFolders()) {
810
0
      container->mOptions->SetExcludeReadOnlyFolders(true);
811
0
    }
812
0
    if (aNode->IsFolder() && mOptions->AsyncEnabled()) {
813
0
      container->mOptions->SetAsyncEnabled(true);
814
0
    }
815
0
    if (!mOptions->ExpandQueries()) {
816
0
      container->mOptions->SetExpandQueries(false);
817
0
    }
818
0
    container->mResult = mResult;
819
0
    container->FillStats();
820
0
  }
821
0
}
822
823
/**
824
 * This is used when one container changes to do a minimal update of the tree
825
 * structure.  When something changes, you want to call FillStats if necessary
826
 * and update this container completely.  Then call this function which will
827
 * walk up the tree and fill in the previous containers.
828
 *
829
 * Note that you have to tell us by how much our access count changed.  Our
830
 * access count should already be set to the new value; this is used tochange
831
 * the parents without having to re-count all their children.
832
 *
833
 * This does NOT update the last visit date downward.  Therefore, if you are
834
 * deleting a node that has the most recent last visit date, the parents will
835
 * not get their last visit dates downshifted accordingly.  This is a rather
836
 * unusual case: we don't often delete things, and we usually don't even show
837
 * the last visit date for folders.  Updating would be slower because we would
838
 * have to recompute it from scratch.
839
 */
840
nsresult
841
nsNavHistoryContainerResultNode::ReverseUpdateStats(int32_t aAccessCountChange)
842
0
{
843
0
  if (mParent) {
844
0
    nsNavHistoryResult* result = GetResult();
845
0
    bool shouldNotify = result && mParent->mParent &&
846
0
                          mParent->mParent->AreChildrenVisible();
847
0
848
0
    uint32_t oldAccessCount = mParent->mAccessCount;
849
0
    PRTime oldTime = mParent->mTime;
850
0
851
0
    mParent->mAccessCount += aAccessCountChange;
852
0
    bool timeChanged = false;
853
0
    if (mTime > mParent->mTime) {
854
0
      timeChanged = true;
855
0
      mParent->mTime = mTime;
856
0
    }
857
0
858
0
    if (shouldNotify) {
859
0
      NOTIFY_RESULT_OBSERVERS(result,
860
0
                              NodeHistoryDetailsChanged(TO_ICONTAINER(mParent),
861
0
                                                        oldTime,
862
0
                                                        oldAccessCount));
863
0
    }
864
0
865
0
    // check sorting, the stats may have caused this node to move if the
866
0
    // sorting depended on something we are changing.
867
0
    uint16_t sortMode = mParent->GetSortType();
868
0
    bool sortingByVisitCount =
869
0
      sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
870
0
      sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING;
871
0
    bool sortingByTime =
872
0
      sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
873
0
      sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING;
874
0
875
0
    if ((sortingByVisitCount && aAccessCountChange != 0) ||
876
0
        (sortingByTime && timeChanged)) {
877
0
      int32_t ourIndex = mParent->FindChild(this);
878
0
      NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
879
0
      if (ourIndex >= 0)
880
0
        EnsureItemPosition(static_cast<uint32_t>(ourIndex));
881
0
    }
882
0
883
0
    nsresult rv = mParent->ReverseUpdateStats(aAccessCountChange);
884
0
    NS_ENSURE_SUCCESS(rv, rv);
885
0
  }
886
0
887
0
  return NS_OK;
888
0
}
889
890
891
/**
892
 * This walks up the tree until we find a query result node or the root to get
893
 * the sorting type.
894
 */
895
uint16_t
896
nsNavHistoryContainerResultNode::GetSortType()
897
0
{
898
0
  if (mParent)
899
0
    return mParent->GetSortType();
900
0
  if (mResult)
901
0
    return mResult->mSortingMode;
902
0
903
0
  // This is a detached container, just use natural order.
904
0
  return nsINavHistoryQueryOptions::SORT_BY_NONE;
905
0
}
906
907
908
0
nsresult nsNavHistoryContainerResultNode::Refresh() {
909
0
  NS_WARNING("Refresh() is supported by queries or folders, not generic containers.");
910
0
  return NS_OK;
911
0
}
912
913
/**
914
 * @return the sorting comparator function for the give sort type, or null if
915
 * there is no comparator.
916
 */
917
nsNavHistoryContainerResultNode::SortComparator
918
nsNavHistoryContainerResultNode::GetSortingComparator(uint16_t aSortType)
919
0
{
920
0
  switch (aSortType)
921
0
  {
922
0
    case nsINavHistoryQueryOptions::SORT_BY_NONE:
923
0
      return &SortComparison_Bookmark;
924
0
    case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
925
0
      return &SortComparison_TitleLess;
926
0
    case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
927
0
      return &SortComparison_TitleGreater;
928
0
    case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
929
0
      return &SortComparison_DateLess;
930
0
    case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
931
0
      return &SortComparison_DateGreater;
932
0
    case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
933
0
      return &SortComparison_URILess;
934
0
    case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
935
0
      return &SortComparison_URIGreater;
936
0
    case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
937
0
      return &SortComparison_VisitCountLess;
938
0
    case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
939
0
      return &SortComparison_VisitCountGreater;
940
0
    case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
941
0
      return &SortComparison_DateAddedLess;
942
0
    case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
943
0
      return &SortComparison_DateAddedGreater;
944
0
    case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
945
0
      return &SortComparison_LastModifiedLess;
946
0
    case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
947
0
      return &SortComparison_LastModifiedGreater;
948
0
    case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
949
0
      return &SortComparison_TagsLess;
950
0
    case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
951
0
      return &SortComparison_TagsGreater;
952
0
    case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
953
0
      return &SortComparison_FrecencyLess;
954
0
    case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
955
0
      return &SortComparison_FrecencyGreater;
956
0
    default:
957
0
      MOZ_ASSERT_UNREACHABLE("Bad sorting type");
958
0
      return nullptr;
959
0
  }
960
0
}
961
962
963
/**
964
 * This is used by Result::SetSortingMode and QueryResultNode::FillChildren to
965
 * sort the child list.
966
 *
967
 * This does NOT update any visibility or tree information.  The caller will
968
 * have to completely rebuild the visible list after this.
969
 */
970
void
971
nsNavHistoryContainerResultNode::RecursiveSort(SortComparator aComparator)
972
0
{
973
0
  mChildren.Sort(aComparator, nullptr);
974
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
975
0
    if (mChildren[i]->IsContainer())
976
0
      mChildren[i]->GetAsContainer()->RecursiveSort(aComparator);
977
0
  }
978
0
}
979
980
981
/**
982
 * @return the index that the given item would fall on if it were to be
983
 * inserted using the given sorting.
984
 */
985
uint32_t
986
nsNavHistoryContainerResultNode::FindInsertionPoint(
987
    nsNavHistoryResultNode* aNode, SortComparator aComparator,
988
    bool* aItemExists)
989
0
{
990
0
  if (aItemExists)
991
0
    (*aItemExists) = false;
992
0
993
0
  if (mChildren.Count() == 0)
994
0
    return 0;
995
0
996
0
  // The common case is the beginning or the end because this is used to insert
997
0
  // new items that are added to history, which is usually sorted by date.
998
0
  int32_t res;
999
0
  res = aComparator(aNode, mChildren[0], nullptr);
1000
0
  if (res <= 0) {
1001
0
    if (aItemExists && res == 0)
1002
0
      (*aItemExists) = true;
1003
0
    return 0;
1004
0
  }
1005
0
  res = aComparator(aNode, mChildren[mChildren.Count() - 1], nullptr);
1006
0
  if (res >= 0) {
1007
0
    if (aItemExists && res == 0)
1008
0
      (*aItemExists) = true;
1009
0
    return mChildren.Count();
1010
0
  }
1011
0
1012
0
  uint32_t beginRange = 0; // inclusive
1013
0
  uint32_t endRange = mChildren.Count(); // exclusive
1014
0
  while (1) {
1015
0
    if (beginRange == endRange)
1016
0
      return endRange;
1017
0
    uint32_t center = beginRange + (endRange - beginRange) / 2;
1018
0
    int32_t res = aComparator(aNode, mChildren[center], nullptr);
1019
0
    if (res <= 0) {
1020
0
      endRange = center; // left side
1021
0
      if (aItemExists && res == 0)
1022
0
        (*aItemExists) = true;
1023
0
    }
1024
0
    else {
1025
0
      beginRange = center + 1; // right site
1026
0
    }
1027
0
  }
1028
0
}
1029
1030
1031
/**
1032
 * This checks the child node at the given index to see if its sorting is
1033
 * correct.  This is called when nodes are updated and we need to see whether
1034
 * we need to move it.
1035
 *
1036
 * @returns true if not and it should be resorted.
1037
*/
1038
bool
1039
nsNavHistoryContainerResultNode::DoesChildNeedResorting(uint32_t aIndex,
1040
    SortComparator aComparator)
1041
0
{
1042
0
  NS_ASSERTION(aIndex < uint32_t(mChildren.Count()),
1043
0
               "Input index out of range");
1044
0
  if (mChildren.Count() == 1)
1045
0
    return false;
1046
0
1047
0
  if (aIndex > 0) {
1048
0
    // compare to previous item
1049
0
    if (aComparator(mChildren[aIndex - 1], mChildren[aIndex], nullptr) > 0)
1050
0
      return true;
1051
0
  }
1052
0
  if (aIndex < uint32_t(mChildren.Count()) - 1) {
1053
0
    // compare to next item
1054
0
    if (aComparator(mChildren[aIndex], mChildren[aIndex + 1], nullptr) > 0)
1055
0
      return true;
1056
0
  }
1057
0
  return false;
1058
0
}
1059
1060
1061
/* static */
1062
int32_t nsNavHistoryContainerResultNode::SortComparison_StringLess(
1063
0
    const nsAString& a, const nsAString& b) {
1064
0
1065
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
1066
0
  NS_ENSURE_TRUE(history, 0);
1067
0
  nsICollation* collation = history->GetCollation();
1068
0
  NS_ENSURE_TRUE(collation, 0);
1069
0
1070
0
  int32_t res = 0;
1071
0
  collation->CompareString(nsICollation::kCollationCaseInSensitive, a, b, &res);
1072
0
  return res;
1073
0
}
1074
1075
1076
/**
1077
 * When there are bookmark indices, we should never have ties, so we don't
1078
 * need to worry about tiebreaking.  When there are no bookmark indices,
1079
 * everything will be -1 and we don't worry about sorting.
1080
 */
1081
int32_t nsNavHistoryContainerResultNode::SortComparison_Bookmark(
1082
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1083
0
{
1084
0
  return a->mBookmarkIndex - b->mBookmarkIndex;
1085
0
}
1086
1087
/**
1088
 * These are a little more complicated because they do a localization
1089
 * conversion.  If this is too slow, we can compute the sort keys once in
1090
 * advance, sort that array, and then reorder the real array accordingly.
1091
 * This would save some key generations.
1092
 *
1093
 * The collation object must be allocated before sorting on title!
1094
 */
1095
int32_t nsNavHistoryContainerResultNode::SortComparison_TitleLess(
1096
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1097
0
{
1098
0
  uint32_t aType;
1099
0
  a->GetType(&aType);
1100
0
1101
0
  int32_t value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1102
0
                                            NS_ConvertUTF8toUTF16(b->mTitle));
1103
0
  if (value == 0) {
1104
0
    // resolve by URI
1105
0
    if (a->IsURI()) {
1106
0
      value = a->mURI.Compare(b->mURI.get());
1107
0
    }
1108
0
    if (value == 0) {
1109
0
      // resolve by date
1110
0
      value = ComparePRTime(a->mTime, b->mTime);
1111
0
      if (value == 0)
1112
0
        value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1113
0
    }
1114
0
  }
1115
0
  return value;
1116
0
}
1117
int32_t nsNavHistoryContainerResultNode::SortComparison_TitleGreater(
1118
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1119
0
{
1120
0
  return -SortComparison_TitleLess(a, b, closure);
1121
0
}
1122
1123
/**
1124
 * Equal times will be very unusual, but it is important that there is some
1125
 * deterministic ordering of the results so they don't move around.
1126
 */
1127
int32_t nsNavHistoryContainerResultNode::SortComparison_DateLess(
1128
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1129
0
{
1130
0
  int32_t value = ComparePRTime(a->mTime, b->mTime);
1131
0
  if (value == 0) {
1132
0
    value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1133
0
                                      NS_ConvertUTF8toUTF16(b->mTitle));
1134
0
    if (value == 0)
1135
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1136
0
  }
1137
0
  return value;
1138
0
}
1139
int32_t nsNavHistoryContainerResultNode::SortComparison_DateGreater(
1140
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1141
0
{
1142
0
  return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b, closure);
1143
0
}
1144
1145
1146
int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
1147
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1148
0
{
1149
0
  int32_t value = ComparePRTime(a->mDateAdded, b->mDateAdded);
1150
0
  if (value == 0) {
1151
0
    value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1152
0
                                      NS_ConvertUTF8toUTF16(b->mTitle));
1153
0
    if (value == 0)
1154
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1155
0
  }
1156
0
  return value;
1157
0
}
1158
int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
1159
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1160
0
{
1161
0
  return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(a, b, closure);
1162
0
}
1163
1164
1165
int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
1166
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1167
0
{
1168
0
  int32_t value = ComparePRTime(a->mLastModified, b->mLastModified);
1169
0
  if (value == 0) {
1170
0
    value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1171
0
                                      NS_ConvertUTF8toUTF16(b->mTitle));
1172
0
    if (value == 0)
1173
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1174
0
  }
1175
0
  return value;
1176
0
}
1177
int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
1178
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1179
0
{
1180
0
  return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(a, b, closure);
1181
0
}
1182
1183
1184
/**
1185
 * Certain types of parent nodes are treated specially because URIs are not
1186
 * valid (like days or hosts).
1187
 */
1188
int32_t nsNavHistoryContainerResultNode::SortComparison_URILess(
1189
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1190
0
{
1191
0
  int32_t value;
1192
0
  if (a->IsURI() && b->IsURI()) {
1193
0
    // normal URI or visit
1194
0
    value = a->mURI.Compare(b->mURI.get());
1195
0
  } else {
1196
0
    // for everything else, use title (= host name)
1197
0
    value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1198
0
                                      NS_ConvertUTF8toUTF16(b->mTitle));
1199
0
  }
1200
0
1201
0
  if (value == 0) {
1202
0
    value = ComparePRTime(a->mTime, b->mTime);
1203
0
    if (value == 0)
1204
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1205
0
  }
1206
0
  return value;
1207
0
}
1208
int32_t nsNavHistoryContainerResultNode::SortComparison_URIGreater(
1209
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1210
0
{
1211
0
  return -SortComparison_URILess(a, b, closure);
1212
0
}
1213
1214
/**
1215
 * Fall back on dates for conflict resolution
1216
 */
1217
int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
1218
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1219
0
{
1220
0
  int32_t value = CompareIntegers(a->mAccessCount, b->mAccessCount);
1221
0
  if (value == 0) {
1222
0
    value = ComparePRTime(a->mTime, b->mTime);
1223
0
    if (value == 0)
1224
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1225
0
  }
1226
0
  return value;
1227
0
}
1228
int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountGreater(
1229
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1230
0
{
1231
0
  return -nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(a, b, closure);
1232
0
}
1233
1234
1235
int32_t nsNavHistoryContainerResultNode::SortComparison_TagsLess(
1236
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1237
0
{
1238
0
  int32_t value = 0;
1239
0
  nsAutoString aTags, bTags;
1240
0
1241
0
  nsresult rv = a->GetTags(aTags);
1242
0
  NS_ENSURE_SUCCESS(rv, 0);
1243
0
1244
0
  rv = b->GetTags(bTags);
1245
0
  NS_ENSURE_SUCCESS(rv, 0);
1246
0
1247
0
  value = SortComparison_StringLess(aTags, bTags);
1248
0
1249
0
  // fall back to title sorting
1250
0
  if (value == 0)
1251
0
    value = SortComparison_TitleLess(a, b, closure);
1252
0
1253
0
  return value;
1254
0
}
1255
1256
int32_t nsNavHistoryContainerResultNode::SortComparison_TagsGreater(
1257
    nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
1258
0
{
1259
0
  return -SortComparison_TagsLess(a, b, closure);
1260
0
}
1261
1262
/**
1263
 * Fall back on date and bookmarked status, for conflict resolution.
1264
 */
1265
int32_t
1266
nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(
1267
  nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
1268
)
1269
0
{
1270
0
  int32_t value = CompareIntegers(a->mFrecency, b->mFrecency);
1271
0
  if (value == 0) {
1272
0
    value = ComparePRTime(a->mTime, b->mTime);
1273
0
    if (value == 0) {
1274
0
      value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
1275
0
    }
1276
0
  }
1277
0
  return value;
1278
0
}
1279
int32_t
1280
nsNavHistoryContainerResultNode::SortComparison_FrecencyGreater(
1281
  nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
1282
)
1283
0
{
1284
0
  return -nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(a, b, closure);
1285
0
}
1286
1287
/**
1288
 * Searches this folder for a node with the given URI.  Returns null if not
1289
 * found.
1290
 *
1291
 * @note Does not addref the node!
1292
 */
1293
nsNavHistoryResultNode*
1294
nsNavHistoryContainerResultNode::FindChildURI(const nsACString& aSpec,
1295
    uint32_t* aNodeIndex)
1296
0
{
1297
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
1298
0
    if (mChildren[i]->IsURI()) {
1299
0
      if (aSpec.Equals(mChildren[i]->mURI)) {
1300
0
        *aNodeIndex = i;
1301
0
        return mChildren[i];
1302
0
      }
1303
0
    }
1304
0
  }
1305
0
  return nullptr;
1306
0
}
1307
1308
/**
1309
 * Searches this folder for a node with the given guid/target-folder-guid.
1310
 *
1311
 * @return the node if found, null otherwise.
1312
 * @note Does not addref the node!
1313
 */
1314
nsNavHistoryResultNode*
1315
nsNavHistoryContainerResultNode::FindChildByGuid(const nsACString& guid,
1316
    int32_t* nodeIndex)
1317
0
{
1318
0
  *nodeIndex = -1;
1319
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
1320
0
    if (mChildren[i]->mBookmarkGuid == guid ||
1321
0
        mChildren[i]->mPageGuid == guid ||
1322
0
        (mChildren[i]->IsFolder() &&
1323
0
         mChildren[i]->GetAsFolder()->mTargetFolderGuid == guid)) {
1324
0
      *nodeIndex = i;
1325
0
      return mChildren[i];
1326
0
    }
1327
0
  }
1328
0
  return nullptr;
1329
0
}
1330
1331
/**
1332
 * This does the work of adding a child to the container.  The child can be
1333
 * either a container or or a single item that may even be collapsed with the
1334
 * adjacent ones.
1335
 */
1336
nsresult
1337
nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode,
1338
                                               int32_t aIndex)
1339
0
{
1340
0
  nsNavHistoryResult* result = GetResult();
1341
0
  NS_ENSURE_STATE(result);
1342
0
1343
0
  SetAsParentOfNode(aNode);
1344
0
1345
0
  if (!mChildren.InsertObjectAt(aNode, aIndex))
1346
0
    return NS_ERROR_OUT_OF_MEMORY;
1347
0
1348
0
  // Update our stats and notify the result's observers.
1349
0
  uint32_t oldAccessCount = mAccessCount;
1350
0
  PRTime oldTime = mTime;
1351
0
1352
0
  mAccessCount += aNode->mAccessCount;
1353
0
  if (mTime < aNode->mTime)
1354
0
    mTime = aNode->mTime;
1355
0
  if (!mParent || mParent->AreChildrenVisible()) {
1356
0
    NOTIFY_RESULT_OBSERVERS(result,
1357
0
                            NodeHistoryDetailsChanged(TO_ICONTAINER(this),
1358
0
                                                      oldTime,
1359
0
                                                      oldAccessCount));
1360
0
  }
1361
0
1362
0
  nsresult rv = ReverseUpdateStats(aNode->mAccessCount);
1363
0
  NS_ENSURE_SUCCESS(rv, rv);
1364
0
1365
0
  // Update tree if we are visible.  Note that we could be here and not
1366
0
  // expanded, like when there is a bookmark folder being updated because its
1367
0
  // parent is visible.
1368
0
  if (AreChildrenVisible())
1369
0
    NOTIFY_RESULT_OBSERVERS(result, NodeInserted(this, aNode, aIndex));
1370
0
1371
0
  return NS_OK;
1372
0
}
1373
1374
1375
/**
1376
 * This locates the proper place for insertion according to the current sort
1377
 * and calls InsertChildAt
1378
 */
1379
nsresult
1380
nsNavHistoryContainerResultNode::InsertSortedChild(
1381
    nsNavHistoryResultNode* aNode,
1382
    bool aIgnoreDuplicates)
1383
0
{
1384
0
1385
0
  if (mChildren.Count() == 0)
1386
0
    return InsertChildAt(aNode, 0);
1387
0
1388
0
  SortComparator comparator = GetSortingComparator(GetSortType());
1389
0
  if (comparator) {
1390
0
    // When inserting a new node, it must have proper statistics because we use
1391
0
    // them to find the correct insertion point.  The insert function will then
1392
0
    // recompute these statistics and fill in the proper parents and hierarchy
1393
0
    // level.  Doing this twice shouldn't be a large performance penalty because
1394
0
    // when we are inserting new containers, they typically contain only one
1395
0
    // item (because we've browsed a new page).
1396
0
    if (aNode->IsContainer()) {
1397
0
      // need to update all the new item's children
1398
0
      nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
1399
0
      container->mResult = mResult;
1400
0
      container->FillStats();
1401
0
    }
1402
0
1403
0
    bool itemExists;
1404
0
    uint32_t position = FindInsertionPoint(aNode, comparator,
1405
0
                                           &itemExists);
1406
0
    if (aIgnoreDuplicates && itemExists)
1407
0
      return NS_OK;
1408
0
1409
0
    return InsertChildAt(aNode, position);
1410
0
  }
1411
0
  return InsertChildAt(aNode, mChildren.Count());
1412
0
}
1413
1414
/**
1415
 * This checks if the item at aIndex is located correctly given the sorting
1416
 * move.  If it's not, the item is moved, and the result's observers are
1417
 * notified.
1418
 *
1419
 * @return true if the item position has been changed, false otherwise.
1420
 */
1421
bool
1422
0
nsNavHistoryContainerResultNode::EnsureItemPosition(uint32_t aIndex) {
1423
0
  NS_ASSERTION(aIndex < (uint32_t)mChildren.Count(), "Invalid index");
1424
0
  if (aIndex >= (uint32_t)mChildren.Count())
1425
0
    return false;
1426
0
1427
0
  SortComparator comparator = GetSortingComparator(GetSortType());
1428
0
  if (!comparator)
1429
0
    return false;
1430
0
1431
0
  if (!DoesChildNeedResorting(aIndex, comparator))
1432
0
    return false;
1433
0
1434
0
  RefPtr<nsNavHistoryResultNode> node(mChildren[aIndex]);
1435
0
  mChildren.RemoveObjectAt(aIndex);
1436
0
1437
0
  uint32_t newIndex = FindInsertionPoint(node, comparator, nullptr);
1438
0
  mChildren.InsertObjectAt(node.get(), newIndex);
1439
0
1440
0
  if (AreChildrenVisible()) {
1441
0
    nsNavHistoryResult* result = GetResult();
1442
0
    NOTIFY_RESULT_OBSERVERS_RET(result,
1443
0
                                NodeMoved(node, this, aIndex, this, newIndex),
1444
0
                                false);
1445
0
  }
1446
0
1447
0
  return true;
1448
0
}
1449
1450
/**
1451
 * This does all the work of removing a child from this container, including
1452
 * updating the tree if necessary.  Note that we do not need to be open for
1453
 * this to work.
1454
 */
1455
nsresult
1456
nsNavHistoryContainerResultNode::RemoveChildAt(int32_t aIndex)
1457
0
{
1458
0
  NS_ASSERTION(aIndex >= 0 && aIndex < mChildren.Count(), "Invalid index");
1459
0
1460
0
  // Hold an owning reference to keep from expiring while we work with it.
1461
0
  RefPtr<nsNavHistoryResultNode> oldNode = mChildren[aIndex];
1462
0
1463
0
  // Update stats.
1464
0
  // XXX This assertion does not reliably pass -- investigate!! (bug 1049797)
1465
0
  // MOZ_ASSERT(mAccessCount >= mChildren[aIndex]->mAccessCount,
1466
0
  //            "Invalid access count while updating!");
1467
0
  uint32_t oldAccessCount = mAccessCount;
1468
0
  mAccessCount -= mChildren[aIndex]->mAccessCount;
1469
0
1470
0
  // Remove it from our list and notify the result's observers.
1471
0
  mChildren.RemoveObjectAt(aIndex);
1472
0
  if (AreChildrenVisible()) {
1473
0
    nsNavHistoryResult* result = GetResult();
1474
0
    NOTIFY_RESULT_OBSERVERS(result,
1475
0
                            NodeRemoved(this, oldNode, aIndex));
1476
0
  }
1477
0
1478
0
  nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
1479
0
  NS_ENSURE_SUCCESS(rv, rv);
1480
0
  oldNode->OnRemoving();
1481
0
  return NS_OK;
1482
0
}
1483
1484
1485
/**
1486
 * Searches for matches for the given URI.  If aOnlyOne is set, it will
1487
 * terminate as soon as it finds a single match.  This would be used when there
1488
 * are URI results so there will only ever be one copy of any URI.
1489
 *
1490
 * When aOnlyOne is false, it will check all elements.  This is for visit
1491
 * style results that may have multiple copies of any given URI.
1492
 */
1493
void
1494
nsNavHistoryContainerResultNode::RecursiveFindURIs(bool aOnlyOne,
1495
    nsNavHistoryContainerResultNode* aContainer, const nsCString& aSpec,
1496
    nsCOMArray<nsNavHistoryResultNode>* aMatches)
1497
0
{
1498
0
  for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
1499
0
    uint32_t type;
1500
0
    aContainer->mChildren[child]->GetType(&type);
1501
0
    if (nsNavHistoryResultNode::IsTypeURI(type)) {
1502
0
      // compare URIs
1503
0
      nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
1504
0
      if (uriNode->mURI.Equals(aSpec)) {
1505
0
        // found
1506
0
        aMatches->AppendObject(uriNode);
1507
0
        if (aOnlyOne)
1508
0
          return;
1509
0
      }
1510
0
    }
1511
0
  }
1512
0
}
1513
1514
1515
/**
1516
 * If aUpdateSort is true, we will also update the sorting of this item.
1517
 * Normally you want this to be true, but it can be false if the thing you are
1518
 * changing can not affect sorting (like favicons).
1519
 *
1520
 * You should NOT change any child lists as part of the callback function.
1521
 */
1522
bool
1523
nsNavHistoryContainerResultNode::UpdateURIs(bool aRecursive, bool aOnlyOne,
1524
    bool aUpdateSort, const nsCString& aSpec,
1525
    nsresult (*aCallback)(nsNavHistoryResultNode*, const void*, const nsNavHistoryResult*),
1526
    const void* aClosure)
1527
0
{
1528
0
  const nsNavHistoryResult* result = GetResult();
1529
0
  if (!result) {
1530
0
    MOZ_ASSERT(false, "Should have a result");
1531
0
    return false;
1532
0
  }
1533
0
1534
0
  // this needs to be owning since sometimes we remove and re-insert nodes
1535
0
  // in their parents and we don't want them to go away.
1536
0
  nsCOMArray<nsNavHistoryResultNode> matches;
1537
0
1538
0
  if (aRecursive) {
1539
0
    RecursiveFindURIs(aOnlyOne, this, aSpec, &matches);
1540
0
  } else if (aOnlyOne) {
1541
0
    uint32_t nodeIndex;
1542
0
    nsNavHistoryResultNode* node = FindChildURI(aSpec, &nodeIndex);
1543
0
    if (node)
1544
0
      matches.AppendObject(node);
1545
0
  } else {
1546
0
    MOZ_ASSERT(false,
1547
0
               "UpdateURIs does not handle nonrecursive updates of multiple items.");
1548
0
    // this case easy to add if you need it, just find all the matching URIs
1549
0
    // at this level.  However, this isn't currently used. History uses
1550
0
    // recursive, Bookmarks uses one level and knows that the match is unique.
1551
0
    return false;
1552
0
  }
1553
0
1554
0
  if (matches.Count() == 0)
1555
0
    return false;
1556
0
1557
0
  // PERFORMANCE: This updates each container for each child in it that
1558
0
  // changes.  In some cases, many elements have changed inside the same
1559
0
  // container.  It would be better to compose a list of containers, and
1560
0
  // update each one only once for all the items that have changed in it.
1561
0
  for (int32_t i = 0; i < matches.Count(); ++i)
1562
0
  {
1563
0
    nsNavHistoryResultNode* node = matches[i];
1564
0
    nsNavHistoryContainerResultNode* parent = node->mParent;
1565
0
    if (!parent) {
1566
0
      MOZ_ASSERT(false, "All URI nodes being updated must have parents");
1567
0
      continue;
1568
0
    }
1569
0
1570
0
    uint32_t oldAccessCount = node->mAccessCount;
1571
0
    PRTime oldTime = node->mTime;
1572
0
    uint32_t parentOldAccessCount = parent->mAccessCount;
1573
0
    PRTime parentOldTime = parent->mTime;
1574
0
1575
0
    aCallback(node, aClosure, result);
1576
0
1577
0
    if (oldAccessCount != node->mAccessCount || oldTime != node->mTime) {
1578
0
      parent->mAccessCount += node->mAccessCount - oldAccessCount;
1579
0
      if (node->mTime > parent->mTime)
1580
0
        parent->mTime = node->mTime;
1581
0
      if (parent->AreChildrenVisible()) {
1582
0
        NOTIFY_RESULT_OBSERVERS_RET(result,
1583
0
                                    NodeHistoryDetailsChanged(
1584
0
                                      TO_ICONTAINER(parent),
1585
0
                                      parentOldTime,
1586
0
                                      parentOldAccessCount),
1587
0
                                    true);
1588
0
      }
1589
0
      DebugOnly<nsresult> rv = parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount);
1590
0
      MOZ_ASSERT(NS_SUCCEEDED(rv), "should be able to ReverseUpdateStats");
1591
0
    }
1592
0
1593
0
    if (aUpdateSort) {
1594
0
      int32_t childIndex = parent->FindChild(node);
1595
0
      MOZ_ASSERT(childIndex >= 0, "Could not find child we just got a reference to");
1596
0
      if (childIndex >= 0)
1597
0
        parent->EnsureItemPosition(childIndex);
1598
0
    }
1599
0
  }
1600
0
1601
0
  return true;
1602
0
}
1603
1604
1605
/**
1606
 * This is used to update the titles in the tree.  This is called from both
1607
 * query and bookmark folder containers to update the tree.  Bookmark folders
1608
 * should be sure to set recursive to false, since child folders will have
1609
 * their own callbacks registered.
1610
 */
1611
static nsresult setTitleCallback(nsNavHistoryResultNode* aNode,
1612
                                 const void* aClosure,
1613
                                 const nsNavHistoryResult* aResult)
1614
0
{
1615
0
  const nsACString* newTitle = static_cast<const nsACString*>(aClosure);
1616
0
  aNode->mTitle = *newTitle;
1617
0
1618
0
  if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
1619
0
    NOTIFY_RESULT_OBSERVERS(aResult, NodeTitleChanged(aNode, *newTitle));
1620
0
1621
0
  return NS_OK;
1622
0
}
1623
nsresult
1624
nsNavHistoryContainerResultNode::ChangeTitles(nsIURI* aURI,
1625
                                              const nsACString& aNewTitle,
1626
                                              bool aRecursive,
1627
                                              bool aOnlyOne)
1628
0
{
1629
0
  // uri string
1630
0
  nsAutoCString uriString;
1631
0
  nsresult rv = aURI->GetSpec(uriString);
1632
0
  NS_ENSURE_SUCCESS(rv, rv);
1633
0
1634
0
  // The recursive function will update the result's tree nodes, but only if we
1635
0
  // give it a non-null pointer.  So if there isn't a tree, just pass nullptr
1636
0
  // so it doesn't bother trying to call the result.
1637
0
  nsNavHistoryResult* result = GetResult();
1638
0
  NS_ENSURE_STATE(result);
1639
0
1640
0
  uint16_t sortType = GetSortType();
1641
0
  bool updateSorting =
1642
0
    (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
1643
0
     sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING);
1644
0
1645
0
  UpdateURIs(aRecursive, aOnlyOne, updateSorting, uriString,
1646
0
             setTitleCallback,
1647
0
             static_cast<const void*>(&aNewTitle));
1648
0
1649
0
  return NS_OK;
1650
0
}
1651
1652
1653
/**
1654
 * Complex containers (folders and queries) will override this.  Here, we
1655
 * handle the case of simple containers (like host groups) where the children
1656
 * are always stored.
1657
 */
1658
NS_IMETHODIMP
1659
nsNavHistoryContainerResultNode::GetHasChildren(bool *aHasChildren)
1660
0
{
1661
0
  *aHasChildren = (mChildren.Count() > 0);
1662
0
  return NS_OK;
1663
0
}
1664
1665
1666
/**
1667
 * @throws if this node is closed.
1668
 */
1669
NS_IMETHODIMP
1670
nsNavHistoryContainerResultNode::GetChildCount(uint32_t* aChildCount)
1671
0
{
1672
0
  if (!mExpanded)
1673
0
    return NS_ERROR_NOT_AVAILABLE;
1674
0
  *aChildCount = mChildren.Count();
1675
0
  return NS_OK;
1676
0
}
1677
1678
1679
NS_IMETHODIMP
1680
nsNavHistoryContainerResultNode::GetChild(uint32_t aIndex,
1681
                                          nsINavHistoryResultNode** _child)
1682
0
{
1683
0
  if (!mExpanded)
1684
0
    return NS_ERROR_NOT_AVAILABLE;
1685
0
  if (aIndex >= uint32_t(mChildren.Count()))
1686
0
    return NS_ERROR_INVALID_ARG;
1687
0
  nsCOMPtr<nsINavHistoryResultNode> child = mChildren[aIndex];
1688
0
  child.forget(_child);
1689
0
  return NS_OK;
1690
0
}
1691
1692
1693
NS_IMETHODIMP
1694
nsNavHistoryContainerResultNode::GetChildIndex(nsINavHistoryResultNode* aNode,
1695
                                               uint32_t* _retval)
1696
0
{
1697
0
  if (!mExpanded)
1698
0
    return NS_ERROR_NOT_AVAILABLE;
1699
0
1700
0
  int32_t nodeIndex = FindChild(static_cast<nsNavHistoryResultNode*>(aNode));
1701
0
  if (nodeIndex == -1)
1702
0
    return NS_ERROR_INVALID_ARG;
1703
0
1704
0
  *_retval = nodeIndex;
1705
0
  return NS_OK;
1706
0
}
1707
1708
/**
1709
 * HOW QUERY UPDATING WORKS
1710
 *
1711
 * Queries are different than bookmark folders in that we can not always do
1712
 * dynamic updates (easily) and updates are more expensive.  Therefore, we do
1713
 * NOT query if we are not open and want to see if we have any children (for
1714
 * drawing a twisty) and always assume we will.
1715
 *
1716
 * When the container is opened, we execute the query and register the
1717
 * listeners.  Like bookmark folders, we stay registered even when closed, and
1718
 * clear ourselves as soon as a message comes in.  This lets us respond quickly
1719
 * if the user closes and reopens the container.
1720
 *
1721
 * We try to handle the most common notifications for the most common query
1722
 * types dynamically, that is, figuring out what should happen in response to
1723
 * a message without doing a requery.  For complex changes or complex queries,
1724
 * we give up and requery.
1725
 */
1726
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
1727
                            nsNavHistoryContainerResultNode,
1728
                            nsINavHistoryQueryResultNode)
1729
1730
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
1731
    const nsACString& aTitle,
1732
    PRTime aTime,
1733
    const nsACString& aQueryURI,
1734
    const RefPtr<nsNavHistoryQuery>& aQuery,
1735
    const RefPtr<nsNavHistoryQueryOptions>& aOptions)
1736
  : nsNavHistoryContainerResultNode(aQueryURI, aTitle, aTime,
1737
                                    nsNavHistoryResultNode::RESULT_TYPE_QUERY,
1738
                                    aOptions),
1739
  mQuery(aQuery),
1740
  mLiveUpdate(getUpdateRequirements(aQuery, aOptions, &mHasSearchTerms)),
1741
  mContentsValid(false),
1742
  mBatchChanges(0),
1743
  mTransitions(aQuery->Transitions())
1744
0
{
1745
0
}
1746
1747
0
nsNavHistoryQueryResultNode::~nsNavHistoryQueryResultNode() {
1748
0
  // Remove this node from result's observers.  We don't need to be notified
1749
0
  // anymore.
1750
0
  if (mResult && mResult->mAllBookmarksObservers.Contains(this))
1751
0
    mResult->RemoveAllBookmarksObserver(this);
1752
0
  if (mResult && mResult->mHistoryObservers.Contains(this))
1753
0
    mResult->RemoveHistoryObserver(this);
1754
0
  if (mResult && mResult->mMobilePrefObservers.Contains(this))
1755
0
    mResult->RemoveMobilePrefsObserver(this);
1756
0
}
1757
1758
/**
1759
 * Whoever made us may want non-expanding queries. However, we always expand
1760
 * when we are the root node, or else asking for non-expanding queries would be
1761
 * useless.  A query node is not expandable if excludeItems is set or if
1762
 * expandQueries is unset.
1763
 */
1764
bool
1765
nsNavHistoryQueryResultNode::CanExpand()
1766
0
{
1767
0
  // The root node and containersQueries can always expand;
1768
0
  if ((mResult && mResult->mRootNode == this) ||
1769
0
      IsContainersQuery()) {
1770
0
    return true;
1771
0
  }
1772
0
1773
0
  if (mOptions->ExcludeItems()) {
1774
0
    return false;
1775
0
  }
1776
0
  if (mOptions->ExpandQueries()) {
1777
0
    return true;
1778
0
  }
1779
0
1780
0
  return false;
1781
0
}
1782
1783
1784
/**
1785
 * Some query with a particular result type can contain other queries.  They
1786
 * must be always expandable
1787
 */
1788
bool
1789
nsNavHistoryQueryResultNode::IsContainersQuery()
1790
0
{
1791
0
  uint16_t resultType = Options()->ResultType();
1792
0
  return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
1793
0
         resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
1794
0
         resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT ||
1795
0
         resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY ||
1796
0
         resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
1797
0
         resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY;
1798
0
}
1799
1800
1801
/**
1802
 * Here we do not want to call ContainerResultNode::OnRemoving since our own
1803
 * ClearChildren will do the same thing and more (unregister the observers).
1804
 * The base ResultNode::OnRemoving will clear some regular node stats, so it
1805
 * is OK.
1806
 */
1807
void
1808
nsNavHistoryQueryResultNode::OnRemoving()
1809
0
{
1810
0
  nsNavHistoryResultNode::OnRemoving();
1811
0
  ClearChildren(true);
1812
0
  mResult = nullptr;
1813
0
}
1814
1815
1816
/**
1817
 * Marks the container as open, rebuilding results if they are invalid.  We
1818
 * may still have valid results if the container was previously open and
1819
 * nothing happened since closing it.
1820
 *
1821
 * We do not handle CloseContainer specially.  The default one just marks the
1822
 * container as closed, but doesn't actually mark the results as invalid.
1823
 * The results will be invalidated by the next history or bookmark
1824
 * notification that comes in.  This means if you open and close the item
1825
 * without anything happening in between, it will be fast (this actually
1826
 * happens when results are used as menus).
1827
 */
1828
nsresult
1829
nsNavHistoryQueryResultNode::OpenContainer()
1830
0
{
1831
0
  NS_ASSERTION(!mExpanded, "Container must be closed to open it");
1832
0
  mExpanded = true;
1833
0
1834
0
  nsresult rv;
1835
0
1836
0
  if (!CanExpand())
1837
0
    return NS_OK;
1838
0
  if (!mContentsValid) {
1839
0
    rv = FillChildren();
1840
0
    NS_ENSURE_SUCCESS(rv, rv);
1841
0
  }
1842
0
1843
0
  rv = NotifyOnStateChange(STATE_CLOSED);
1844
0
  NS_ENSURE_SUCCESS(rv, rv);
1845
0
1846
0
  return NS_OK;
1847
0
}
1848
1849
1850
/**
1851
 * When we have valid results we can always give an exact answer.  When we
1852
 * don't we just assume we'll have results, since actually doing the query
1853
 * might be hard.  This is used to draw twisties on the tree, so precise results
1854
 * don't matter.
1855
 */
1856
NS_IMETHODIMP
1857
nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
1858
0
{
1859
0
  *aHasChildren = false;
1860
0
1861
0
  if (!CanExpand()) {
1862
0
    return NS_OK;
1863
0
  }
1864
0
1865
0
  uint16_t resultType = mOptions->ResultType();
1866
0
1867
0
  // Tags are always populated, otherwise they are removed.
1868
0
  if (mQuery->Tags().Length() == 1 && mParent &&
1869
0
      mParent->mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT) {
1870
0
    *aHasChildren = true;
1871
0
    return NS_OK;
1872
0
  }
1873
0
1874
0
  // AllBookmarks and the left pane folder also always have children.
1875
0
  if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
1876
0
      resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
1877
0
    *aHasChildren = true;
1878
0
    return NS_OK;
1879
0
  }
1880
0
1881
0
  // For history containers query we must check if we have any history
1882
0
  if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
1883
0
      resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
1884
0
      resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
1885
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
1886
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1887
0
    *aHasChildren = history->hasHistoryEntries();
1888
0
    return NS_OK;
1889
0
  }
1890
0
1891
0
  // TODO (Bug 1477934): We don't have a good synchronous way to fetch whether
1892
0
  // we have tags or not, to properly reply to the hasChildren request on the
1893
0
  // tags root. Potentially we could pass this information when we create the
1894
0
  // container.
1895
0
1896
0
  // If the container is open and populated, this is trivial.
1897
0
  if (mContentsValid) {
1898
0
    *aHasChildren = (mChildren.Count() > 0);
1899
0
    return NS_OK;
1900
0
  }
1901
0
1902
0
  // Fallback to assume we have children.
1903
0
  *aHasChildren = true;
1904
0
  return NS_OK;
1905
0
}
1906
1907
1908
/**
1909
 * This doesn't just return mURI because in the case of queries that may
1910
 * be lazily constructed from the query objects.
1911
 */
1912
NS_IMETHODIMP
1913
nsNavHistoryQueryResultNode::GetUri(nsACString& aURI)
1914
0
{
1915
0
  aURI = mURI;
1916
0
  return NS_OK;
1917
0
}
1918
1919
1920
NS_IMETHODIMP
1921
nsNavHistoryQueryResultNode::GetFolderItemId(int64_t* aItemId)
1922
0
{
1923
0
  *aItemId = -1;
1924
0
  return NS_OK;
1925
0
}
1926
1927
NS_IMETHODIMP
1928
0
nsNavHistoryQueryResultNode::GetTargetFolderGuid(nsACString& aGuid) {
1929
0
  aGuid = EmptyCString();
1930
0
  return NS_OK;
1931
0
}
1932
1933
NS_IMETHODIMP
1934
nsNavHistoryQueryResultNode::GetQuery(nsINavHistoryQuery** _query)
1935
0
{
1936
0
  nsCOMPtr<nsINavHistoryQuery> query = do_QueryInterface(mQuery);
1937
0
  query.forget(_query);
1938
0
  return NS_OK;
1939
0
}
1940
1941
1942
NS_IMETHODIMP
1943
nsNavHistoryQueryResultNode::GetQueryOptions(nsINavHistoryQueryOptions** _options)
1944
0
{
1945
0
  MOZ_ASSERT(mOptions, "Options should be valid");
1946
0
  nsCOMPtr<nsINavHistoryQueryOptions> options = do_QueryInterface(mOptions);
1947
0
  options.forget(_options);
1948
0
  return NS_OK;
1949
0
}
1950
1951
/**
1952
 * Safe options getter, ensures query is parsed first.
1953
 */
1954
nsNavHistoryQueryOptions*
1955
nsNavHistoryQueryResultNode::Options()
1956
0
{
1957
0
  MOZ_ASSERT(mOptions, "Options invalid, cannot generate from URI");
1958
0
  return mOptions;
1959
0
}
1960
1961
nsresult
1962
nsNavHistoryQueryResultNode::FillChildren()
1963
0
{
1964
0
  MOZ_ASSERT(!mContentsValid,
1965
0
             "Don't call FillChildren when contents are valid");
1966
0
  MOZ_ASSERT(mChildren.Count() == 0,
1967
0
             "We are trying to fill children when there already are some");
1968
0
  NS_ENSURE_STATE(mQuery && mOptions);
1969
0
1970
0
  // get the results from the history service
1971
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
1972
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1973
0
  nsresult rv = history->GetQueryResults(this, mQuery, mOptions, &mChildren);
1974
0
  NS_ENSURE_SUCCESS(rv, rv);
1975
0
1976
0
  // it is important to call FillStats to fill in the parents on all
1977
0
  // nodes and the result node pointers on the containers
1978
0
  FillStats();
1979
0
1980
0
  uint16_t sortType = GetSortType();
1981
0
1982
0
  if (mResult && mResult->mNeedsToApplySortingMode) {
1983
0
    // We should repopulate container and then apply sortingMode.  To avoid
1984
0
    // sorting 2 times we simply do that here.
1985
0
    mResult->SetSortingMode(mResult->mSortingMode);
1986
0
  }
1987
0
  else if (mOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
1988
0
           sortType != nsINavHistoryQueryOptions::SORT_BY_NONE) {
1989
0
    // The default SORT_BY_NONE sorts by the bookmark index (position),
1990
0
    // which we do not have for history queries.
1991
0
    // Once we've computed all tree stats, we can sort, because containers will
1992
0
    // then have proper visit counts and dates.
1993
0
    SortComparator comparator = GetSortingComparator(GetSortType());
1994
0
    if (comparator) {
1995
0
      // Usually containers queries results comes already sorted from the
1996
0
      // database, but some locales could have special rules to sort by title.
1997
0
      // RecursiveSort won't apply these rules to containers in containers
1998
0
      // queries because when setting sortingMode on the result we want to sort
1999
0
      // contained items (bug 473157).
2000
0
      // Base container RecursiveSort will sort both our children and all
2001
0
      // descendants, and is used in this case because we have to do manual
2002
0
      // title sorting.
2003
0
      // Query RecursiveSort will instead only sort descendants if we are a
2004
0
      // constinaersQuery, e.g. a grouped query that will return other queries.
2005
0
      // For other type of queries it will act as the base one.
2006
0
      if (IsContainersQuery() &&
2007
0
          sortType == mOptions->SortingMode() &&
2008
0
          (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
2009
0
           sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING)) {
2010
0
        nsNavHistoryContainerResultNode::RecursiveSort(comparator);
2011
0
      }
2012
0
      else {
2013
0
        RecursiveSort(comparator);
2014
0
      }
2015
0
    }
2016
0
  }
2017
0
2018
0
  // if we are limiting our results remove items from the end of the
2019
0
  // mChildren array after sorting. This is done for root node only.
2020
0
  // note, if count < max results, we won't do anything.
2021
0
  if (!mParent && mOptions->MaxResults()) {
2022
0
    while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
2023
0
      mChildren.RemoveObjectAt(mChildren.Count() - 1);
2024
0
  }
2025
0
2026
0
  // If we're not updating the query, we don't need to add listeners, so bail
2027
0
  // out early.
2028
0
  if (mLiveUpdate == QUERYUPDATE_NONE) {
2029
0
    mContentsValid = true;
2030
0
    return NS_OK;
2031
0
  }
2032
0
2033
0
  nsNavHistoryResult* result = GetResult();
2034
0
  NS_ENSURE_STATE(result);
2035
0
2036
0
  if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
2037
0
      mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED) {
2038
0
    // Date containers that contain site containers have no reason to observe
2039
0
    // history, if the inside site container is expanded it will update,
2040
0
    // otherwise we are going to refresh the parent query.
2041
0
    if (!mParent || mParent->mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
2042
0
      // register with the result for history updates
2043
0
      result->AddHistoryObserver(this);
2044
0
    }
2045
0
  }
2046
0
2047
0
  if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS ||
2048
0
      mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED ||
2049
0
      mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS ||
2050
0
      mHasSearchTerms) {
2051
0
    // register with the result for bookmark updates
2052
0
    result->AddAllBookmarksObserver(this);
2053
0
  }
2054
0
2055
0
  if (mLiveUpdate == QUERYUPDATE_MOBILEPREF) {
2056
0
    result->AddMobilePrefsObserver(this);
2057
0
  }
2058
0
2059
0
  mContentsValid = true;
2060
0
  return NS_OK;
2061
0
}
2062
2063
2064
/**
2065
 * Call with unregister = false when we are going to update the children (for
2066
 * example, when the container is open).  This will clear the list and notify
2067
 * all the children that they are going away.
2068
 *
2069
 * When the results are becoming invalid and we are not going to refresh them,
2070
 * set unregister = true, which will unregister the listener from the
2071
 * result if any.  We use unregister = false when we are refreshing the list
2072
 * immediately so want to stay a notifier.
2073
 */
2074
void
2075
nsNavHistoryQueryResultNode::ClearChildren(bool aUnregister)
2076
0
{
2077
0
  for (int32_t i = 0; i < mChildren.Count(); ++i)
2078
0
    mChildren[i]->OnRemoving();
2079
0
  mChildren.Clear();
2080
0
2081
0
  if (aUnregister && mContentsValid) {
2082
0
    nsNavHistoryResult* result = GetResult();
2083
0
    if (result) {
2084
0
      result->RemoveHistoryObserver(this);
2085
0
      result->RemoveAllBookmarksObserver(this);
2086
0
      result->RemoveMobilePrefsObserver(this);
2087
0
    }
2088
0
  }
2089
0
  mContentsValid = false;
2090
0
}
2091
2092
2093
/**
2094
 * This is called to update the result when something has changed that we
2095
 * can not incrementally update.
2096
 */
2097
nsresult
2098
nsNavHistoryQueryResultNode::Refresh()
2099
0
{
2100
0
  nsNavHistoryResult* result = GetResult();
2101
0
  NS_ENSURE_STATE(result);
2102
0
  if (result->mBatchInProgress) {
2103
0
    result->requestRefresh(this);
2104
0
    return NS_OK;
2105
0
  }
2106
0
2107
0
  // This is not a root node but it does not have a parent - this means that
2108
0
  // the node has already been cleared and it is now called, because it was
2109
0
  // left in a local copy of the observers array.
2110
0
  if (mIndentLevel > -1 && !mParent)
2111
0
    return NS_OK;
2112
0
2113
0
  // Do not refresh if we are not expanded or if we are child of a query
2114
0
  // containing other queries.  In this case calling Refresh for each child
2115
0
  // query could cause a major slowdown.  We should not refresh nested
2116
0
  // queries, since we will already refresh the parent one.
2117
0
  // The only exception to this, is if the parent query is of QUERYUPDATE_NONE,
2118
0
  // this can be the case for the RESULTS_AS_TAGS_ROOT
2119
0
  // under RESULTS_AS_LEFT_PANE_QUERY.
2120
0
  if (!mExpanded) {
2121
0
    ClearChildren(true);
2122
0
    return NS_OK;
2123
0
  }
2124
0
2125
0
  if (mParent && mParent->IsQuery()) {
2126
0
    nsNavHistoryQueryResultNode* parent = mParent->GetAsQuery();
2127
0
    if (parent->IsContainersQuery() && parent->mLiveUpdate != QUERYUPDATE_NONE) {
2128
0
      // Don't update, just invalidate and unhook
2129
0
      ClearChildren(true);
2130
0
      return NS_OK; // no updates in tree state
2131
0
    }
2132
0
  }
2133
0
2134
0
  if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
2135
0
    ClearChildren(true);
2136
0
  else
2137
0
    ClearChildren(false);
2138
0
2139
0
  // Ignore errors from FillChildren, since we will still want to refresh
2140
0
  // the tree (there just might not be anything in it on error).
2141
0
  (void)FillChildren();
2142
0
2143
0
  NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
2144
0
  return NS_OK;
2145
0
}
2146
2147
2148
/**
2149
 * Here, we override GetSortType to return the current sorting for this
2150
 * query.  GetSortType is used when dynamically inserting query results so we
2151
 * can see which comparator we should use to find the proper insertion point
2152
 * (it shouldn't be called from folder containers which maintain their own
2153
 * sorting).
2154
 *
2155
 * Normally, the container just forwards it up the chain.  This is what we want
2156
 * for host groups, for example.  For queries, we often want to use the query's
2157
 * sorting mode.
2158
 *
2159
 * However, we only use this query node's sorting when it is not the root.
2160
 * When it is the root, we use the result's sorting mode.  This is because
2161
 * there are two cases:
2162
 *   - You are looking at a bookmark hierarchy that contains an embedded
2163
 *     result.  We should always use the query's sort ordering since the result
2164
 *     node's headers have nothing to do with us (and are disabled).
2165
 *   - You are looking at a query in the tree.  In this case, we want the
2166
 *     result sorting to override ours (it should be initialized to the same
2167
 *     sorting mode).
2168
 */
2169
uint16_t
2170
nsNavHistoryQueryResultNode::GetSortType()
2171
0
{
2172
0
  if (mParent)
2173
0
    return mOptions->SortingMode();
2174
0
  if (mResult)
2175
0
    return mResult->mSortingMode;
2176
0
2177
0
  // This is a detached container, just use natural order.
2178
0
  return nsINavHistoryQueryOptions::SORT_BY_NONE;
2179
0
}
2180
2181
2182
void
2183
nsNavHistoryQueryResultNode::RecursiveSort(SortComparator aComparator)
2184
0
{
2185
0
  if (!IsContainersQuery())
2186
0
    mChildren.Sort(aComparator, nullptr);
2187
0
2188
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
2189
0
    if (mChildren[i]->IsContainer())
2190
0
      mChildren[i]->GetAsContainer()->RecursiveSort(aComparator);
2191
0
  }
2192
0
}
2193
2194
NS_IMETHODIMP
2195
nsNavHistoryQueryResultNode::GetSkipTags(bool *aSkipTags)
2196
0
{
2197
0
  *aSkipTags = false;
2198
0
  return NS_OK;
2199
0
}
2200
2201
NS_IMETHODIMP
2202
nsNavHistoryQueryResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
2203
0
{
2204
0
  *aSkipDescendantsOnItemRemoval = false;
2205
0
  return NS_OK;
2206
0
}
2207
2208
NS_IMETHODIMP
2209
nsNavHistoryQueryResultNode::OnBeginUpdateBatch()
2210
0
{
2211
0
  return NS_OK;
2212
0
}
2213
2214
NS_IMETHODIMP
2215
nsNavHistoryQueryResultNode::OnEndUpdateBatch()
2216
0
{
2217
0
  // If the query has no children it's possible it's not yet listening to
2218
0
  // bookmarks changes, in such a case it's safer to force a refresh to gather
2219
0
  // eventual new nodes matching query options.
2220
0
  if (mChildren.Count() == 0) {
2221
0
    nsresult rv = Refresh();
2222
0
    NS_ENSURE_SUCCESS(rv, rv);
2223
0
  }
2224
0
2225
0
  mBatchChanges = 0;
2226
0
  return NS_OK;
2227
0
}
2228
2229
static nsresult setHistoryDetailsCallback(nsNavHistoryResultNode* aNode,
2230
                                          const void* aClosure,
2231
                                          const nsNavHistoryResult* aResult)
2232
0
{
2233
0
  const nsNavHistoryResultNode* updatedNode =
2234
0
    static_cast<const nsNavHistoryResultNode*>(aClosure);
2235
0
2236
0
  aNode->mAccessCount = updatedNode->mAccessCount;
2237
0
  aNode->mTime = updatedNode->mTime;
2238
0
  aNode->mFrecency = updatedNode->mFrecency;
2239
0
  aNode->mHidden = updatedNode->mHidden;
2240
0
2241
0
  return NS_OK;
2242
0
}
2243
2244
/**
2245
 * Here we need to update all copies of the URI we have with the new visit
2246
 * count, and potentially add a new entry in our query.  This is the most
2247
 * common update operation and it is important that it be as efficient as
2248
 * possible.
2249
 */
2250
nsresult
2251
nsNavHistoryQueryResultNode::OnVisit(nsIURI* aURI, int64_t aVisitId,
2252
                                     PRTime aTime, uint32_t aTransitionType,
2253
                                     bool aHidden, uint32_t* aAdded)
2254
0
{
2255
0
  if (aHidden && !mOptions->IncludeHidden())
2256
0
    return NS_OK;
2257
0
  // Skip the notification if the query is filtered by specific transition types
2258
0
  // and this visit has a different one.
2259
0
  if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
2260
0
    return NS_OK;
2261
0
2262
0
  nsNavHistoryResult* result = GetResult();
2263
0
  NS_ENSURE_STATE(result);
2264
0
  if (result->mBatchInProgress &&
2265
0
      ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
2266
0
    nsresult rv = Refresh();
2267
0
    NS_ENSURE_SUCCESS(rv, rv);
2268
0
    return NS_OK;
2269
0
  }
2270
0
2271
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
2272
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2273
0
2274
0
  switch(mLiveUpdate) {
2275
0
    case QUERYUPDATE_MOBILEPREF: {
2276
0
      return NS_OK;
2277
0
    }
2278
0
2279
0
    case QUERYUPDATE_HOST: {
2280
0
      // For these simple yet common cases we can check the host ourselves
2281
0
      // before doing the overhead of creating a new result node.
2282
0
      if (mQuery->Domain().IsVoid())
2283
0
        return NS_OK;
2284
0
2285
0
      nsAutoCString host;
2286
0
      if (NS_FAILED(aURI->GetAsciiHost(host)))
2287
0
        return NS_OK;
2288
0
2289
0
      if (!mQuery->Domain().Equals(host))
2290
0
        return NS_OK;
2291
0
2292
0
      // Fall through to check the time, if the time is not present it will
2293
0
      // still match.
2294
0
      MOZ_FALLTHROUGH;
2295
0
    }
2296
0
2297
0
    case QUERYUPDATE_TIME: {
2298
0
      // For these simple yet common cases we can check the time ourselves
2299
0
      // before doing the overhead of creating a new result node.
2300
0
      bool hasIt;
2301
0
      mQuery->GetHasBeginTime(&hasIt);
2302
0
      if (hasIt) {
2303
0
        PRTime beginTime = history->NormalizeTime(mQuery->BeginTimeReference(),
2304
0
                                                  mQuery->BeginTime());
2305
0
        if (aTime < beginTime)
2306
0
          return NS_OK; // before our time range
2307
0
      }
2308
0
      mQuery->GetHasEndTime(&hasIt);
2309
0
      if (hasIt) {
2310
0
        PRTime endTime = history->NormalizeTime(mQuery->EndTimeReference(),
2311
0
                                                mQuery->EndTime());
2312
0
        if (aTime > endTime)
2313
0
          return NS_OK; // after our time range
2314
0
      }
2315
0
      // Now we know that our visit satisfies the time range, fall through to
2316
0
      // the QUERYUPDATE_SIMPLE case below.
2317
0
      MOZ_FALLTHROUGH;
2318
0
    }
2319
0
2320
0
    case QUERYUPDATE_SIMPLE: {
2321
0
      // The history service can tell us whether the new item should appear
2322
0
      // in the result.  We first have to construct a node for it to check.
2323
0
      RefPtr<nsNavHistoryResultNode> addition;
2324
0
      nsresult rv = history->VisitIdToResultNode(aVisitId, mOptions,
2325
0
                                                 getter_AddRefs(addition));
2326
0
      NS_ENSURE_SUCCESS(rv, rv);
2327
0
      if (!addition) {
2328
0
        // Certain result types manage the nodes by themselves.
2329
0
        return NS_OK;
2330
0
      }
2331
0
      addition->mTransitionType = aTransitionType;
2332
0
      if (!evaluateQueryForNode(mQuery, mOptions, addition))
2333
0
        return NS_OK; // don't need to include in our query
2334
0
2335
0
      // Optimization for a common case: if the query has maxResults and is
2336
0
      // sorted by date, get the current boundaries and check if the added visit
2337
0
      // would fit.
2338
0
      // Later, we may have to remove the last child to respect maxResults.
2339
0
      if (mOptions->MaxResults() &&
2340
0
          static_cast<uint32_t>(mChildren.Count()) >= mOptions->MaxResults()) {
2341
0
        uint16_t sortType = GetSortType();
2342
0
        if (sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
2343
0
            aTime > std::max(mChildren[0]->mTime,
2344
0
                             mChildren[mChildren.Count() -1]->mTime)) {
2345
0
          return NS_OK;
2346
0
        }
2347
0
        if (sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING &&
2348
0
            aTime < std::min(mChildren[0]->mTime,
2349
0
                             mChildren[mChildren.Count() -1]->mTime)) {
2350
0
          return NS_OK;
2351
0
        }
2352
0
      }
2353
0
2354
0
      if (mOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
2355
0
        // If this is a visit type query, just insert the new visit.  We never
2356
0
        // update visits, only add or remove them.
2357
0
        rv = InsertSortedChild(addition);
2358
0
        NS_ENSURE_SUCCESS(rv, rv);
2359
0
      } else {
2360
0
        uint16_t sortType = GetSortType();
2361
0
        bool updateSorting =
2362
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
2363
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
2364
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
2365
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
2366
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
2367
0
          sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING;
2368
0
2369
0
        if (!UpdateURIs(false, true, updateSorting, addition->mURI,
2370
0
                        setHistoryDetailsCallback,
2371
0
                        const_cast<void*>(static_cast<void*>(addition.get())))) {
2372
0
          // Couldn't find a node to update.
2373
0
          rv = InsertSortedChild(addition);
2374
0
          NS_ENSURE_SUCCESS(rv, rv);
2375
0
        }
2376
0
      }
2377
0
2378
0
      // Trim the children if necessary.
2379
0
      if (mOptions->MaxResults() &&
2380
0
          static_cast<uint32_t>(mChildren.Count()) > mOptions->MaxResults()) {
2381
0
        mChildren.RemoveObjectAt(mChildren.Count() - 1);
2382
0
      }
2383
0
2384
0
      if (aAdded)
2385
0
        ++(*aAdded);
2386
0
2387
0
      break;
2388
0
    }
2389
0
2390
0
    case QUERYUPDATE_COMPLEX:
2391
0
    case QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
2392
0
      // need to requery in complex cases
2393
0
      return Refresh();
2394
0
2395
0
    default:
2396
0
      MOZ_ASSERT(false, "Invalid value for mLiveUpdate");
2397
0
      return Refresh();
2398
0
  }
2399
0
2400
0
  return NS_OK;
2401
0
}
2402
2403
2404
/**
2405
 * Find every node that matches this URI and rename it.  We try to do
2406
 * incremental updates here, even when we are closed, because changing titles
2407
 * is easier than requerying if we are invalid.
2408
 *
2409
 * This actually gets called a lot.  Typically, we will get an AddURI message
2410
 * when the user visits the page, and then the title will be set asynchronously
2411
 * when the title element of the page is parsed.
2412
 */
2413
NS_IMETHODIMP
2414
nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
2415
                                            const nsAString& aPageTitle,
2416
                                            const nsACString& aGUID)
2417
0
{
2418
0
  if (!mExpanded) {
2419
0
    // When we are not expanded, we don't update, just invalidate and unhook.
2420
0
    // It would still be pretty easy to traverse the results and update the
2421
0
    // titles, but when a title changes, its unlikely that it will be the only
2422
0
    // thing.  Therefore, we just give up.
2423
0
    ClearChildren(true);
2424
0
    return NS_OK; // no updates in tree state
2425
0
  }
2426
0
2427
0
  nsNavHistoryResult* result = GetResult();
2428
0
  NS_ENSURE_STATE(result);
2429
0
  if (result->mBatchInProgress &&
2430
0
      ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
2431
0
    nsresult rv = Refresh();
2432
0
    NS_ENSURE_SUCCESS(rv, rv);
2433
0
    return NS_OK;
2434
0
  }
2435
0
2436
0
  // compute what the new title should be
2437
0
  NS_ConvertUTF16toUTF8 newTitle(aPageTitle);
2438
0
2439
0
  bool onlyOneEntry =
2440
0
    mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
2441
0
2442
0
  // See if our query has any search term matching.
2443
0
  if (mHasSearchTerms) {
2444
0
    // Find all matching URI nodes.
2445
0
    nsCOMArray<nsNavHistoryResultNode> matches;
2446
0
    nsAutoCString spec;
2447
0
    nsresult rv = aURI->GetSpec(spec);
2448
0
    NS_ENSURE_SUCCESS(rv, rv);
2449
0
    RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
2450
0
    if (matches.Count() == 0) {
2451
0
      // This could be a new node matching the query, thus we could need
2452
0
      // to add it to the result.
2453
0
      RefPtr<nsNavHistoryResultNode> node;
2454
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
2455
0
      NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2456
0
      rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
2457
0
      NS_ENSURE_SUCCESS(rv, rv);
2458
0
      if (evaluateQueryForNode(mQuery, mOptions, node)) {
2459
0
        rv = InsertSortedChild(node);
2460
0
        NS_ENSURE_SUCCESS(rv, rv);
2461
0
      }
2462
0
    }
2463
0
    for (int32_t i = 0; i < matches.Count(); ++i) {
2464
0
      // For each matched node we check if it passes the query filter, if not
2465
0
      // we remove the node from the result, otherwise we'll update the title
2466
0
      // later.
2467
0
      nsNavHistoryResultNode* node = matches[i];
2468
0
      // We must check the node with the new title.
2469
0
      node->mTitle = newTitle;
2470
0
2471
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
2472
0
      NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2473
0
      if (!evaluateQueryForNode(mQuery, mOptions, node)) {
2474
0
        nsNavHistoryContainerResultNode* parent = node->mParent;
2475
0
        // URI nodes should always have parents
2476
0
        NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
2477
0
        int32_t childIndex = parent->FindChild(node);
2478
0
        NS_ASSERTION(childIndex >= 0, "Child not found in parent");
2479
0
        parent->RemoveChildAt(childIndex);
2480
0
      }
2481
0
    }
2482
0
  }
2483
0
2484
0
  return ChangeTitles(aURI, newTitle, true, onlyOneEntry);
2485
0
}
2486
2487
2488
NS_IMETHODIMP
2489
nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
2490
                                               int32_t aNewFrecency,
2491
                                               const nsACString& aGUID,
2492
                                               bool aHidden,
2493
                                               PRTime aLastVisitDate)
2494
0
{
2495
0
  return NS_OK;
2496
0
}
2497
2498
2499
NS_IMETHODIMP
2500
nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
2501
0
{
2502
0
  return NS_OK;
2503
0
}
2504
2505
2506
/**
2507
 * Here, we can always live update by just deleting all occurrences of
2508
 * the given URI.
2509
 */
2510
NS_IMETHODIMP
2511
nsNavHistoryQueryResultNode::OnDeleteURI(nsIURI* aURI,
2512
                                         const nsACString& aGUID,
2513
                                         uint16_t aReason)
2514
0
{
2515
0
  nsNavHistoryResult* result = GetResult();
2516
0
  NS_ENSURE_STATE(result);
2517
0
  if (result->mBatchInProgress &&
2518
0
      ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
2519
0
    nsresult rv = Refresh();
2520
0
    NS_ENSURE_SUCCESS(rv, rv);
2521
0
    return NS_OK;
2522
0
  }
2523
0
2524
0
  if (IsContainersQuery()) {
2525
0
    // Incremental updates of query returning queries are pretty much
2526
0
    // complicated.  In this case it's possible one of the child queries has
2527
0
    // no more children and it should be removed.  Unfortunately there is no
2528
0
    // way to know that without executing the child query and counting results.
2529
0
    nsresult rv = Refresh();
2530
0
    NS_ENSURE_SUCCESS(rv, rv);
2531
0
    return NS_OK;
2532
0
  }
2533
0
2534
0
  bool onlyOneEntry =
2535
0
    mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
2536
0
2537
0
  nsAutoCString spec;
2538
0
  nsresult rv = aURI->GetSpec(spec);
2539
0
  NS_ENSURE_SUCCESS(rv, rv);
2540
0
2541
0
  nsCOMArray<nsNavHistoryResultNode> matches;
2542
0
  RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
2543
0
  for (int32_t i = 0; i < matches.Count(); ++i) {
2544
0
    nsNavHistoryResultNode* node = matches[i];
2545
0
    nsNavHistoryContainerResultNode* parent = node->mParent;
2546
0
    // URI nodes should always have parents
2547
0
    NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
2548
0
2549
0
    int32_t childIndex = parent->FindChild(node);
2550
0
    NS_ASSERTION(childIndex >= 0, "Child not found in parent");
2551
0
    parent->RemoveChildAt(childIndex);
2552
0
    if (parent->mChildren.Count() == 0 && parent->IsQuery() &&
2553
0
        parent->mIndentLevel > -1) {
2554
0
      // When query subcontainers (like hosts) get empty we should remove them
2555
0
      // as well.  If the parent is not the root node, append it to our list
2556
0
      // and it will get evaluated later in the loop.
2557
0
      matches.AppendObject(parent);
2558
0
    }
2559
0
  }
2560
0
  return NS_OK;
2561
0
}
2562
2563
2564
NS_IMETHODIMP
2565
nsNavHistoryQueryResultNode::OnClearHistory()
2566
0
{
2567
0
  nsresult rv = Refresh();
2568
0
  NS_ENSURE_SUCCESS(rv, rv);
2569
0
  return NS_OK;
2570
0
}
2571
2572
2573
static nsresult setFaviconCallback(nsNavHistoryResultNode* aNode,
2574
                                   const void * aClosure,
2575
                                   const nsNavHistoryResult* aResult)
2576
0
{
2577
0
  if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
2578
0
    NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
2579
0
2580
0
  return NS_OK;
2581
0
}
2582
2583
2584
NS_IMETHODIMP
2585
nsNavHistoryQueryResultNode::OnPageChanged(nsIURI* aURI,
2586
                                           uint32_t aChangedAttribute,
2587
                                           const nsAString& aNewValue,
2588
                                           const nsACString& aGUID)
2589
0
{
2590
0
  nsAutoCString spec;
2591
0
  nsresult rv = aURI->GetSpec(spec);
2592
0
  NS_ENSURE_SUCCESS(rv, rv);
2593
0
2594
0
  switch (aChangedAttribute) {
2595
0
    case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
2596
0
      bool onlyOneEntry =
2597
0
        mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
2598
0
      UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
2599
0
                 nullptr);
2600
0
      break;
2601
0
    }
2602
0
    default:
2603
0
      NS_WARNING("Unknown page changed notification");
2604
0
  }
2605
0
  return NS_OK;
2606
0
}
2607
2608
2609
NS_IMETHODIMP
2610
nsNavHistoryQueryResultNode::OnDeleteVisits(nsIURI* aURI,
2611
                                            bool aPartialRemoval,
2612
                                            const nsACString& aGUID,
2613
                                            uint16_t aReason,
2614
                                            uint32_t aTransitionType)
2615
0
{
2616
0
  MOZ_ASSERT(mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY,
2617
0
             "Bookmarks queries should not get a OnDeleteVisits notification");
2618
0
2619
0
  if (!aPartialRemoval) {
2620
0
    // All visits for this uri have been removed, but the uri won't be removed
2621
0
    // from the databse, most likely because it's a bookmark.  For a history
2622
0
    // query this is equivalent to a onDeleteURI notification.
2623
0
    nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
2624
0
    NS_ENSURE_SUCCESS(rv, rv);
2625
0
  }
2626
0
  if (aTransitionType > 0) {
2627
0
    // All visits for aTransitionType have been removed, if the query is
2628
0
    // filtering on such transition type, this is equivalent to an onDeleteURI
2629
0
    // notification.
2630
0
    if (mTransitions.Length() > 0 && mTransitions.Contains(aTransitionType)) {
2631
0
      nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
2632
0
      NS_ENSURE_SUCCESS(rv, rv);
2633
0
    }
2634
0
  }
2635
0
2636
0
  return NS_OK;
2637
0
}
2638
2639
nsresult
2640
nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI)
2641
0
{
2642
0
  nsNavHistoryResult* result = GetResult();
2643
0
  NS_ENSURE_STATE(result);
2644
0
  nsAutoCString spec;
2645
0
  nsresult rv = aURI->GetSpec(spec);
2646
0
  NS_ENSURE_SUCCESS(rv, rv);
2647
0
  bool onlyOneEntry =
2648
0
    mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
2649
0
2650
0
  // Find matching URI nodes.
2651
0
  RefPtr<nsNavHistoryResultNode> node;
2652
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
2653
0
2654
0
  nsCOMArray<nsNavHistoryResultNode> matches;
2655
0
  RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
2656
0
2657
0
  if (matches.Count() == 0 && mHasSearchTerms) {
2658
0
    // A new tag has been added, it's possible it matches our query.
2659
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2660
0
    rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
2661
0
    NS_ENSURE_SUCCESS(rv, rv);
2662
0
    if (evaluateQueryForNode(mQuery, mOptions, node)) {
2663
0
      rv = InsertSortedChild(node);
2664
0
      NS_ENSURE_SUCCESS(rv, rv);
2665
0
    }
2666
0
  }
2667
0
2668
0
  for (int32_t i = 0; i < matches.Count(); ++i) {
2669
0
    nsNavHistoryResultNode* node = matches[i];
2670
0
    // Force a tags update before checking the node.
2671
0
    node->mTags.SetIsVoid(true);
2672
0
    nsAutoString tags;
2673
0
    rv = node->GetTags(tags);
2674
0
    NS_ENSURE_SUCCESS(rv, rv);
2675
0
    // It's possible now this node does not respect anymore the conditions.
2676
0
    // In such a case it should be removed.
2677
0
    if (mHasSearchTerms &&
2678
0
        !evaluateQueryForNode(mQuery, mOptions, node)) {
2679
0
      nsNavHistoryContainerResultNode* parent = node->mParent;
2680
0
      // URI nodes should always have parents
2681
0
      NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
2682
0
      int32_t childIndex = parent->FindChild(node);
2683
0
      NS_ASSERTION(childIndex >= 0, "Child not found in parent");
2684
0
      parent->RemoveChildAt(childIndex);
2685
0
    }
2686
0
    else {
2687
0
      NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(node));
2688
0
    }
2689
0
  }
2690
0
2691
0
  return NS_OK;
2692
0
}
2693
2694
/**
2695
 * These are the bookmark observer functions for query nodes.  They listen
2696
 * for bookmark events and refresh the results if we have any dependence on
2697
 * the bookmark system.
2698
 */
2699
NS_IMETHODIMP
2700
nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
2701
                                         int64_t aParentId,
2702
                                         int32_t aIndex,
2703
                                         uint16_t aItemType,
2704
                                         nsIURI* aURI,
2705
                                         const nsACString& aTitle,
2706
                                         PRTime aDateAdded,
2707
                                         const nsACString& aGUID,
2708
                                         const nsACString& aParentGUID,
2709
                                         uint16_t aSource)
2710
0
{
2711
0
  if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
2712
0
      mLiveUpdate != QUERYUPDATE_SIMPLE &&
2713
0
      mLiveUpdate != QUERYUPDATE_TIME &&
2714
0
      mLiveUpdate != QUERYUPDATE_MOBILEPREF) {
2715
0
    nsresult rv = Refresh();
2716
0
    NS_ENSURE_SUCCESS(rv, rv);
2717
0
  }
2718
0
  return NS_OK;
2719
0
}
2720
2721
2722
NS_IMETHODIMP
2723
nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
2724
                                           int64_t aParentId,
2725
                                           int32_t aIndex,
2726
                                           uint16_t aItemType,
2727
                                           nsIURI* aURI,
2728
                                           const nsACString& aGUID,
2729
                                           const nsACString& aParentGUID,
2730
                                           uint16_t aSource)
2731
0
{
2732
0
  if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
2733
0
      mLiveUpdate != QUERYUPDATE_SIMPLE &&
2734
0
      mLiveUpdate != QUERYUPDATE_TIME &&
2735
0
      mLiveUpdate != QUERYUPDATE_MOBILEPREF) {
2736
0
    nsresult rv = Refresh();
2737
0
    NS_ENSURE_SUCCESS(rv, rv);
2738
0
  }
2739
0
  return NS_OK;
2740
0
}
2741
2742
2743
NS_IMETHODIMP
2744
nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
2745
                                           const nsACString& aProperty,
2746
                                           bool aIsAnnotationProperty,
2747
                                           const nsACString& aNewValue,
2748
                                           PRTime aLastModified,
2749
                                           uint16_t aItemType,
2750
                                           int64_t aParentId,
2751
                                           const nsACString& aGUID,
2752
                                           const nsACString& aParentGUID,
2753
                                           const nsACString& aOldValue,
2754
                                           uint16_t aSource)
2755
0
{
2756
0
  if (aItemType != nsINavBookmarksService::TYPE_BOOKMARK) {
2757
0
    // No separators or folders in queries.
2758
0
    return NS_OK;
2759
0
  }
2760
0
2761
0
  // Update ourselves first.
2762
0
  nsresult rv = nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
2763
0
                                                      aIsAnnotationProperty,
2764
0
                                                      aNewValue, aLastModified,
2765
0
                                                      aItemType, aParentId,
2766
0
                                                      aGUID, aParentGUID,
2767
0
                                                      aOldValue, aSource);
2768
0
  NS_ENSURE_SUCCESS(rv, rv);
2769
0
2770
0
  // Did our uri change?
2771
0
  if (aItemId == mItemId && aProperty.EqualsLiteral("uri")) {
2772
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
2773
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
2774
0
    nsCOMPtr<nsINavHistoryQuery> query;
2775
0
    nsCOMPtr<nsINavHistoryQueryOptions> options;
2776
0
    rv = history->QueryStringToQuery(mURI, getter_AddRefs(query),
2777
0
                                           getter_AddRefs(options));
2778
0
    NS_ENSURE_SUCCESS(rv, rv);
2779
0
    mQuery = do_QueryObject(query);
2780
0
    NS_ENSURE_STATE(mQuery);
2781
0
    mOptions = do_QueryObject(options);
2782
0
    NS_ENSURE_STATE(mOptions);
2783
0
    rv = mOptions->Clone(getter_AddRefs(mOriginalOptions));
2784
0
    NS_ENSURE_SUCCESS(rv, rv);
2785
0
  }
2786
0
2787
0
  // History observers should not get OnItemChanged but should get the
2788
0
  // corresponding history notifications instead.
2789
0
  // For bookmark queries, "all bookmark" observers should get OnItemChanged.
2790
0
  // For example, when a title of a bookmark changes, we want that to refresh.
2791
0
  if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) {
2792
0
    return Refresh();
2793
0
  }
2794
0
2795
0
  // Some node could observe both bookmarks and history.  But a node observing
2796
0
  // only history should never get a bookmark notification.
2797
0
  NS_WARNING_ASSERTION(
2798
0
    mResult && (mResult->mIsAllBookmarksObserver ||
2799
0
                mResult->mIsBookmarkFolderObserver),
2800
0
    "history observers should not get OnItemChanged, but should get the "
2801
0
    "corresponding history notifications instead");
2802
0
2803
0
  // Tags in history queries are a special case since tags are per uri and
2804
0
  // we filter tags based on searchterms.
2805
0
  if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
2806
0
      aProperty.EqualsLiteral("tags")) {
2807
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
2808
0
    NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
2809
0
    nsCOMPtr<nsIURI> uri;
2810
0
    nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(uri));
2811
0
    NS_ENSURE_SUCCESS(rv, rv);
2812
0
    rv = NotifyIfTagsChanged(uri);
2813
0
    NS_ENSURE_SUCCESS(rv, rv);
2814
0
  }
2815
0
  return NS_OK;
2816
0
}
2817
2818
NS_IMETHODIMP
2819
nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
2820
                                           int64_t aVisitId,
2821
                                           PRTime aTime,
2822
                                           uint32_t aTransitionType,
2823
                                           nsIURI* aURI,
2824
                                           int64_t aParentId,
2825
                                           const nsACString& aGUID,
2826
                                           const nsACString& aParentGUID)
2827
0
{
2828
0
  // for bookmark queries, "all bookmark" observer should get OnItemVisited
2829
0
  // but it is ignored.
2830
0
  if (mLiveUpdate != QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
2831
0
    NS_WARNING_ASSERTION(
2832
0
      mResult && (mResult->mIsAllBookmarksObserver ||
2833
0
                  mResult->mIsBookmarkFolderObserver),
2834
0
      "history observers should not get OnItemVisited, but should get OnVisit "
2835
0
      "instead");
2836
0
  return NS_OK;
2837
0
}
2838
2839
NS_IMETHODIMP
2840
nsNavHistoryQueryResultNode::OnItemMoved(int64_t aFolder,
2841
                                         int64_t aOldParent,
2842
                                         int32_t aOldIndex,
2843
                                         int64_t aNewParent,
2844
                                         int32_t aNewIndex,
2845
                                         uint16_t aItemType,
2846
                                         const nsACString& aGUID,
2847
                                         const nsACString& aOldParentGUID,
2848
                                         const nsACString& aNewParentGUID,
2849
                                         uint16_t aSource,
2850
                                         const nsACString& aURI)
2851
0
{
2852
0
  // 1. The query cannot be affected by the item's position
2853
0
  // 2. For the time being, we cannot optimize this not to update
2854
0
  //    queries which are not restricted to some folders, due to way
2855
0
  //    sub-queries are updated (see Refresh)
2856
0
  if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS &&
2857
0
      aItemType != nsINavBookmarksService::TYPE_SEPARATOR &&
2858
0
      aOldParent != aNewParent) {
2859
0
    return Refresh();
2860
0
  }
2861
0
  return NS_OK;
2862
0
}
2863
2864
/**
2865
 * HOW DYNAMIC FOLDER UPDATING WORKS
2866
 *
2867
 * When you create a result, it will automatically keep itself in sync with
2868
 * stuff that happens in the system.  For folder nodes, this means changes to
2869
 * bookmarks.
2870
 *
2871
 * A folder will fill its children "when necessary." This means it is being
2872
 * opened or whether we need to see if it is empty for twisty drawing.  It will
2873
 * then register its ID with the main result object that owns it.  This result
2874
 * object will listen for all bookmark notifications and pass those
2875
 * notifications to folder nodes that have registered for that specific folder
2876
 * ID.
2877
 *
2878
 * When a bookmark folder is closed, it will not clear its children.  Instead,
2879
 * it will keep them and also stay registered as a listener.  This means that
2880
 * you can more quickly re-open the same folder without doing any work.  This
2881
 * happens a lot for menus, and bookmarks don't change very often.
2882
 *
2883
 * When a message comes in and the folder is open, we will do the correct
2884
 * operations to keep ourselves in sync with the bookmark service.  If the
2885
 * folder is closed, we just clear our list to mark it as invalid and
2886
 * unregister as a listener.  This means we do not have to keep maintaining
2887
 * an up-to-date list for the entire bookmark menu structure in every place
2888
 * it is used.
2889
 */
2890
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryFolderResultNode,
2891
                            nsNavHistoryContainerResultNode,
2892
                            nsINavHistoryQueryResultNode,
2893
                            mozIStorageStatementCallback)
2894
2895
nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
2896
    const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
2897
    int64_t aFolderId) :
2898
  nsNavHistoryContainerResultNode(EmptyCString(), aTitle, 0,
2899
                                  nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
2900
                                  aOptions),
2901
  mContentsValid(false),
2902
  mTargetFolderItemId(aFolderId),
2903
  mIsRegisteredFolderObserver(false)
2904
0
{
2905
0
  mItemId = aFolderId;
2906
0
}
2907
2908
nsNavHistoryFolderResultNode::~nsNavHistoryFolderResultNode()
2909
0
{
2910
0
  if (mIsRegisteredFolderObserver && mResult)
2911
0
    mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
2912
0
}
2913
2914
2915
/**
2916
 * Here we do not want to call ContainerResultNode::OnRemoving since our own
2917
 * ClearChildren will do the same thing and more (unregister the observers).
2918
 * The base ResultNode::OnRemoving will clear some regular node stats, so it is
2919
 * OK.
2920
 */
2921
void
2922
nsNavHistoryFolderResultNode::OnRemoving()
2923
0
{
2924
0
  nsNavHistoryResultNode::OnRemoving();
2925
0
  ClearChildren(true);
2926
0
  mResult = nullptr;
2927
0
}
2928
2929
2930
nsresult
2931
nsNavHistoryFolderResultNode::OpenContainer()
2932
0
{
2933
0
  NS_ASSERTION(!mExpanded, "Container must be expanded to close it");
2934
0
  nsresult rv;
2935
0
2936
0
  if (!mContentsValid) {
2937
0
    rv = FillChildren();
2938
0
    NS_ENSURE_SUCCESS(rv, rv);
2939
0
  }
2940
0
  mExpanded = true;
2941
0
2942
0
  rv = NotifyOnStateChange(STATE_CLOSED);
2943
0
  NS_ENSURE_SUCCESS(rv, rv);
2944
0
2945
0
  return NS_OK;
2946
0
}
2947
2948
2949
/**
2950
 * The async version of OpenContainer.
2951
 */
2952
nsresult
2953
nsNavHistoryFolderResultNode::OpenContainerAsync()
2954
0
{
2955
0
  NS_ASSERTION(!mExpanded, "Container already expanded when opening it");
2956
0
2957
0
  // If the children are valid, open the container synchronously.  This will be
2958
0
  // the case when the container has already been opened and any other time
2959
0
  // FillChildren or FillChildrenAsync has previously been called.
2960
0
  if (mContentsValid)
2961
0
    return OpenContainer();
2962
0
2963
0
  nsresult rv = FillChildrenAsync();
2964
0
  NS_ENSURE_SUCCESS(rv, rv);
2965
0
2966
0
  rv = NotifyOnStateChange(STATE_CLOSED);
2967
0
  NS_ENSURE_SUCCESS(rv, rv);
2968
0
2969
0
  return NS_OK;
2970
0
}
2971
2972
2973
/**
2974
 * @see nsNavHistoryQueryResultNode::HasChildren.  The semantics here are a
2975
 * little different.  Querying the contents of a bookmark folder is relatively
2976
 * fast and it is common to have empty folders.  Therefore, we always want to
2977
 * return the correct result so that twisties are drawn properly.
2978
 */
2979
NS_IMETHODIMP
2980
nsNavHistoryFolderResultNode::GetHasChildren(bool* aHasChildren)
2981
0
{
2982
0
  if (!mContentsValid) {
2983
0
    nsresult rv = FillChildren();
2984
0
    NS_ENSURE_SUCCESS(rv, rv);
2985
0
  }
2986
0
  *aHasChildren = (mChildren.Count() > 0);
2987
0
  return NS_OK;
2988
0
}
2989
2990
NS_IMETHODIMP
2991
nsNavHistoryFolderResultNode::GetFolderItemId(int64_t* aItemId)
2992
0
{
2993
0
  *aItemId = mTargetFolderItemId;
2994
0
  return NS_OK;
2995
0
}
2996
2997
NS_IMETHODIMP
2998
0
nsNavHistoryFolderResultNode::GetTargetFolderGuid(nsACString& aGuid) {
2999
0
  aGuid = mTargetFolderGuid;
3000
0
  return NS_OK;
3001
0
}
3002
3003
/**
3004
 * Lazily computes the URI for this specific folder query with the current
3005
 * options.
3006
 */
3007
NS_IMETHODIMP
3008
nsNavHistoryFolderResultNode::GetUri(nsACString& aURI)
3009
0
{
3010
0
  if (!mURI.IsEmpty()) {
3011
0
    aURI = mURI;
3012
0
    return NS_OK;
3013
0
  }
3014
0
3015
0
  nsCOMPtr<nsINavHistoryQuery> query;
3016
0
  nsresult rv = GetQuery(getter_AddRefs(query));
3017
0
  NS_ENSURE_SUCCESS(rv, rv);
3018
0
3019
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
3020
0
  NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
3021
0
  rv = history->QueryToQueryString(query, mOriginalOptions, mURI);
3022
0
  NS_ENSURE_SUCCESS(rv, rv);
3023
0
  aURI = mURI;
3024
0
  return NS_OK;
3025
0
}
3026
3027
3028
/**
3029
 * @return the queries that give you this bookmarks folder
3030
 */
3031
NS_IMETHODIMP
3032
nsNavHistoryFolderResultNode::GetQuery(nsINavHistoryQuery** _query)
3033
0
{
3034
0
  // get the query object
3035
0
  RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
3036
0
3037
0
  nsTArray<nsCString> parents;
3038
0
  // query just has the folder ID set and nothing else
3039
0
  if (!parents.AppendElement(mTargetFolderGuid)) {
3040
0
    return NS_ERROR_OUT_OF_MEMORY;
3041
0
  }
3042
0
  nsresult rv = query->SetParents(parents);
3043
0
  NS_ENSURE_SUCCESS(rv, rv);
3044
0
3045
0
  query.forget(_query);
3046
0
  return NS_OK;
3047
0
}
3048
3049
3050
/**
3051
 * Options for the query that gives you this bookmarks folder.  This is just
3052
 * the options for the folder with the current folder ID set.
3053
 */
3054
NS_IMETHODIMP
3055
nsNavHistoryFolderResultNode::GetQueryOptions(nsINavHistoryQueryOptions** _options)
3056
0
{
3057
0
  MOZ_ASSERT(mOptions, "Options should be valid");
3058
0
  nsCOMPtr<nsINavHistoryQueryOptions> options = do_QueryInterface(mOptions);
3059
0
  options.forget(_options);
3060
0
  return NS_OK;
3061
0
}
3062
3063
3064
nsresult
3065
nsNavHistoryFolderResultNode::FillChildren()
3066
0
{
3067
0
  NS_ASSERTION(!mContentsValid,
3068
0
               "Don't call FillChildren when contents are valid");
3069
0
  NS_ASSERTION(mChildren.Count() == 0,
3070
0
               "We are trying to fill children when there already are some");
3071
0
3072
0
  nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
3073
0
  NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3074
0
3075
0
  // Actually get the folder children from the bookmark service.
3076
0
  nsresult rv = bookmarks->QueryFolderChildren(mTargetFolderItemId,
3077
0
                                               mOptions,
3078
0
                                               &mChildren);
3079
0
  NS_ENSURE_SUCCESS(rv, rv);
3080
0
3081
0
  // PERFORMANCE: it may be better to also fill any child folders at this point
3082
0
  // so that we can draw tree twisties without doing a separate query later.
3083
0
  // If we don't end up drawing twisties a lot, it doesn't matter. If we do
3084
0
  // this, we should wrap everything in a transaction here on the bookmark
3085
0
  // service's connection.
3086
0
3087
0
  return OnChildrenFilled();
3088
0
}
3089
3090
3091
/**
3092
 * Performs some tasks after all the children of the container have been added.
3093
 * The container's contents are not valid until this method has been called.
3094
 */
3095
nsresult
3096
nsNavHistoryFolderResultNode::OnChildrenFilled()
3097
0
{
3098
0
  // It is important to call FillStats to fill in the parents on all
3099
0
  // nodes and the result node pointers on the containers.
3100
0
  FillStats();
3101
0
3102
0
  if (mResult && mResult->mNeedsToApplySortingMode) {
3103
0
    // We should repopulate container and then apply sortingMode.  To avoid
3104
0
    // sorting 2 times we simply do that here.
3105
0
    mResult->SetSortingMode(mResult->mSortingMode);
3106
0
  }
3107
0
  else {
3108
0
    // Once we've computed all tree stats, we can sort, because containers will
3109
0
    // then have proper visit counts and dates.
3110
0
    SortComparator comparator = GetSortingComparator(GetSortType());
3111
0
    if (comparator) {
3112
0
      RecursiveSort(comparator);
3113
0
    }
3114
0
  }
3115
0
3116
0
  // If we are limiting our results remove items from the end of the
3117
0
  // mChildren array after sorting.  This is done for root node only.
3118
0
  // Note, if count < max results, we won't do anything.
3119
0
  if (!mParent && mOptions->MaxResults()) {
3120
0
    while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
3121
0
      mChildren.RemoveObjectAt(mChildren.Count() - 1);
3122
0
  }
3123
0
3124
0
  // Register with the result for updates.
3125
0
  EnsureRegisteredAsFolderObserver();
3126
0
3127
0
  mContentsValid = true;
3128
0
  return NS_OK;
3129
0
}
3130
3131
3132
/**
3133
 * Registers the node with its result as a folder observer if it is not already
3134
 * registered.
3135
 */
3136
void
3137
nsNavHistoryFolderResultNode::EnsureRegisteredAsFolderObserver()
3138
0
{
3139
0
  if (!mIsRegisteredFolderObserver && mResult) {
3140
0
    mResult->AddBookmarkFolderObserver(this, mTargetFolderItemId);
3141
0
    mIsRegisteredFolderObserver = true;
3142
0
  }
3143
0
}
3144
3145
3146
/**
3147
 * The async version of FillChildren.  This begins asynchronous execution by
3148
 * calling nsNavBookmarks::QueryFolderChildrenAsync.  During execution, this
3149
 * node's async Storage callbacks, HandleResult and HandleCompletion, will be
3150
 * called.
3151
 */
3152
nsresult
3153
nsNavHistoryFolderResultNode::FillChildrenAsync()
3154
0
{
3155
0
  NS_ASSERTION(!mContentsValid, "FillChildrenAsync when contents are valid");
3156
0
  NS_ASSERTION(mChildren.Count() == 0, "FillChildrenAsync when children exist");
3157
0
3158
0
  // ProcessFolderNodeChild, called in HandleResult, increments this for every
3159
0
  // result row it processes.  Initialize it here as we begin async execution.
3160
0
  mAsyncBookmarkIndex = -1;
3161
0
3162
0
  nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
3163
0
  NS_ENSURE_TRUE(bmSvc, NS_ERROR_OUT_OF_MEMORY);
3164
0
  nsresult rv =
3165
0
    bmSvc->QueryFolderChildrenAsync(this, getter_AddRefs(mAsyncPendingStmt));
3166
0
  NS_ENSURE_SUCCESS(rv, rv);
3167
0
3168
0
  // Register with the result for updates.  All updates during async execution
3169
0
  // will cause it to be restarted.
3170
0
  EnsureRegisteredAsFolderObserver();
3171
0
3172
0
  return NS_OK;
3173
0
}
3174
3175
3176
/**
3177
 * A mozIStorageStatementCallback method.  Called during the async execution
3178
 * begun by FillChildrenAsync.
3179
 *
3180
 * @param aResultSet
3181
 *        The result set containing the data from the database.
3182
 */
3183
NS_IMETHODIMP
3184
nsNavHistoryFolderResultNode::HandleResult(mozIStorageResultSet* aResultSet)
3185
0
{
3186
0
  NS_ENSURE_ARG_POINTER(aResultSet);
3187
0
3188
0
  nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
3189
0
  if (!bmSvc) {
3190
0
    CancelAsyncOpen(false);
3191
0
    return NS_ERROR_OUT_OF_MEMORY;
3192
0
  }
3193
0
3194
0
  // Consume all the currently available rows of the result set.
3195
0
  nsCOMPtr<mozIStorageRow> row;
3196
0
  while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
3197
0
    nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOptions, &mChildren,
3198
0
                                              mAsyncBookmarkIndex);
3199
0
    if (NS_FAILED(rv)) {
3200
0
      CancelAsyncOpen(false);
3201
0
      return rv;
3202
0
    }
3203
0
  }
3204
0
3205
0
  return NS_OK;
3206
0
}
3207
3208
3209
/**
3210
 * A mozIStorageStatementCallback method.  Called during the async execution
3211
 * begun by FillChildrenAsync.
3212
 *
3213
 * @param aReason
3214
 *        Indicates the final state of execution.
3215
 */
3216
NS_IMETHODIMP
3217
nsNavHistoryFolderResultNode::HandleCompletion(uint16_t aReason)
3218
0
{
3219
0
  if (aReason == mozIStorageStatementCallback::REASON_FINISHED &&
3220
0
      mAsyncCanceledState == NOT_CANCELED) {
3221
0
    // Async execution successfully completed.  The container is ready to open.
3222
0
3223
0
    nsresult rv = OnChildrenFilled();
3224
0
    NS_ENSURE_SUCCESS(rv, rv);
3225
0
3226
0
    mExpanded = true;
3227
0
    mAsyncPendingStmt = nullptr;
3228
0
3229
0
    // Notify observers only after mExpanded and mAsyncPendingStmt are set.
3230
0
    rv = NotifyOnStateChange(STATE_LOADING);
3231
0
    NS_ENSURE_SUCCESS(rv, rv);
3232
0
  }
3233
0
3234
0
  else if (mAsyncCanceledState == CANCELED_RESTART_NEEDED) {
3235
0
    // Async execution was canceled and needs to be restarted.
3236
0
    mAsyncCanceledState = NOT_CANCELED;
3237
0
    ClearChildren(false);
3238
0
    FillChildrenAsync();
3239
0
  }
3240
0
3241
0
  else {
3242
0
    // Async execution failed or was canceled without restart.  Remove all
3243
0
    // children and close the container, notifying observers.
3244
0
    mAsyncCanceledState = NOT_CANCELED;
3245
0
    ClearChildren(true);
3246
0
    CloseContainer();
3247
0
  }
3248
0
3249
0
  return NS_OK;
3250
0
}
3251
3252
3253
void
3254
nsNavHistoryFolderResultNode::ClearChildren(bool unregister)
3255
0
{
3256
0
  for (int32_t i = 0; i < mChildren.Count(); ++i)
3257
0
    mChildren[i]->OnRemoving();
3258
0
  mChildren.Clear();
3259
0
3260
0
  bool needsUnregister = unregister && (mContentsValid || mAsyncPendingStmt);
3261
0
  if (needsUnregister && mResult && mIsRegisteredFolderObserver) {
3262
0
    mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
3263
0
    mIsRegisteredFolderObserver = false;
3264
0
  }
3265
0
  mContentsValid = false;
3266
0
}
3267
3268
3269
/**
3270
 * This is called to update the result when something has changed that we
3271
 * can not incrementally update.
3272
 */
3273
nsresult
3274
nsNavHistoryFolderResultNode::Refresh()
3275
0
{
3276
0
  nsNavHistoryResult* result = GetResult();
3277
0
  NS_ENSURE_STATE(result);
3278
0
  if (result->mBatchInProgress) {
3279
0
    result->requestRefresh(this);
3280
0
    return NS_OK;
3281
0
  }
3282
0
3283
0
  ClearChildren(true);
3284
0
3285
0
  if (!mExpanded) {
3286
0
    // When we are not expanded, we don't update, just invalidate and unhook.
3287
0
    return NS_OK;
3288
0
  }
3289
0
3290
0
  // Ignore errors from FillChildren, since we will still want to refresh
3291
0
  // the tree (there just might not be anything in it on error).  ClearChildren
3292
0
  // has unregistered us as an observer since FillChildren will try to
3293
0
  // re-register us.
3294
0
  (void)FillChildren();
3295
0
3296
0
  NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
3297
0
  return NS_OK;
3298
0
}
3299
3300
3301
/**
3302
 * Implements the logic described above the constructor.  This sees if we
3303
 * should do an incremental update and returns true if so.  If not, it
3304
 * invalidates our children, unregisters us an observer, and returns false.
3305
 */
3306
bool
3307
nsNavHistoryFolderResultNode::StartIncrementalUpdate()
3308
0
{
3309
0
  // if any items are excluded, we can not do incremental updates since the
3310
0
  // indices from the bookmark service will not be valid
3311
0
3312
0
  if (!mOptions->ExcludeItems() &&
3313
0
      !mOptions->ExcludeQueries() &&
3314
0
      !mOptions->ExcludeReadOnlyFolders()) {
3315
0
    // easy case: we are visible, always do incremental update
3316
0
    if (mExpanded || AreChildrenVisible())
3317
0
      return true;
3318
0
3319
0
    nsNavHistoryResult* result = GetResult();
3320
0
    NS_ENSURE_TRUE(result, false);
3321
0
3322
0
    // When any observers are attached also do incremental updates if our
3323
0
    // parent is visible, so that twisties are drawn correctly.
3324
0
    if (mParent)
3325
0
      return result->mObservers.Length() > 0;
3326
0
  }
3327
0
3328
0
  // otherwise, we don't do incremental updates, invalidate and unregister
3329
0
  (void)Refresh();
3330
0
  return false;
3331
0
}
3332
3333
3334
/**
3335
 * This function adds aDelta to all bookmark indices between the two endpoints,
3336
 * inclusive.  It is used when items are added or removed from the bookmark
3337
 * folder.
3338
 */
3339
void
3340
nsNavHistoryFolderResultNode::ReindexRange(int32_t aStartIndex,
3341
                                           int32_t aEndIndex,
3342
                                           int32_t aDelta)
3343
0
{
3344
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
3345
0
    nsNavHistoryResultNode* node = mChildren[i];
3346
0
    if (node->mBookmarkIndex >= aStartIndex &&
3347
0
        node->mBookmarkIndex <= aEndIndex)
3348
0
      node->mBookmarkIndex += aDelta;
3349
0
  }
3350
0
}
3351
3352
3353
/**
3354
 * Searches this folder for a node with the given id/target-folder-id.
3355
 *
3356
 * @return the node if found, null otherwise.
3357
 * @note Does not addref the node!
3358
 */
3359
nsNavHistoryResultNode*
3360
nsNavHistoryFolderResultNode::FindChildById(int64_t aItemId,
3361
    uint32_t* aNodeIndex)
3362
0
{
3363
0
  for (int32_t i = 0; i < mChildren.Count(); ++i) {
3364
0
    if (mChildren[i]->mItemId == aItemId ||
3365
0
        (mChildren[i]->IsFolder() &&
3366
0
         mChildren[i]->GetAsFolder()->mTargetFolderItemId == aItemId)) {
3367
0
      *aNodeIndex = i;
3368
0
      return mChildren[i];
3369
0
    }
3370
0
  }
3371
0
  return nullptr;
3372
0
}
3373
3374
3375
// Used by nsNavHistoryFolderResultNode's nsINavBookmarkObserver methods below.
3376
// If the container is notified of a bookmark event while asynchronous execution
3377
// is pending, this restarts it and returns.
3378
#define RESTART_AND_RETURN_IF_ASYNC_PENDING() \
3379
0
  if (mAsyncPendingStmt) { \
3380
0
    CancelAsyncOpen(true); \
3381
0
    return NS_OK; \
3382
0
  }
3383
3384
NS_IMETHODIMP
3385
nsNavHistoryFolderResultNode::GetSkipTags(bool *aSkipTags)
3386
0
{
3387
0
  *aSkipTags = false;
3388
0
  return NS_OK;
3389
0
}
3390
3391
NS_IMETHODIMP
3392
nsNavHistoryFolderResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
3393
0
{
3394
0
  *aSkipDescendantsOnItemRemoval = false;
3395
0
  return NS_OK;
3396
0
}
3397
3398
NS_IMETHODIMP
3399
nsNavHistoryFolderResultNode::OnBeginUpdateBatch()
3400
0
{
3401
0
  return NS_OK;
3402
0
}
3403
3404
3405
NS_IMETHODIMP
3406
nsNavHistoryFolderResultNode::OnEndUpdateBatch()
3407
0
{
3408
0
  return NS_OK;
3409
0
}
3410
3411
3412
NS_IMETHODIMP
3413
nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
3414
                                          int64_t aParentFolder,
3415
                                          int32_t aIndex,
3416
                                          uint16_t aItemType,
3417
                                          nsIURI* aURI,
3418
                                          const nsACString& aTitle,
3419
                                          PRTime aDateAdded,
3420
                                          const nsACString& aGUID,
3421
                                          const nsACString& aParentGUID,
3422
                                          uint16_t aSource)
3423
0
{
3424
0
  MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
3425
0
3426
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3427
0
3428
0
  {
3429
0
    uint32_t index;
3430
0
    nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
3431
0
    // Bug 1097528.
3432
0
    // It's possible our result registered due to a previous notification, for
3433
0
    // example the Library left pane could have refreshed and replaced the
3434
0
    // right pane as a consequence. In such a case our contents are already
3435
0
    // up-to-date.  That's OK.
3436
0
    if (node)
3437
0
      return NS_OK;
3438
0
  }
3439
0
3440
0
  bool excludeItems = mOptions->ExcludeItems();
3441
0
3442
0
  // here, try to do something reasonable if the bookmark service gives us
3443
0
  // a bogus index.
3444
0
  if (aIndex < 0) {
3445
0
    MOZ_ASSERT_UNREACHABLE("Invalid index for item adding: <0");
3446
0
    aIndex = 0;
3447
0
  }
3448
0
  else if (aIndex > mChildren.Count()) {
3449
0
    if (!excludeItems) {
3450
0
      // Something wrong happened while updating indexes.
3451
0
      MOZ_ASSERT_UNREACHABLE("Invalid index for item adding: greater than "
3452
0
                             "count");
3453
0
    }
3454
0
    aIndex = mChildren.Count();
3455
0
  }
3456
0
3457
0
  nsresult rv;
3458
0
3459
0
  // Check for query URIs, which are bookmarks, but treated as containers
3460
0
  // in results and views.
3461
0
  bool isQuery = false;
3462
0
  if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
3463
0
    NS_ASSERTION(aURI, "Got a null URI when we are a bookmark?!");
3464
0
    nsAutoCString itemURISpec;
3465
0
    rv = aURI->GetSpec(itemURISpec);
3466
0
    NS_ENSURE_SUCCESS(rv, rv);
3467
0
    isQuery = IsQueryURI(itemURISpec);
3468
0
  }
3469
0
3470
0
  if (aItemType != nsINavBookmarksService::TYPE_FOLDER &&
3471
0
      !isQuery && excludeItems) {
3472
0
    // don't update items when we aren't displaying them, but we still need
3473
0
    // to adjust bookmark indices to account for the insertion
3474
0
    ReindexRange(aIndex, INT32_MAX, 1);
3475
0
    return NS_OK;
3476
0
  }
3477
0
3478
0
  if (!StartIncrementalUpdate())
3479
0
    return NS_OK; // folder was completely refreshed for us
3480
0
3481
0
  // adjust indices to account for insertion
3482
0
  ReindexRange(aIndex, INT32_MAX, 1);
3483
0
3484
0
  RefPtr<nsNavHistoryResultNode> node;
3485
0
  if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
3486
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
3487
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
3488
0
    rv = history->BookmarkIdToResultNode(aItemId, mOptions, getter_AddRefs(node));
3489
0
    NS_ENSURE_SUCCESS(rv, rv);
3490
0
  }
3491
0
  else if (aItemType == nsINavBookmarksService::TYPE_FOLDER) {
3492
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
3493
0
    NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3494
0
    rv = bookmarks->ResultNodeForContainer(PromiseFlatCString(aGUID),
3495
0
                                           new nsNavHistoryQueryOptions(),
3496
0
                                           getter_AddRefs(node));
3497
0
    NS_ENSURE_SUCCESS(rv, rv);
3498
0
  }
3499
0
  else if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR) {
3500
0
    node = new nsNavHistorySeparatorResultNode();
3501
0
    node->mItemId = aItemId;
3502
0
    node->mBookmarkGuid = aGUID;
3503
0
    node->mDateAdded = aDateAdded;
3504
0
    node->mLastModified = aDateAdded;
3505
0
  }
3506
0
3507
0
  node->mBookmarkIndex = aIndex;
3508
0
3509
0
  if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR ||
3510
0
      GetSortType() == nsINavHistoryQueryOptions::SORT_BY_NONE) {
3511
0
    // insert at natural bookmarks position
3512
0
    return InsertChildAt(node, aIndex);
3513
0
  }
3514
0
3515
0
  // insert at sorted position
3516
0
  return InsertSortedChild(node);
3517
0
}
3518
3519
nsresult
3520
nsNavHistoryQueryResultNode::OnMobilePrefChanged(bool newValue)
3521
0
{
3522
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3523
0
3524
0
  if (newValue) {
3525
0
    // If the folder is being added, simply refresh the query as that is
3526
0
    // simpler than re-inserting just the mobile folder.
3527
0
    return Refresh();
3528
0
  }
3529
0
3530
0
  // We're removing the mobile folder, so find it.
3531
0
  int32_t existingIndex;
3532
0
  FindChildByGuid(NS_LITERAL_CSTRING(MOBILE_BOOKMARKS_VIRTUAL_GUID), &existingIndex);
3533
0
3534
0
  if (existingIndex == -1) {
3535
0
    return NS_OK;
3536
0
  }
3537
0
3538
0
  return RemoveChildAt(existingIndex);
3539
0
}
3540
3541
NS_IMETHODIMP
3542
nsNavHistoryFolderResultNode::OnItemRemoved(int64_t aItemId,
3543
                                            int64_t aParentFolder,
3544
                                            int32_t aIndex,
3545
                                            uint16_t aItemType,
3546
                                            nsIURI* aURI,
3547
                                            const nsACString& aGUID,
3548
                                            const nsACString& aParentGUID,
3549
                                            uint16_t aSource)
3550
0
{
3551
0
  // Folder shortcuts should not be notified removal of the target folder.
3552
0
  MOZ_ASSERT_IF(mItemId != mTargetFolderItemId, aItemId != mTargetFolderItemId);
3553
0
  // Concrete folders should not be notified their own removal.
3554
0
  // Note aItemId may equal mItemId for recursive folder shortcuts.
3555
0
  MOZ_ASSERT_IF(mItemId == mTargetFolderItemId, aItemId != mItemId);
3556
0
3557
0
  // In any case though, here we only care about the children removal.
3558
0
  if (mTargetFolderItemId == aItemId || mItemId == aItemId)
3559
0
    return NS_OK;
3560
0
3561
0
  MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
3562
0
3563
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3564
0
3565
0
  // don't trust the index from the bookmark service, find it ourselves.  The
3566
0
  // sorting could be different, or the bookmark services indices and ours might
3567
0
  // be out of sync somehow.
3568
0
  uint32_t index;
3569
0
  nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
3570
0
    // Bug 1097528.
3571
0
    // It's possible our result registered due to a previous notification, for
3572
0
    // example the Library left pane could have refreshed and replaced the
3573
0
    // right pane as a consequence. In such a case our contents are already
3574
0
    // up-to-date.  That's OK.
3575
0
  if (!node) {
3576
0
    return NS_OK;
3577
0
  }
3578
0
3579
0
  bool excludeItems = mOptions->ExcludeItems();
3580
0
3581
0
  if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
3582
0
    // don't update items when we aren't displaying them, but we do need to
3583
0
    // adjust everybody's bookmark indices to account for the removal
3584
0
    ReindexRange(aIndex, INT32_MAX, -1);
3585
0
    return NS_OK;
3586
0
  }
3587
0
3588
0
  if (!StartIncrementalUpdate())
3589
0
    return NS_OK; // we are completely refreshed
3590
0
3591
0
  // shift all following indices down
3592
0
  ReindexRange(aIndex + 1, INT32_MAX, -1);
3593
0
3594
0
  return RemoveChildAt(index);
3595
0
}
3596
3597
3598
NS_IMETHODIMP
3599
nsNavHistoryResultNode::OnItemChanged(int64_t aItemId,
3600
                                      const nsACString& aProperty,
3601
                                      bool aIsAnnotationProperty,
3602
                                      const nsACString& aNewValue,
3603
                                      PRTime aLastModified,
3604
                                      uint16_t aItemType,
3605
                                      int64_t aParentId,
3606
                                      const nsACString& aGUID,
3607
                                      const nsACString& aParentGUID,
3608
                                      const nsACString& aOldValue,
3609
                                      uint16_t aSource)
3610
0
{
3611
0
  if (aItemId != mItemId)
3612
0
    return NS_OK;
3613
0
3614
0
  // Last modified isn't changed for favicon updates and it is notified as `0`,
3615
0
  // so don't reset it here.
3616
0
  if (!aProperty.EqualsLiteral("favicon")) {
3617
0
    mLastModified = aLastModified;
3618
0
  }
3619
0
3620
0
  nsNavHistoryResult* result = GetResult();
3621
0
  NS_ENSURE_STATE(result);
3622
0
3623
0
  bool shouldNotify = !mParent || mParent->AreChildrenVisible();
3624
0
3625
0
  if (aIsAnnotationProperty) {
3626
0
    if (shouldNotify)
3627
0
      NOTIFY_RESULT_OBSERVERS(result, NodeAnnotationChanged(this, aProperty));
3628
0
  }
3629
0
  else if (aProperty.EqualsLiteral("title")) {
3630
0
    // XXX: what should we do if the new title is void?
3631
0
    mTitle = aNewValue;
3632
0
    if (shouldNotify)
3633
0
      NOTIFY_RESULT_OBSERVERS(result, NodeTitleChanged(this, mTitle));
3634
0
  }
3635
0
  else if (aProperty.EqualsLiteral("uri")) {
3636
0
    // clear the tags string as well
3637
0
    mTags.SetIsVoid(true);
3638
0
    nsCString oldURI(mURI);
3639
0
    mURI = aNewValue;
3640
0
    if (shouldNotify)
3641
0
      NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, oldURI));
3642
0
  }
3643
0
  else if (aProperty.EqualsLiteral("favicon")) {
3644
0
    if (shouldNotify)
3645
0
      NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
3646
0
  }
3647
0
  else if (aProperty.EqualsLiteral("cleartime")) {
3648
0
    PRTime oldTime = mTime;
3649
0
    mTime = 0;
3650
0
    if (shouldNotify) {
3651
0
      NOTIFY_RESULT_OBSERVERS(result,
3652
0
                              NodeHistoryDetailsChanged(this, oldTime, mAccessCount));
3653
0
    }
3654
0
  }
3655
0
  else if (aProperty.EqualsLiteral("tags")) {
3656
0
    mTags.SetIsVoid(true);
3657
0
    if (shouldNotify)
3658
0
      NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(this));
3659
0
  }
3660
0
  else if (aProperty.EqualsLiteral("dateAdded")) {
3661
0
    // aNewValue has the date as a string, but we can use aLastModified,
3662
0
    // because it's set to the same value when dateAdded is changed.
3663
0
    mDateAdded = aLastModified;
3664
0
    if (shouldNotify)
3665
0
      NOTIFY_RESULT_OBSERVERS(result, NodeDateAddedChanged(this, mDateAdded));
3666
0
  }
3667
0
  else if (aProperty.EqualsLiteral("lastModified")) {
3668
0
    if (shouldNotify) {
3669
0
      NOTIFY_RESULT_OBSERVERS(result,
3670
0
                              NodeLastModifiedChanged(this, aLastModified));
3671
0
    }
3672
0
  }
3673
0
  else if (aProperty.EqualsLiteral("keyword")) {
3674
0
    if (shouldNotify)
3675
0
      NOTIFY_RESULT_OBSERVERS(result, NodeKeywordChanged(this, aNewValue));
3676
0
  }
3677
0
  else
3678
0
    MOZ_ASSERT_UNREACHABLE("Unknown bookmark property changing.");
3679
0
3680
0
  if (!mParent)
3681
0
    return NS_OK;
3682
0
3683
0
  // DO NOT OPTIMIZE THIS TO CHECK aProperty
3684
0
  // The sorting methods fall back to each other so we need to re-sort the
3685
0
  // result even if it's not set to sort by the given property.
3686
0
  int32_t ourIndex = mParent->FindChild(this);
3687
0
  NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
3688
0
  if (ourIndex >= 0)
3689
0
    mParent->EnsureItemPosition(ourIndex);
3690
0
3691
0
  return NS_OK;
3692
0
}
3693
3694
3695
NS_IMETHODIMP
3696
nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
3697
                                            const nsACString& aProperty,
3698
                                            bool aIsAnnotationProperty,
3699
                                            const nsACString& aNewValue,
3700
                                            PRTime aLastModified,
3701
                                            uint16_t aItemType,
3702
                                            int64_t aParentId,
3703
                                            const nsACString& aGUID,
3704
                                            const nsACString& aParentGUID,
3705
                                            const nsACString& aOldValue,
3706
                                            uint16_t aSource)
3707
0
{
3708
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3709
0
3710
0
  return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
3711
0
                                               aIsAnnotationProperty,
3712
0
                                               aNewValue, aLastModified,
3713
0
                                               aItemType, aParentId, aGUID,
3714
0
                                               aParentGUID, aOldValue, aSource);
3715
0
}
3716
3717
/**
3718
 * Updates visit count and last visit time and refreshes.
3719
 */
3720
NS_IMETHODIMP
3721
nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
3722
                                            int64_t aVisitId,
3723
                                            PRTime aTime,
3724
                                            uint32_t aTransitionType,
3725
                                            nsIURI* aURI,
3726
                                            int64_t aParentId,
3727
                                            const nsACString& aGUID,
3728
                                            const nsACString& aParentGUID)
3729
0
{
3730
0
  if (mOptions->ExcludeItems())
3731
0
    return NS_OK; // don't update items when we aren't displaying them
3732
0
3733
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3734
0
3735
0
  if (!StartIncrementalUpdate())
3736
0
    return NS_OK;
3737
0
3738
0
  uint32_t nodeIndex;
3739
0
  nsNavHistoryResultNode* node = FindChildById(aItemId, &nodeIndex);
3740
0
  if (!node)
3741
0
    return NS_ERROR_FAILURE;
3742
0
3743
0
  // Update node.
3744
0
  uint32_t nodeOldAccessCount = node->mAccessCount;
3745
0
  PRTime nodeOldTime = node->mTime;
3746
0
  node->mTime = aTime;
3747
0
  ++node->mAccessCount;
3748
0
3749
0
  // Update us.
3750
0
  int32_t oldAccessCount = mAccessCount;
3751
0
  ++mAccessCount;
3752
0
  if (aTime > mTime)
3753
0
    mTime = aTime;
3754
0
  nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
3755
0
  NS_ENSURE_SUCCESS(rv, rv);
3756
0
3757
0
  // Update frecency for proper frecency ordering.
3758
0
  // TODO (bug 832617): we may avoid one query here, by providing the new
3759
0
  // frecency value in the notification.
3760
0
  nsNavHistory* history = nsNavHistory::GetHistoryService();
3761
0
  NS_ENSURE_TRUE(history, NS_OK);
3762
0
  RefPtr<nsNavHistoryResultNode> visitNode;
3763
0
  rv = history->VisitIdToResultNode(aVisitId, mOptions,
3764
0
                                    getter_AddRefs(visitNode));
3765
0
  NS_ENSURE_SUCCESS(rv, rv);
3766
0
  if (!visitNode) {
3767
0
    // Certain result types manage the nodes by themselves.
3768
0
    return NS_OK;
3769
0
  }
3770
0
  node->mFrecency = visitNode->mFrecency;
3771
0
3772
0
  if (AreChildrenVisible()) {
3773
0
    // Sorting has not changed, just redraw the row if it's visible.
3774
0
    nsNavHistoryResult* result = GetResult();
3775
0
    NOTIFY_RESULT_OBSERVERS(result,
3776
0
                            NodeHistoryDetailsChanged(node, nodeOldTime, nodeOldAccessCount));
3777
0
  }
3778
0
3779
0
  // Update sorting if necessary.
3780
0
  uint32_t sortType = GetSortType();
3781
0
  if (sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
3782
0
      sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
3783
0
      sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
3784
0
      sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
3785
0
      sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
3786
0
      sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING) {
3787
0
    int32_t childIndex = FindChild(node);
3788
0
    NS_ASSERTION(childIndex >= 0, "Could not find child we just got a reference to");
3789
0
    if (childIndex >= 0) {
3790
0
      EnsureItemPosition(childIndex);
3791
0
    }
3792
0
  }
3793
0
3794
0
  return NS_OK;
3795
0
}
3796
3797
3798
NS_IMETHODIMP
3799
nsNavHistoryFolderResultNode::OnItemMoved(int64_t aItemId,
3800
                                          int64_t aOldParent,
3801
                                          int32_t aOldIndex,
3802
                                          int64_t aNewParent,
3803
                                          int32_t aNewIndex,
3804
                                          uint16_t aItemType,
3805
                                          const nsACString& aGUID,
3806
                                          const nsACString& aOldParentGUID,
3807
                                          const nsACString& aNewParentGUID,
3808
                                          uint16_t aSource,
3809
                                          const nsACString& aURI)
3810
0
{
3811
0
  NS_ASSERTION(aOldParent == mTargetFolderItemId || aNewParent == mTargetFolderItemId,
3812
0
               "Got a bookmark message that doesn't belong to us");
3813
0
3814
0
  RESTART_AND_RETURN_IF_ASYNC_PENDING();
3815
0
3816
0
  bool excludeItems = mOptions->ExcludeItems();
3817
0
  if (excludeItems &&
3818
0
      (aItemType == nsINavBookmarksService::TYPE_SEPARATOR ||
3819
0
       (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
3820
0
        !StringBeginsWith(aURI, NS_LITERAL_CSTRING("place:"))))) {
3821
0
    // This is a bookmark or a separator, so we don't need to handle this if
3822
0
    // we're excluding items.
3823
0
    return NS_OK;
3824
0
  }
3825
0
3826
0
  uint32_t index;
3827
0
  nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
3828
0
  // Bug 1097528.
3829
0
  // It's possible our result registered due to a previous notification, for
3830
0
  // example the Library left pane could have refreshed and replaced the
3831
0
  // right pane as a consequence. In such a case our contents are already
3832
0
  // up-to-date.  That's OK.
3833
0
  if (node && aNewParent == mTargetFolderItemId && index == static_cast<uint32_t>(aNewIndex))
3834
0
    return NS_OK;
3835
0
  if (!node && aOldParent == mTargetFolderItemId)
3836
0
    return NS_OK;
3837
0
3838
0
  if (!StartIncrementalUpdate())
3839
0
    return NS_OK; // entire container was refreshed for us
3840
0
3841
0
  if (aOldParent == aNewParent) {
3842
0
    // getting moved within the same folder, we don't want to do a remove and
3843
0
    // an add because that will lose your tree state.
3844
0
3845
0
    // adjust bookmark indices
3846
0
    ReindexRange(aOldIndex + 1, INT32_MAX, -1);
3847
0
    ReindexRange(aNewIndex, INT32_MAX, 1);
3848
0
3849
0
    MOZ_ASSERT(node, "Can't find folder that is moving!");
3850
0
    if (!node) {
3851
0
      return NS_ERROR_FAILURE;
3852
0
    }
3853
0
    MOZ_ASSERT(index < uint32_t(mChildren.Count()), "Invalid index!");
3854
0
    node->mBookmarkIndex = aNewIndex;
3855
0
3856
0
    // adjust position
3857
0
    EnsureItemPosition(index);
3858
0
    return NS_OK;
3859
0
  } else {
3860
0
    // moving between two different folders, just do a remove and an add
3861
0
    nsCOMPtr<nsIURI> itemURI;
3862
0
    nsAutoCString itemTitle;
3863
0
    if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
3864
0
      nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
3865
0
      NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3866
0
      nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
3867
0
      NS_ENSURE_SUCCESS(rv, rv);
3868
0
      rv = bookmarks->GetItemTitle(aItemId, itemTitle);
3869
0
      NS_ENSURE_SUCCESS(rv, rv);
3870
0
    }
3871
0
    if (aOldParent == mTargetFolderItemId) {
3872
0
      OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
3873
0
                    aGUID, aOldParentGUID, aSource);
3874
0
    }
3875
0
    if (aNewParent == mTargetFolderItemId) {
3876
0
      OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
3877
0
                  RoundedPRNow(), // This is a dummy dateAdded, not the real value.
3878
0
                  aGUID, aNewParentGUID, aSource);
3879
0
    }
3880
0
  }
3881
0
  return NS_OK;
3882
0
}
3883
3884
3885
/**
3886
 * Separator nodes do not hold any data.
3887
 */
3888
nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode()
3889
  : nsNavHistoryResultNode(EmptyCString(), EmptyCString(),
3890
                           0, 0)
3891
0
{
3892
0
}
3893
3894
3895
NS_IMPL_CYCLE_COLLECTION_CLASS(nsNavHistoryResult)
3896
3897
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNavHistoryResult)
3898
0
  tmp->StopObserving();
3899
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
3900
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
3901
0
  for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
3902
0
    delete it.Data();
3903
0
    it.Remove();
3904
0
  }
3905
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMobilePrefObservers)
3906
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAllBookmarksObservers)
3907
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistoryObservers)
3908
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3909
3910
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNavHistoryResult)
3911
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
3912
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
3913
0
  for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
3914
0
    nsNavHistoryResult::FolderObserverList*& list = it.Data();
3915
0
    for (uint32_t i = 0; i < list->Length(); ++i) {
3916
0
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
3917
0
                                         "mBookmarkFolderObservers value[i]");
3918
0
      nsNavHistoryResultNode* node = list->ElementAt(i);
3919
0
      cb.NoteXPCOMChild(node);
3920
0
    }
3921
0
  }
3922
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobilePrefObservers)
3923
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAllBookmarksObservers)
3924
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistoryObservers)
3925
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3926
3927
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResult)
3928
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResult)
3929
3930
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResult)
3931
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResult)
3932
0
  NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryResult)
3933
0
  NS_INTERFACE_MAP_ENTRY(nsINavHistoryResult)
3934
0
  NS_INTERFACE_MAP_ENTRY(nsINavBookmarkObserver)
3935
0
  NS_INTERFACE_MAP_ENTRY(nsINavHistoryObserver)
3936
0
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
3937
0
NS_INTERFACE_MAP_END
3938
3939
nsNavHistoryResult::nsNavHistoryResult(nsNavHistoryContainerResultNode* aRoot,
3940
                                       const RefPtr<nsNavHistoryQuery>& aQuery,
3941
                                       const RefPtr<nsNavHistoryQueryOptions>& aOptions
3942
) : mRootNode(aRoot)
3943
  , mQuery(aQuery)
3944
  , mOptions(aOptions)
3945
  , mNeedsToApplySortingMode(false)
3946
  , mIsHistoryObserver(false)
3947
  , mIsBookmarkFolderObserver(false)
3948
  , mIsAllBookmarksObserver(false)
3949
  , mIsMobilePrefObserver(false)
3950
  , mBookmarkFolderObservers(64)
3951
  , mBatchInProgress(false)
3952
  , mSuppressNotifications(false)
3953
0
{
3954
0
  mSortingMode = aOptions->SortingMode();
3955
0
3956
0
  mRootNode->mResult = this;
3957
0
  MOZ_ASSERT(mRootNode->mIndentLevel == -1,
3958
0
             "Root node's indent level initialized wrong");
3959
0
  mRootNode->FillStats();
3960
0
}
3961
3962
nsNavHistoryResult::~nsNavHistoryResult()
3963
0
{
3964
0
  // Delete all heap-allocated bookmark folder observer arrays.
3965
0
  for (auto it = mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
3966
0
    delete it.Data();
3967
0
    it.Remove();
3968
0
  }
3969
0
}
3970
3971
void
3972
nsNavHistoryResult::StopObserving()
3973
0
{
3974
0
  if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
3975
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
3976
0
    if (bookmarks) {
3977
0
      bookmarks->RemoveObserver(this);
3978
0
      mIsBookmarkFolderObserver = false;
3979
0
      mIsAllBookmarksObserver = false;
3980
0
    }
3981
0
  }
3982
0
  if (mIsMobilePrefObserver) {
3983
0
    Preferences::UnregisterCallback(OnMobilePrefChangedCallback,
3984
0
                                    MOBILE_BOOKMARKS_PREF,
3985
0
                                    this);
3986
0
    mIsMobilePrefObserver = false;
3987
0
  }
3988
0
  if (mIsHistoryObserver) {
3989
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
3990
0
    if (history) {
3991
0
      history->RemoveObserver(this);
3992
0
      AutoTArray<PlacesEventType, 1> events;
3993
0
      events.AppendElement(PlacesEventType::Page_visited);
3994
0
      PlacesObservers::RemoveListener(events, this);
3995
0
      mIsHistoryObserver = false;
3996
0
    }
3997
0
  }
3998
0
}
3999
4000
void
4001
nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
4002
0
{
4003
0
  if (!mIsHistoryObserver) {
4004
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
4005
0
      NS_ASSERTION(history, "Can't create history service");
4006
0
      history->AddObserver(this, true);
4007
0
      AutoTArray<PlacesEventType, 1> events;
4008
0
      events.AppendElement(PlacesEventType::Page_visited);
4009
0
      PlacesObservers::AddListener(events, this);
4010
0
      mIsHistoryObserver = true;
4011
0
  }
4012
0
  // Don't add duplicate observers.  In some case we don't unregister when
4013
0
  // children are cleared (see ClearChildren) and the next FillChildren call
4014
0
  // will try to add the observer again.
4015
0
  if (mHistoryObservers.IndexOf(aNode) == QueryObserverList::NoIndex) {
4016
0
    mHistoryObservers.AppendElement(aNode);
4017
0
  }
4018
0
}
4019
4020
4021
void
4022
nsNavHistoryResult::AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
4023
0
{
4024
0
  if (!mIsAllBookmarksObserver && !mIsBookmarkFolderObserver) {
4025
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
4026
0
    if (!bookmarks) {
4027
0
      MOZ_ASSERT_UNREACHABLE("Can't create bookmark service");
4028
0
      return;
4029
0
    }
4030
0
    bookmarks->AddObserver(this, true);
4031
0
    mIsAllBookmarksObserver = true;
4032
0
  }
4033
0
  // Don't add duplicate observers.  In some case we don't unregister when
4034
0
  // children are cleared (see ClearChildren) and the next FillChildren call
4035
0
  // will try to add the observer again.
4036
0
  if (mAllBookmarksObservers.IndexOf(aNode) == QueryObserverList::NoIndex) {
4037
0
    mAllBookmarksObservers.AppendElement(aNode);
4038
0
  }
4039
0
}
4040
4041
4042
void
4043
nsNavHistoryResult::AddMobilePrefsObserver(nsNavHistoryQueryResultNode* aNode)
4044
0
{
4045
0
  if (!mIsMobilePrefObserver) {
4046
0
    Preferences::RegisterCallback(OnMobilePrefChangedCallback,
4047
0
                                  MOBILE_BOOKMARKS_PREF,
4048
0
                                  this);
4049
0
    mIsMobilePrefObserver = true;
4050
0
  }
4051
0
  // Don't add duplicate observers.  In some case we don't unregister when
4052
0
  // children are cleared (see ClearChildren) and the next FillChildren call
4053
0
  // will try to add the observer again.
4054
0
  if (mMobilePrefObservers.IndexOf(aNode) == mMobilePrefObservers.NoIndex) {
4055
0
    mMobilePrefObservers.AppendElement(aNode);
4056
0
  }
4057
0
}
4058
4059
4060
void
4061
nsNavHistoryResult::AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
4062
                                              int64_t aFolder)
4063
0
{
4064
0
  if (!mIsBookmarkFolderObserver && !mIsAllBookmarksObserver) {
4065
0
    nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
4066
0
    if (!bookmarks) {
4067
0
      MOZ_ASSERT_UNREACHABLE("Can't create bookmark service");
4068
0
      return;
4069
0
    }
4070
0
    bookmarks->AddObserver(this, true);
4071
0
    mIsBookmarkFolderObserver = true;
4072
0
  }
4073
0
  // Don't add duplicate observers.  In some case we don't unregister when
4074
0
  // children are cleared (see ClearChildren) and the next FillChildren call
4075
0
  // will try to add the observer again.
4076
0
  FolderObserverList* list = BookmarkFolderObserversForId(aFolder, true);
4077
0
  if (list->IndexOf(aNode) == FolderObserverList::NoIndex) {
4078
0
    list->AppendElement(aNode);
4079
0
  }
4080
0
}
4081
4082
4083
void
4084
nsNavHistoryResult::RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode)
4085
0
{
4086
0
  mHistoryObservers.RemoveElement(aNode);
4087
0
}
4088
4089
4090
void
4091
nsNavHistoryResult::RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
4092
0
{
4093
0
  mAllBookmarksObservers.RemoveElement(aNode);
4094
0
}
4095
4096
4097
void
4098
nsNavHistoryResult::RemoveMobilePrefsObserver(nsNavHistoryQueryResultNode* aNode)
4099
0
{
4100
0
  mMobilePrefObservers.RemoveElement(aNode);
4101
0
}
4102
4103
4104
void
4105
nsNavHistoryResult::RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
4106
                                                 int64_t aFolder)
4107
0
{
4108
0
  FolderObserverList* list = BookmarkFolderObserversForId(aFolder, false);
4109
0
  if (!list)
4110
0
    return; // we don't even have an entry for that folder
4111
0
  list->RemoveElement(aNode);
4112
0
}
4113
4114
4115
nsNavHistoryResult::FolderObserverList*
4116
nsNavHistoryResult::BookmarkFolderObserversForId(int64_t aFolderId, bool aCreate)
4117
0
{
4118
0
  FolderObserverList* list;
4119
0
  if (mBookmarkFolderObservers.Get(aFolderId, &list))
4120
0
    return list;
4121
0
  if (!aCreate)
4122
0
    return nullptr;
4123
0
4124
0
  // need to create a new list
4125
0
  list = new FolderObserverList;
4126
0
  mBookmarkFolderObservers.Put(aFolderId, list);
4127
0
  return list;
4128
0
}
4129
4130
4131
NS_IMETHODIMP
4132
nsNavHistoryResult::GetSortingMode(uint16_t* aSortingMode)
4133
0
{
4134
0
  *aSortingMode = mSortingMode;
4135
0
  return NS_OK;
4136
0
}
4137
4138
4139
NS_IMETHODIMP
4140
nsNavHistoryResult::SetSortingMode(uint16_t aSortingMode)
4141
0
{
4142
0
  NS_ENSURE_STATE(mRootNode);
4143
0
4144
0
  if (aSortingMode > nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING)
4145
0
    return NS_ERROR_INVALID_ARG;
4146
0
4147
0
  // Keep everything in sync.
4148
0
  NS_ASSERTION(mOptions, "Options should always be present for a root query");
4149
0
4150
0
  mSortingMode = aSortingMode;
4151
0
4152
0
  if (!mRootNode->mExpanded) {
4153
0
    // Need to do this later when node will be expanded.
4154
0
    mNeedsToApplySortingMode = true;
4155
0
    return NS_OK;
4156
0
  }
4157
0
4158
0
  // Actually do sorting.
4159
0
  nsNavHistoryContainerResultNode::SortComparator comparator =
4160
0
      nsNavHistoryContainerResultNode::GetSortingComparator(aSortingMode);
4161
0
  if (comparator) {
4162
0
    nsNavHistory* history = nsNavHistory::GetHistoryService();
4163
0
    NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
4164
0
    mRootNode->RecursiveSort(comparator);
4165
0
  }
4166
0
4167
0
  NOTIFY_RESULT_OBSERVERS(this, SortingChanged(aSortingMode));
4168
0
  NOTIFY_RESULT_OBSERVERS(this, InvalidateContainer(mRootNode));
4169
0
  return NS_OK;
4170
0
}
4171
4172
4173
NS_IMETHODIMP
4174
nsNavHistoryResult::AddObserver(nsINavHistoryResultObserver* aObserver,
4175
                                bool aOwnsWeak)
4176
0
{
4177
0
  NS_ENSURE_ARG(aObserver);
4178
0
  nsresult rv = mObservers.AppendWeakElement(aObserver, aOwnsWeak);
4179
0
  NS_ENSURE_SUCCESS(rv, rv);
4180
0
4181
0
  rv = aObserver->SetResult(this);
4182
0
  NS_ENSURE_SUCCESS(rv, rv);
4183
0
4184
0
  // If we are batching, notify a fake batch start to the observers.
4185
0
  // Not doing so would then notify a not coupled batch end.
4186
0
  if (mBatchInProgress) {
4187
0
    NOTIFY_RESULT_OBSERVERS(this, Batching(true));
4188
0
  }
4189
0
4190
0
  return NS_OK;
4191
0
}
4192
4193
4194
NS_IMETHODIMP
4195
nsNavHistoryResult::RemoveObserver(nsINavHistoryResultObserver* aObserver)
4196
0
{
4197
0
  NS_ENSURE_ARG(aObserver);
4198
0
  return mObservers.RemoveWeakElement(aObserver);
4199
0
}
4200
4201
4202
NS_IMETHODIMP
4203
nsNavHistoryResult::GetSuppressNotifications(bool* _retval)
4204
0
{
4205
0
  *_retval = mSuppressNotifications;
4206
0
  return NS_OK;
4207
0
}
4208
4209
4210
NS_IMETHODIMP
4211
nsNavHistoryResult::SetSuppressNotifications(bool aSuppressNotifications)
4212
0
{
4213
0
  mSuppressNotifications = aSuppressNotifications;
4214
0
  return NS_OK;
4215
0
}
4216
4217
4218
NS_IMETHODIMP
4219
nsNavHistoryResult::GetRoot(nsINavHistoryContainerResultNode** aRoot)
4220
0
{
4221
0
  if (!mRootNode) {
4222
0
    MOZ_ASSERT_UNREACHABLE("Root is null");
4223
0
    *aRoot = nullptr;
4224
0
    return NS_ERROR_FAILURE;
4225
0
  }
4226
0
  RefPtr<nsNavHistoryContainerResultNode> node(mRootNode);
4227
0
  node.forget(aRoot);
4228
0
  return NS_OK;
4229
0
}
4230
4231
4232
void
4233
nsNavHistoryResult::requestRefresh(nsNavHistoryContainerResultNode* aContainer)
4234
0
{
4235
0
  // Don't add twice the same container.
4236
0
  if (mRefreshParticipants.IndexOf(aContainer) == ContainerObserverList::NoIndex)
4237
0
    mRefreshParticipants.AppendElement(aContainer);
4238
0
}
4239
4240
// nsINavBookmarkObserver implementation
4241
4242
// Here, it is important that we create a COPY of the observer array. Some
4243
// observers will requery themselves, which may cause the observer array to
4244
// be modified or added to.
4245
#define ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(_folderId, _functionCall) \
4246
0
  PR_BEGIN_MACRO \
4247
0
    FolderObserverList* _fol = BookmarkFolderObserversForId(_folderId, false); \
4248
0
    if (_fol) { \
4249
0
      FolderObserverList _listCopy(*_fol); \
4250
0
      for (uint32_t _fol_i = 0; _fol_i < _listCopy.Length(); ++_fol_i) { \
4251
0
        if (_listCopy[_fol_i]) \
4252
0
          _listCopy[_fol_i]->_functionCall; \
4253
0
      } \
4254
0
    } \
4255
0
  PR_END_MACRO
4256
#define ENUMERATE_LIST_OBSERVERS(_listType, _functionCall, _observersList, _conditionCall) \
4257
0
  PR_BEGIN_MACRO \
4258
0
    _listType _listCopy(_observersList); \
4259
0
    for (uint32_t _obs_i = 0; _obs_i < _listCopy.Length(); ++_obs_i) { \
4260
0
      if (_listCopy[_obs_i] && _listCopy[_obs_i]->_conditionCall) \
4261
0
        _listCopy[_obs_i]->_functionCall; \
4262
0
    } \
4263
0
  PR_END_MACRO
4264
#define ENUMERATE_QUERY_OBSERVERS(_functionCall, _observersList, _conditionCall) \
4265
0
  ENUMERATE_LIST_OBSERVERS(QueryObserverList, _functionCall, _observersList, _conditionCall)
4266
#define ENUMERATE_ALL_BOOKMARKS_OBSERVERS(_functionCall) \
4267
0
  ENUMERATE_QUERY_OBSERVERS(_functionCall, mAllBookmarksObservers, IsQuery())
4268
#define ENUMERATE_HISTORY_OBSERVERS(_functionCall) \
4269
0
  ENUMERATE_QUERY_OBSERVERS(_functionCall, mHistoryObservers, IsQuery())
4270
#define ENUMERATE_MOBILE_PREF_OBSERVERS(_functionCall) \
4271
0
  ENUMERATE_QUERY_OBSERVERS(_functionCall, mMobilePrefObservers, IsQuery())
4272
4273
#define NOTIFY_REFRESH_PARTICIPANTS() \
4274
0
  PR_BEGIN_MACRO \
4275
0
  ENUMERATE_LIST_OBSERVERS(ContainerObserverList, Refresh(), mRefreshParticipants, IsContainer()); \
4276
0
  mRefreshParticipants.Clear(); \
4277
0
  PR_END_MACRO
4278
4279
NS_IMETHODIMP
4280
nsNavHistoryResult::GetSkipTags(bool *aSkipTags)
4281
0
{
4282
0
  *aSkipTags = false;
4283
0
  return NS_OK;
4284
0
}
4285
4286
NS_IMETHODIMP
4287
nsNavHistoryResult::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
4288
0
{
4289
0
  *aSkipDescendantsOnItemRemoval = true;
4290
0
  return NS_OK;
4291
0
}
4292
4293
NS_IMETHODIMP
4294
nsNavHistoryResult::OnBeginUpdateBatch()
4295
0
{
4296
0
  // Since we could be observing both history and bookmarks, it's possible both
4297
0
  // notify the batch.  We can safely ignore nested calls.
4298
0
  if (!mBatchInProgress) {
4299
0
    mBatchInProgress = true;
4300
0
    ENUMERATE_HISTORY_OBSERVERS(OnBeginUpdateBatch());
4301
0
    ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnBeginUpdateBatch());
4302
0
4303
0
    NOTIFY_RESULT_OBSERVERS(this, Batching(true));
4304
0
  }
4305
0
4306
0
  return NS_OK;
4307
0
}
4308
4309
4310
NS_IMETHODIMP
4311
nsNavHistoryResult::OnEndUpdateBatch()
4312
0
{
4313
0
  // Since we could be observing both history and bookmarks, it's possible both
4314
0
  // notify the batch.  We can safely ignore nested calls.
4315
0
  // Notice it's possible we are notified OnEndUpdateBatch more times than
4316
0
  // onBeginUpdateBatch, since the result could be created in the middle of
4317
0
  // nested batches.
4318
0
  if (mBatchInProgress) {
4319
0
    ENUMERATE_HISTORY_OBSERVERS(OnEndUpdateBatch());
4320
0
    ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnEndUpdateBatch());
4321
0
4322
0
    // Setting mBatchInProgress before notifying the end of the batch to
4323
0
    // observers would make evantual calls to Refresh() directly handled rather
4324
0
    // than enqueued.  Thus set it just before handling refreshes.
4325
0
    mBatchInProgress = false;
4326
0
    NOTIFY_REFRESH_PARTICIPANTS();
4327
0
    NOTIFY_RESULT_OBSERVERS(this, Batching(false));
4328
0
  }
4329
0
4330
0
  return NS_OK;
4331
0
}
4332
4333
4334
NS_IMETHODIMP
4335
nsNavHistoryResult::OnItemAdded(int64_t aItemId,
4336
                                int64_t aParentId,
4337
                                int32_t aIndex,
4338
                                uint16_t aItemType,
4339
                                nsIURI* aURI,
4340
                                const nsACString& aTitle,
4341
                                PRTime aDateAdded,
4342
                                const nsACString& aGUID,
4343
                                const nsACString& aParentGUID,
4344
                                uint16_t aSource)
4345
0
{
4346
0
  NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
4347
0
                aURI);
4348
0
4349
0
  ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
4350
0
    OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
4351
0
                aGUID, aParentGUID, aSource)
4352
0
  );
4353
0
  ENUMERATE_HISTORY_OBSERVERS(
4354
0
    OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
4355
0
                aGUID, aParentGUID, aSource)
4356
0
  );
4357
0
  ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
4358
0
    OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
4359
0
                aGUID, aParentGUID, aSource)
4360
0
  );
4361
0
  return NS_OK;
4362
0
}
4363
4364
4365
NS_IMETHODIMP
4366
nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
4367
                                  int64_t aParentId,
4368
                                  int32_t aIndex,
4369
                                  uint16_t aItemType,
4370
                                  nsIURI* aURI,
4371
                                  const nsACString& aGUID,
4372
                                  const nsACString& aParentGUID,
4373
                                  uint16_t aSource)
4374
0
{
4375
0
  NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
4376
0
                aURI);
4377
0
4378
0
  ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
4379
0
      OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
4380
0
                    aParentGUID, aSource));
4381
0
  ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
4382
0
      OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
4383
0
                    aParentGUID, aSource));
4384
0
  ENUMERATE_HISTORY_OBSERVERS(
4385
0
      OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
4386
0
                    aParentGUID, aSource));
4387
0
  return NS_OK;
4388
0
}
4389
4390
4391
NS_IMETHODIMP
4392
nsNavHistoryResult::OnItemChanged(int64_t aItemId,
4393
                                  const nsACString &aProperty,
4394
                                  bool aIsAnnotationProperty,
4395
                                  const nsACString &aNewValue,
4396
                                  PRTime aLastModified,
4397
                                  uint16_t aItemType,
4398
                                  int64_t aParentId,
4399
                                  const nsACString& aGUID,
4400
                                  const nsACString& aParentGUID,
4401
                                  const nsACString& aOldValue,
4402
                                  uint16_t aSource)
4403
0
{
4404
0
  ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
4405
0
    OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
4406
0
                  aLastModified, aItemType, aParentId, aGUID, aParentGUID,
4407
0
                  aOldValue, aSource));
4408
0
4409
0
  // Note: folder-nodes set their own bookmark observer only once they're
4410
0
  // opened, meaning we cannot optimize this code path for changes done to
4411
0
  // folder-nodes.
4412
0
4413
0
  FolderObserverList* list = BookmarkFolderObserversForId(aParentId, false);
4414
0
  if (!list)
4415
0
    return NS_OK;
4416
0
4417
0
  for (uint32_t i = 0; i < list->Length(); ++i) {
4418
0
    RefPtr<nsNavHistoryFolderResultNode> folder = list->ElementAt(i);
4419
0
    if (folder) {
4420
0
      uint32_t nodeIndex;
4421
0
      RefPtr<nsNavHistoryResultNode> node =
4422
0
        folder->FindChildById(aItemId, &nodeIndex);
4423
0
      // if ExcludeItems is true we don't update non visible items
4424
0
      bool excludeItems = folder->mOptions->ExcludeItems();
4425
0
      if (node &&
4426
0
          (!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
4427
0
          folder->StartIncrementalUpdate()) {
4428
0
        node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
4429
0
                            aNewValue, aLastModified, aItemType, aParentId,
4430
0
                            aGUID, aParentGUID, aOldValue, aSource);
4431
0
      }
4432
0
    }
4433
0
  }
4434
0
4435
0
  // Note: we do NOT call history observers in this case.  This notification is
4436
0
  // the same as other history notification, except that here we know the item
4437
0
  // is a bookmark.  History observers will handle the history notification
4438
0
  // instead.
4439
0
  return NS_OK;
4440
0
}
4441
4442
4443
NS_IMETHODIMP
4444
nsNavHistoryResult::OnItemVisited(int64_t aItemId,
4445
                                  int64_t aVisitId,
4446
                                  PRTime aVisitTime,
4447
                                  uint32_t aTransitionType,
4448
                                  nsIURI* aURI,
4449
                                  int64_t aParentId,
4450
                                  const nsACString& aGUID,
4451
                                  const nsACString& aParentGUID)
4452
0
{
4453
0
  NS_ENSURE_ARG(aURI);
4454
0
4455
0
  ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
4456
0
      OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
4457
0
                    aParentId, aGUID, aParentGUID));
4458
0
  ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
4459
0
      OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
4460
0
                    aParentId, aGUID, aParentGUID));
4461
0
  // Note: we do NOT call history observers in this case.  This notification is
4462
0
  // the same as OnVisit, except that here we know the item is a bookmark.
4463
0
  // History observers will handle the history notification instead.
4464
0
  return NS_OK;
4465
0
}
4466
4467
4468
/**
4469
 * Need to notify both the source and the destination folders (if they are
4470
 * different).
4471
 */
4472
NS_IMETHODIMP
4473
nsNavHistoryResult::OnItemMoved(int64_t aItemId,
4474
                                int64_t aOldParent,
4475
                                int32_t aOldIndex,
4476
                                int64_t aNewParent,
4477
                                int32_t aNewIndex,
4478
                                uint16_t aItemType,
4479
                                const nsACString& aGUID,
4480
                                const nsACString& aOldParentGUID,
4481
                                const nsACString& aNewParentGUID,
4482
                                uint16_t aSource,
4483
                                const nsACString& aURI)
4484
0
{
4485
0
  ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aOldParent,
4486
0
      OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
4487
0
                  aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource, aURI));
4488
0
  if (aNewParent != aOldParent) {
4489
0
    ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aNewParent,
4490
0
        OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
4491
0
                    aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource, aURI));
4492
0
  }
4493
0
  ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
4494
0
                                                aNewParent, aNewIndex,
4495
0
                                                aItemType, aGUID,
4496
0
                                                aOldParentGUID,
4497
0
                                                aNewParentGUID, aSource, aURI));
4498
0
  ENUMERATE_HISTORY_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
4499
0
                                          aNewParent, aNewIndex, aItemType,
4500
0
                                          aGUID, aOldParentGUID,
4501
0
                                          aNewParentGUID, aSource, aURI));
4502
0
  return NS_OK;
4503
0
}
4504
4505
4506
nsresult
4507
nsNavHistoryResult::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
4508
                            uint32_t aTransitionType, const nsACString& aGUID, bool aHidden,
4509
                            uint32_t aVisitCount, const nsAString& aLastKnownTitle)
4510
0
{
4511
0
  NS_ENSURE_ARG(aURI);
4512
0
4513
0
  // Embed visits are never shown in our views.
4514
0
  if (aTransitionType == nsINavHistoryService::TRANSITION_EMBED) {
4515
0
    return NS_OK;
4516
0
  }
4517
0
4518
0
  uint32_t added = 0;
4519
0
4520
0
  ENUMERATE_HISTORY_OBSERVERS(OnVisit(aURI, aVisitId, aTime, aTransitionType,
4521
0
                                      aHidden, &added));
4522
0
4523
0
  // When we add visits, we don't bother telling the world that the title
4524
0
  // 'changed' from nothing to the first title we ever see for a history entry.
4525
0
  // Our consumers here might still care, though, so we have to tell them, but
4526
0
  // only for the first visit we add. Subsequent changes will get an usual
4527
0
  // ontitlechanged notification.
4528
0
  if (!aLastKnownTitle.IsVoid() && aVisitCount == 1) {
4529
0
    ENUMERATE_HISTORY_OBSERVERS(OnTitleChanged(aURI, aLastKnownTitle, aGUID));
4530
0
  }
4531
0
4532
0
  if (!mRootNode->mExpanded)
4533
0
    return NS_OK;
4534
0
4535
0
  // If this visit is accepted by an overlapped container, and not all
4536
0
  // overlapped containers are visible, we should still call Refresh if the
4537
0
  // visit falls into any of them.
4538
0
  bool todayIsMissing = false;
4539
0
  uint32_t resultType = mRootNode->mOptions->ResultType();
4540
0
  if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
4541
0
      resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
4542
0
    uint32_t childCount;
4543
0
    nsresult rv = mRootNode->GetChildCount(&childCount);
4544
0
    NS_ENSURE_SUCCESS(rv, rv);
4545
0
    if (childCount) {
4546
0
      nsCOMPtr<nsINavHistoryResultNode> firstChild;
4547
0
      rv = mRootNode->GetChild(0, getter_AddRefs(firstChild));
4548
0
      NS_ENSURE_SUCCESS(rv, rv);
4549
0
      nsAutoCString title;
4550
0
      rv = firstChild->GetTitle(title);
4551
0
      NS_ENSURE_SUCCESS(rv, rv);
4552
0
      nsNavHistory* history = nsNavHistory::GetHistoryService();
4553
0
      NS_ENSURE_TRUE(history, NS_OK);
4554
0
      nsAutoCString todayLabel;
4555
0
      history->GetStringFromName(
4556
0
        "finduri-AgeInDays-is-0", todayLabel);
4557
0
      todayIsMissing = !todayLabel.Equals(title);
4558
0
    }
4559
0
  }
4560
0
4561
0
  if (!added || todayIsMissing) {
4562
0
    // None of registered query observers has accepted our URI.  This means,
4563
0
    // that a matching query either was not expanded or it does not exist.
4564
0
    uint32_t resultType = mRootNode->mOptions->ResultType();
4565
0
    if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
4566
0
        resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
4567
0
      // If the visit falls into the Today bucket and the bucket exists, it was
4568
0
      // just not expanded, thus there's no reason to update.
4569
0
      int64_t beginOfToday =
4570
0
        nsNavHistory::NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
4571
0
      if (todayIsMissing || aTime < beginOfToday) {
4572
0
        (void)mRootNode->GetAsQuery()->Refresh();
4573
0
      }
4574
0
      return NS_OK;
4575
0
    }
4576
0
4577
0
    if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
4578
0
      (void)mRootNode->GetAsQuery()->Refresh();
4579
0
      return NS_OK;
4580
0
    }
4581
0
4582
0
    // We are result of a folder node, then we should run through history
4583
0
    // observers that are containers queries and refresh them.
4584
0
    // We use a copy of the observers array since requerying could potentially
4585
0
    // cause changes to the array.
4586
0
    ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
4587
0
  }
4588
0
4589
0
  return NS_OK;
4590
0
}
4591
4592
4593
void
4594
0
nsNavHistoryResult::HandlePlacesEvent(const PlacesEventSequence& aEvents) {
4595
0
  for (const auto& event : aEvents) {
4596
0
    if (NS_WARN_IF(event->Type() != PlacesEventType::Page_visited)) {
4597
0
      continue;
4598
0
    }
4599
0
4600
0
    const dom::PlacesVisit* visit = event->AsPlacesVisit();
4601
0
    if (NS_WARN_IF(!visit)) {
4602
0
      continue;
4603
0
    }
4604
0
4605
0
    nsCOMPtr<nsIURI> uri;
4606
0
    MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), visit->mUrl));
4607
0
    if (!uri) {
4608
0
      return;
4609
0
    }
4610
0
    OnVisit(uri, visit->mVisitId, visit->mVisitTime * 1000,
4611
0
            visit->mTransitionType, visit->mPageGuid,
4612
0
            visit->mHidden, visit->mVisitCount, visit->mLastKnownTitle);
4613
0
  }
4614
0
}
4615
4616
4617
NS_IMETHODIMP
4618
nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
4619
                                   const nsAString& aPageTitle,
4620
                                   const nsACString& aGUID)
4621
0
{
4622
0
  NS_ENSURE_ARG(aURI);
4623
0
4624
0
  ENUMERATE_HISTORY_OBSERVERS(OnTitleChanged(aURI, aPageTitle, aGUID));
4625
0
  return NS_OK;
4626
0
}
4627
4628
4629
NS_IMETHODIMP
4630
nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
4631
                                      int32_t aNewFrecency,
4632
                                      const nsACString& aGUID,
4633
                                      bool aHidden,
4634
                                      PRTime aLastVisitDate)
4635
0
{
4636
0
  return NS_OK;
4637
0
}
4638
4639
4640
NS_IMETHODIMP
4641
nsNavHistoryResult::OnManyFrecenciesChanged()
4642
0
{
4643
0
  return NS_OK;
4644
0
}
4645
4646
4647
NS_IMETHODIMP
4648
nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
4649
                                const nsACString& aGUID,
4650
                                uint16_t aReason)
4651
0
{
4652
0
  NS_ENSURE_ARG(aURI);
4653
0
4654
0
  ENUMERATE_HISTORY_OBSERVERS(OnDeleteURI(aURI, aGUID, aReason));
4655
0
  return NS_OK;
4656
0
}
4657
4658
4659
NS_IMETHODIMP
4660
nsNavHistoryResult::OnClearHistory()
4661
0
{
4662
0
  ENUMERATE_HISTORY_OBSERVERS(OnClearHistory());
4663
0
  return NS_OK;
4664
0
}
4665
4666
4667
NS_IMETHODIMP
4668
nsNavHistoryResult::OnPageChanged(nsIURI* aURI,
4669
                                  uint32_t aChangedAttribute,
4670
                                  const nsAString& aValue,
4671
                                  const nsACString& aGUID)
4672
0
{
4673
0
  NS_ENSURE_ARG(aURI);
4674
0
4675
0
  ENUMERATE_HISTORY_OBSERVERS(OnPageChanged(aURI, aChangedAttribute, aValue, aGUID));
4676
0
  return NS_OK;
4677
0
}
4678
4679
4680
/**
4681
 * Don't do anything when visits expire.
4682
 */
4683
NS_IMETHODIMP
4684
nsNavHistoryResult::OnDeleteVisits(nsIURI* aURI,
4685
                                   bool aPartialRemoval,
4686
                                   const nsACString& aGUID,
4687
                                   uint16_t aReason,
4688
                                   uint32_t aTransitionType)
4689
0
{
4690
0
  NS_ENSURE_ARG(aURI);
4691
0
4692
0
  ENUMERATE_HISTORY_OBSERVERS(OnDeleteVisits(aURI, aPartialRemoval, aGUID, aReason,
4693
0
                                             aTransitionType));
4694
0
  return NS_OK;
4695
0
}
4696
4697
void
4698
nsNavHistoryResult::OnMobilePrefChanged()
4699
0
{
4700
0
  ENUMERATE_MOBILE_PREF_OBSERVERS(OnMobilePrefChanged(Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)));
4701
0
}
4702
4703
4704
void
4705
nsNavHistoryResult::OnMobilePrefChangedCallback(const char *prefName,
4706
                                                nsNavHistoryResult *self)
4707
0
{
4708
0
  MOZ_ASSERT(!strcmp(prefName, MOBILE_BOOKMARKS_PREF),
4709
0
    "We only expected Mobile Bookmarks pref change.");
4710
0
4711
0
  self->OnMobilePrefChanged();
4712
0
}