Source code

Revision control

Other Tools

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) static_cast<nsNavHistoryContainerResultNode*>(_node)
34
35
#define NOTIFY_RESULT_OBSERVERS_RET(_result, _method, _ret) \
36
PR_BEGIN_MACRO \
37
NS_ENSURE_TRUE(_result, _ret); \
38
if (!_result->mSuppressNotifications) { \
39
ENUMERATE_WEAKARRAY(_result->mObservers, nsINavHistoryResultObserver, \
40
_method) \
41
} \
42
PR_END_MACRO
43
44
#define NOTIFY_RESULT_OBSERVERS(_result, _method) \
45
NOTIFY_RESULT_OBSERVERS_RET(_result, _method, NS_ERROR_UNEXPECTED)
46
47
// What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors,
48
// but some of our classes (like nsNavHistoryResult) have an ambiguous base
49
// class of nsISupports which prevents this from working (the default macro
50
// converts it to nsISupports, then addrefs it, then returns it). Therefore, we
51
// expand the macro here and change it so that it works. Yuck.
52
#define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \
53
if (aIID.Equals(NS_GET_IID(_class))) { \
54
NS_ADDREF(this); \
55
*aInstancePtr = this; \
56
return NS_OK; \
57
} else
58
59
// Number of changes to handle separately in a batch. If more changes are
60
// requested the node will switch to full refresh mode.
61
#define MAX_BATCH_CHANGES_BEFORE_REFRESH 5
62
63
using namespace mozilla;
64
using namespace mozilla::places;
65
66
namespace {
67
68
/**
69
* Returns conditions for query update.
70
* QUERYUPDATE_TIME:
71
* This query is only limited by an inclusive time range on the first
72
* query object. The caller can quickly evaluate the time itself if it
73
* chooses. This is even simpler than "simple" below.
74
* QUERYUPDATE_SIMPLE:
75
* This query is evaluatable using evaluateQueryForNode to do live
76
* updating.
77
* QUERYUPDATE_COMPLEX:
78
* This query is not evaluatable using evaluateQueryForNode. When something
79
* happens that this query updates, you will need to re-run the query.
80
* QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
81
* A complex query that additionally has dependence on bookmarks. All
82
* bookmark-dependent queries fall under this category.
83
* QUERYUPDATE_MOBILEPREF:
84
* A complex query but only updates when the mobile preference changes.
85
* QUERYUPDATE_NONE:
86
* A query that never updates, e.g. the left-pane root query.
87
*
88
* aHasSearchTerms will be set to true if the query has any dependence on
89
* keywords. When there is no dependence on keywords, we can handle title
90
* change operations as simple instead of complex.
91
*/
92
uint32_t getUpdateRequirements(const RefPtr<nsNavHistoryQuery>& aQuery,
93
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
94
bool* aHasSearchTerms) {
95
// first check if there are search terms
96
bool hasSearchTerms = *aHasSearchTerms = !aQuery->SearchTerms().IsEmpty();
97
98
bool nonTimeBasedItems = false;
99
bool domainBasedItems = false;
100
101
if (aQuery->Parents().Length() > 0 || aQuery->OnlyBookmarked() ||
102
aQuery->Tags().Length() > 0 ||
103
(aOptions->QueryType() ==
104
nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
105
hasSearchTerms)) {
106
return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
107
}
108
109
// Note: we don't currently have any complex non-bookmarked items, but these
110
// are expected to be added. Put detection of these items here.
111
if (hasSearchTerms || !aQuery->Domain().IsVoid() || aQuery->Uri() != nullptr)
112
nonTimeBasedItems = true;
113
114
if (!aQuery->Domain().IsVoid()) domainBasedItems = true;
115
116
if (aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT)
117
return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
118
119
if (aOptions->ResultType() ==
120
nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY)
121
return QUERYUPDATE_MOBILEPREF;
122
123
if (aOptions->ResultType() ==
124
nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY)
125
return QUERYUPDATE_NONE;
126
127
// Whenever there is a maximum number of results,
128
// and we are not a bookmark query we must requery. This
129
// is because we can't generally know if any given addition/change causes
130
// the item to be in the top N items in the database.
131
uint16_t sortingMode = aOptions->SortingMode();
132
if (aOptions->MaxResults() > 0 &&
133
sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING &&
134
sortingMode != nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
135
return QUERYUPDATE_COMPLEX;
136
137
if (domainBasedItems) return QUERYUPDATE_HOST;
138
if (!nonTimeBasedItems) return QUERYUPDATE_TIME;
139
140
return QUERYUPDATE_SIMPLE;
141
}
142
143
/**
144
* We might have interesting encodings and different case in the host name.
145
* This will convert that host name into an ASCII host name by sending it
146
* through the URI canonicalization. The result can be used for comparison
147
* with other ASCII host name strings.
148
*/
149
nsresult asciiHostNameFromHostString(const nsACString& aHostName,
150
nsACString& aAscii) {
151
aAscii.Truncate();
152
if (aHostName.IsEmpty()) {
153
return NS_OK;
154
}
155
// To properly generate a uri we must provide a protocol.
156
nsAutoCString fakeURL("http://");
157
fakeURL.Append(aHostName);
158
nsCOMPtr<nsIURI> uri;
159
nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL);
160
NS_ENSURE_SUCCESS(rv, rv);
161
rv = uri->GetAsciiHost(aAscii);
162
NS_ENSURE_SUCCESS(rv, rv);
163
return NS_OK;
164
}
165
166
/**
167
* This runs the node through the given query to see if satisfies the
168
* query conditions. Not every query parameters are handled by this code,
169
* but we handle the most common ones so that performance is better.
170
* We assume that the time on the node is the time that we want to compare.
171
* This is not necessarily true because URL nodes have the last access time,
172
* which is not necessarily the same. However, since this is being called
173
* to update the list, we assume that the last access time is the current
174
* access time that we are being asked to compare so it works out.
175
* Returns true if node matches the query, false if not.
176
*/
177
bool evaluateQueryForNode(const RefPtr<nsNavHistoryQuery>& aQuery,
178
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
179
const RefPtr<nsNavHistoryResultNode>& aNode) {
180
// Hidden
181
if (aNode->mHidden && !aOptions->IncludeHidden()) return false;
182
183
bool hasIt;
184
// Begin time
185
aQuery->GetHasBeginTime(&hasIt);
186
if (hasIt) {
187
PRTime beginTime = nsNavHistory::NormalizeTime(aQuery->BeginTimeReference(),
188
aQuery->BeginTime());
189
if (aNode->mTime < beginTime) return false;
190
}
191
192
// End time
193
aQuery->GetHasEndTime(&hasIt);
194
if (hasIt) {
195
PRTime endTime = nsNavHistory::NormalizeTime(aQuery->EndTimeReference(),
196
aQuery->EndTime());
197
if (aNode->mTime > endTime) return false;
198
}
199
200
// Search terms
201
if (!aQuery->SearchTerms().IsEmpty()) {
202
// we can use the existing filtering code, just give it our one object in
203
// an array.
204
nsCOMArray<nsNavHistoryResultNode> inputSet;
205
inputSet.AppendObject(aNode);
206
nsCOMArray<nsNavHistoryResultNode> filteredSet;
207
nsresult rv = nsNavHistory::FilterResultSet(nullptr, inputSet, &filteredSet,
208
aQuery, aOptions);
209
if (NS_FAILED(rv)) return false;
210
if (!filteredSet.Count()) return false;
211
}
212
213
// Domain/host
214
if (!aQuery->Domain().IsVoid()) {
215
nsCOMPtr<nsIURI> nodeUri;
216
if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
217
return false;
218
nsAutoCString asciiRequest;
219
if (NS_FAILED(asciiHostNameFromHostString(aQuery->Domain(), asciiRequest)))
220
return false;
221
if (aQuery->DomainIsHost()) {
222
nsAutoCString host;
223
if (NS_FAILED(nodeUri->GetAsciiHost(host))) return false;
224
225
if (!asciiRequest.Equals(host)) return false;
226
}
227
// check domain names.
228
nsNavHistory* history = nsNavHistory::GetHistoryService();
229
if (!history) return false;
230
nsAutoCString domain;
231
history->DomainNameFromURI(nodeUri, domain);
232
if (!asciiRequest.Equals(domain)) return false;
233
}
234
235
// URI
236
if (aQuery->Uri()) {
237
nsCOMPtr<nsIURI> nodeUri;
238
if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
239
return false;
240
bool equals;
241
nsresult rv = aQuery->Uri()->Equals(nodeUri, &equals);
242
NS_ENSURE_SUCCESS(rv, false);
243
if (!equals) return false;
244
}
245
246
// Transitions
247
const nsTArray<uint32_t>& transitions = aQuery->Transitions();
248
if (aNode->mTransitionType > 0 && transitions.Length() &&
249
!transitions.Contains(aNode->mTransitionType)) {
250
return false;
251
}
252
253
// If we ever make it to the bottom, that means it passed all the tests for
254
// the given query.
255
return true;
256
}
257
258
// Emulate string comparison (used for sorting) for PRTime and int.
259
inline int32_t ComparePRTime(PRTime a, PRTime b) {
260
if (a < b)
261
return -1;
262
else if (a > b)
263
return 1;
264
return 0;
265
}
266
inline int32_t CompareIntegers(uint32_t a, uint32_t b) { return a - b; }
267
268
} // anonymous namespace
269
270
NS_IMPL_CYCLE_COLLECTION(nsNavHistoryResultNode, mParent)
271
272
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResultNode)
273
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultNode)
274
NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
275
NS_INTERFACE_MAP_END
276
277
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
278
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
279
280
nsNavHistoryResultNode::nsNavHistoryResultNode(const nsACString& aURI,
281
const nsACString& aTitle,
282
uint32_t aAccessCount,
283
PRTime aTime)
284
: mParent(nullptr),
285
mURI(aURI),
286
mTitle(aTitle),
287
mAreTagsSorted(false),
288
mAccessCount(aAccessCount),
289
mTime(aTime),
290
mBookmarkIndex(-1),
291
mItemId(-1),
292
mFolderId(-1),
293
mVisitId(-1),
294
mFromVisitId(-1),
295
mDateAdded(0),
296
mLastModified(0),
297
mIndentLevel(-1),
298
mFrecency(0),
299
mHidden(false),
300
mTransitionType(0) {
301
mTags.SetIsVoid(true);
302
}
303
304
NS_IMETHODIMP
305
nsNavHistoryResultNode::GetIcon(nsACString& aIcon) {
306
if (this->IsContainer() || mURI.IsEmpty()) {
307
return NS_OK;
308
}
309
310
aIcon.AppendLiteral("page-icon:");
311
aIcon.Append(mURI);
312
313
return NS_OK;
314
}
315
316
NS_IMETHODIMP
317
nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent) {
318
NS_IF_ADDREF(*aParent = mParent);
319
return NS_OK;
320
}
321
322
NS_IMETHODIMP
323
nsNavHistoryResultNode::GetParentResult(nsINavHistoryResult** aResult) {
324
*aResult = nullptr;
325
if (IsContainer())
326
NS_IF_ADDREF(*aResult = GetAsContainer()->mResult);
327
else if (mParent)
328
NS_IF_ADDREF(*aResult = mParent->mResult);
329
330
NS_ENSURE_STATE(*aResult);
331
return NS_OK;
332
}
333
334
NS_IMETHODIMP
335
nsNavHistoryResultNode::GetTags(nsAString& aTags) {
336
// Only URI-nodes may be associated with tags
337
if (!IsURI()) {
338
aTags.Truncate();
339
return NS_OK;
340
}
341
342
// Initially, the tags string is set to a void string (see constructor). We
343
// then build it the first time this method called is called (and by that,
344
// implicitly unset the void flag). Result observers may re-set the void flag
345
// in order to force rebuilding of the tags string.
346
if (!mTags.IsVoid()) {
347
// If mTags is assigned by a history query it is unsorted for performance
348
// reasons, it must be sorted by name on first read access.
349
if (!mAreTagsSorted) {
350
nsTArray<nsCString> tags;
351
ParseString(NS_ConvertUTF16toUTF8(mTags), ',', tags);
352
tags.Sort();
353
mTags.SetIsVoid(true);
354
for (nsTArray<nsCString>::index_type i = 0; i < tags.Length(); ++i) {
355
AppendUTF8toUTF16(tags[i], mTags);
356
if (i < tags.Length() - 1) mTags.AppendLiteral(", ");
357
}
358
mAreTagsSorted = true;
359
}
360
aTags.Assign(mTags);
361
return NS_OK;
362
}
363
364
// Fetch the tags
365
RefPtr<Database> DB = Database::GetDatabase();
366
NS_ENSURE_STATE(DB);
367
nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
368
"/* do not warn (bug 487594) */ "
369
"SELECT GROUP_CONCAT(tag_title, ', ') "
370
"FROM ( "
371
"SELECT t.title AS tag_title "
372
"FROM moz_bookmarks b "
373
"JOIN moz_bookmarks t ON t.id = +b.parent "
374
"WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = "
375
"hash(:page_url) AND url = :page_url) "
376
"AND t.parent = :tags_folder "
377
"ORDER BY t.title COLLATE NOCASE ASC "
378
") ");
379
NS_ENSURE_STATE(stmt);
380
mozStorageStatementScoper scoper(stmt);
381
382
nsNavHistory* history = nsNavHistory::GetHistoryService();
383
NS_ENSURE_STATE(history);
384
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
385
history->GetTagsFolder());
386
NS_ENSURE_SUCCESS(rv, rv);
387
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
388
NS_ENSURE_SUCCESS(rv, rv);
389
390
bool hasTags = false;
391
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasTags)) && hasTags) {
392
rv = stmt->GetString(0, mTags);
393
NS_ENSURE_SUCCESS(rv, rv);
394
aTags.Assign(mTags);
395
mAreTagsSorted = true;
396
}
397
398
// If this node is a child of a history query, we need to make sure changes
399
// to tags are properly live-updated.
400
if (mParent && mParent->IsQuery() &&
401
mParent->mOptions->QueryType() ==
402
nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
403
nsNavHistoryQueryResultNode* query = mParent->GetAsQuery();
404
nsNavHistoryResult* result = query->GetResult();
405
NS_ENSURE_STATE(result);
406
result->AddAllBookmarksObserver(query);
407
}
408
409
return NS_OK;
410
}
411
412
NS_IMETHODIMP
413
nsNavHistoryResultNode::GetPageGuid(nsACString& aPageGuid) {
414
aPageGuid = mPageGuid;
415
return NS_OK;
416
}
417
418
NS_IMETHODIMP
419
nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
420
aBookmarkGuid = mBookmarkGuid;
421
return NS_OK;
422
}
423
424
NS_IMETHODIMP
425
nsNavHistoryResultNode::GetVisitId(int64_t* aVisitId) {
426
*aVisitId = mVisitId;
427
return NS_OK;
428
}
429
430
NS_IMETHODIMP
431
nsNavHistoryResultNode::GetFromVisitId(int64_t* aFromVisitId) {
432
*aFromVisitId = mFromVisitId;
433
return NS_OK;
434
}
435
436
NS_IMETHODIMP
437
nsNavHistoryResultNode::GetVisitType(uint32_t* aVisitType) {
438
*aVisitType = mTransitionType;
439
return NS_OK;
440
}
441
442
void nsNavHistoryResultNode::OnRemoving() { mParent = nullptr; }
443
444
/**
445
* This will find the result for this node. We can ask the nearest container
446
* for this value (either ourselves or our parents should be a container,
447
* and all containers have result pointers).
448
*
449
* @note The result may be null, if the container is detached from the result
450
* who owns it.
451
*/
452
nsNavHistoryResult* nsNavHistoryResultNode::GetResult() {
453
nsNavHistoryResultNode* node = this;
454
do {
455
if (node->IsContainer()) {
456
nsNavHistoryContainerResultNode* container = TO_CONTAINER(node);
457
return container->mResult;
458
}
459
node = node->mParent;
460
} while (node);
461
MOZ_ASSERT(false, "No container node found in hierarchy!");
462
return nullptr;
463
}
464
465
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode,
466
nsNavHistoryResultNode, mResult, mChildren)
467
468
NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode,
469
nsNavHistoryResultNode)
470
NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode,
471
nsNavHistoryResultNode)
472
473
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryContainerResultNode)
474
NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
475
NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
476
NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
477
478
nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
479
const nsACString& aURI, const nsACString& aTitle, PRTime aTime,
480
uint32_t aContainerType, nsNavHistoryQueryOptions* aOptions)
481
: nsNavHistoryResultNode(aURI, aTitle, 0, aTime),
482
mResult(nullptr),
483
mContainerType(aContainerType),
484
mExpanded(false),
485
mOptions(aOptions),
486
mAsyncCanceledState(NOT_CANCELED) {
487
MOZ_ASSERT(mOptions);
488
MOZ_ALWAYS_SUCCEEDS(mOptions->Clone(getter_AddRefs(mOriginalOptions)));
489
}
490
491
nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode() {
492
// Explicitly clean up array of children of this container. We must ensure
493
// all references are gone and all of their destructors are called.
494
mChildren.Clear();
495
}
496
497
/**
498
* Containers should notify their children that they are being removed when the
499
* container is being removed.
500
*/
501
void nsNavHistoryContainerResultNode::OnRemoving() {
502
nsNavHistoryResultNode::OnRemoving();
503
for (int32_t i = 0; i < mChildren.Count(); ++i) mChildren[i]->OnRemoving();
504
mChildren.Clear();
505
mResult = nullptr;
506
}
507
508
bool nsNavHistoryContainerResultNode::AreChildrenVisible() {
509
nsNavHistoryResult* result = GetResult();
510
if (!result) {
511
MOZ_ASSERT_UNREACHABLE("Invalid result");
512
return false;
513
}
514
515
if (!mExpanded) return false;
516
517
// Now check if any ancestor is closed.
518
nsNavHistoryContainerResultNode* ancestor = mParent;
519
while (ancestor) {
520
if (!ancestor->mExpanded) return false;
521
522
ancestor = ancestor->mParent;
523
}
524
525
return true;
526
}
527
528
NS_IMETHODIMP
529
nsNavHistoryContainerResultNode::GetContainerOpen(bool* aContainerOpen) {
530
*aContainerOpen = mExpanded;
531
return NS_OK;
532
}
533
534
NS_IMETHODIMP
535
nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen) {
536
if (aContainerOpen) {
537
if (!mExpanded) {
538
if (mOptions->AsyncEnabled())
539
OpenContainerAsync();
540
else
541
OpenContainer();
542
}
543
} else {
544
if (mExpanded)
545
CloseContainer();
546
else if (mAsyncPendingStmt)
547
CancelAsyncOpen(false);
548
}
549
550
return NS_OK;
551
}
552
553
/**
554
* Notifies the result's observers of a change in the container's state. The
555
* notification includes both the old and new states: The old is aOldState, and
556
* the new is the container's current state.
557
*
558
* @param aOldState
559
* The state being transitioned out of.
560
*/
561
nsresult nsNavHistoryContainerResultNode::NotifyOnStateChange(
562
uint16_t aOldState) {
563
nsNavHistoryResult* result = GetResult();
564
NS_ENSURE_STATE(result);
565
566
nsresult rv;
567
uint16_t currState;
568
rv = GetState(&currState);
569
NS_ENSURE_SUCCESS(rv, rv);
570
571
// Notify via the new ContainerStateChanged observer method.
572
NOTIFY_RESULT_OBSERVERS(result,
573
ContainerStateChanged(this, aOldState, currState));
574
return NS_OK;
575
}
576
577
NS_IMETHODIMP
578
nsNavHistoryContainerResultNode::GetState(uint16_t* _state) {
579
NS_ENSURE_ARG_POINTER(_state);
580
581
*_state = mExpanded ? (uint16_t)STATE_OPENED
582
: mAsyncPendingStmt ? (uint16_t)STATE_LOADING
583
: (uint16_t)STATE_CLOSED;
584
585
return NS_OK;
586
}
587
588
/**
589
* This handles the generic container case. Other container types should
590
* override this to do their own handling.
591
*/
592
nsresult nsNavHistoryContainerResultNode::OpenContainer() {
593
NS_ASSERTION(!mExpanded, "Container must not be expanded to open it");
594
mExpanded = true;
595
596
nsresult rv = NotifyOnStateChange(STATE_CLOSED);
597
NS_ENSURE_SUCCESS(rv, rv);
598
599
return NS_OK;
600
}
601
602
/**
603
* Unset aSuppressNotifications to notify observers on this change. That is
604
* the normal operation. This is set to false for the recursive calls since the
605
* root container that is being closed will handle recomputation of the visible
606
* elements for its entire subtree.
607
*/
608
nsresult nsNavHistoryContainerResultNode::CloseContainer(
609
bool aSuppressNotifications) {
610
NS_ASSERTION(
611
(mExpanded && !mAsyncPendingStmt) || (!mExpanded && mAsyncPendingStmt),
612
"Container must be expanded or loading to close it");
613
614
nsresult rv;
615
uint16_t oldState;
616
rv = GetState(&oldState);
617
NS_ENSURE_SUCCESS(rv, rv);
618
619
if (mExpanded) {
620
// Recursively close all child containers.
621
for (int32_t i = 0; i < mChildren.Count(); ++i) {
622
if (mChildren[i]->IsContainer() &&
623
mChildren[i]->GetAsContainer()->mExpanded)
624
mChildren[i]->GetAsContainer()->CloseContainer(true);
625
}
626
627
mExpanded = false;
628
}
629
630
// Be sure to set this to null before notifying observers. It signifies that
631
// the container is no longer loading (if it was in the first place).
632
mAsyncPendingStmt = nullptr;
633
634
if (!aSuppressNotifications) {
635
rv = NotifyOnStateChange(oldState);
636
NS_ENSURE_SUCCESS(rv, rv);
637
}
638
639
// If this is the root container of a result, we can tell the result to stop
640
// observing changes, otherwise the result will stay in memory and updates
641
// itself till it is cycle collected.
642
nsNavHistoryResult* result = GetResult();
643
NS_ENSURE_STATE(result);
644
if (result->mRootNode == this) {
645
result->StopObserving();
646
// When reopening this node its result will be out of sync.
647
// We must clear our children to ensure we will call FillChildren
648
// again in such a case.
649
if (this->IsQuery())
650
this->GetAsQuery()->ClearChildren(true);
651
else if (this->IsFolder())
652
this->GetAsFolder()->ClearChildren(true);
653
}
654
655
return NS_OK;
656
}
657
658
/**
659
* The async version of OpenContainer.
660
*/
661
nsresult nsNavHistoryContainerResultNode::OpenContainerAsync() {
662
return NS_ERROR_NOT_IMPLEMENTED;
663
}
664
665
/**
666
* Cancels the pending asynchronous Storage execution triggered by
667
* FillChildrenAsync, if it exists. This method doesn't do much, because after
668
* cancelation Storage will call this node's HandleCompletion callback, where
669
* the real work is done.
670
*
671
* @param aRestart
672
* If true, async execution will be restarted by HandleCompletion.
673
*/
674
void nsNavHistoryContainerResultNode::CancelAsyncOpen(bool aRestart) {
675
NS_ASSERTION(mAsyncPendingStmt, "Async execution canceled but not pending");
676
677
mAsyncCanceledState = aRestart ? CANCELED_RESTART_NEEDED : CANCELED;
678
679
// Cancel will fail if the pending statement has already been canceled.
680
// That's OK since this method may be called multiple times, and multiple
681
// cancels don't harm anything.
682
(void)mAsyncPendingStmt->Cancel();
683
}
684
685
/**
686
* This builds up tree statistics from the bottom up. Call with a container
687
* and the indent level of that container. To init the full tree, call with
688
* the root container. The default indent level is -1, which is appropriate
689
* for the root level.
690
*
691
* CALL THIS AFTER FILLING ANY CONTAINER to update the parent and result node
692
* pointers, even if you don't care about visit counts and last visit dates.
693
*/
694
void nsNavHistoryContainerResultNode::FillStats() {
695
uint32_t accessCount = 0;
696
PRTime newTime = 0;
697
698
for (int32_t i = 0; i < mChildren.Count(); ++i) {
699
nsNavHistoryResultNode* node = mChildren[i];
700
SetAsParentOfNode(node);
701
accessCount += node->mAccessCount;
702
// this is how container nodes get sorted by date
703
// The container gets the most recent time of the child nodes.
704
if (node->mTime > newTime) newTime = node->mTime;
705
}
706
707
if (mExpanded) {
708
mAccessCount = accessCount;
709
if (!IsQuery() || newTime > mTime) mTime = newTime;
710
}
711
}
712
713
void nsNavHistoryContainerResultNode::SetAsParentOfNode(
714
nsNavHistoryResultNode* aNode) {
715
aNode->mParent = this;
716
aNode->mIndentLevel = mIndentLevel + 1;
717
if (aNode->IsContainer()) {
718
nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
719
// Propagate some of the parent's options to this container.
720
if (mOptions->ExcludeItems()) {
721
container->mOptions->SetExcludeItems(true);
722
}
723
if (mOptions->ExcludeQueries()) {
724
container->mOptions->SetExcludeQueries(true);
725
}
726
if (aNode->IsFolder() && mOptions->AsyncEnabled()) {
727
container->mOptions->SetAsyncEnabled(true);
728
}
729
if (!mOptions->ExpandQueries()) {
730
container->mOptions->SetExpandQueries(false);
731
}
732
container->mResult = mResult;
733
container->FillStats();
734
}
735
}
736
737
/**
738
* This is used when one container changes to do a minimal update of the tree
739
* structure. When something changes, you want to call FillStats if necessary
740
* and update this container completely. Then call this function which will
741
* walk up the tree and fill in the previous containers.
742
*
743
* Note that you have to tell us by how much our access count changed. Our
744
* access count should already be set to the new value; this is used tochange
745
* the parents without having to re-count all their children.
746
*
747
* This does NOT update the last visit date downward. Therefore, if you are
748
* deleting a node that has the most recent last visit date, the parents will
749
* not get their last visit dates downshifted accordingly. This is a rather
750
* unusual case: we don't often delete things, and we usually don't even show
751
* the last visit date for folders. Updating would be slower because we would
752
* have to recompute it from scratch.
753
*/
754
nsresult nsNavHistoryContainerResultNode::ReverseUpdateStats(
755
int32_t aAccessCountChange) {
756
if (mParent) {
757
nsNavHistoryResult* result = GetResult();
758
bool shouldNotify =
759
result && mParent->mParent && mParent->mParent->AreChildrenVisible();
760
761
uint32_t oldAccessCount = mParent->mAccessCount;
762
PRTime oldTime = mParent->mTime;
763
764
mParent->mAccessCount += aAccessCountChange;
765
bool timeChanged = false;
766
if (mTime > mParent->mTime) {
767
timeChanged = true;
768
mParent->mTime = mTime;
769
}
770
771
if (shouldNotify) {
772
NOTIFY_RESULT_OBSERVERS(
773
result, NodeHistoryDetailsChanged(TO_ICONTAINER(mParent), oldTime,
774
oldAccessCount));
775
}
776
777
// check sorting, the stats may have caused this node to move if the
778
// sorting depended on something we are changing.
779
uint16_t sortMode = mParent->GetSortType();
780
bool sortingByVisitCount =
781
sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
782
sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING;
783
bool sortingByTime =
784
sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
785
sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING;
786
787
if ((sortingByVisitCount && aAccessCountChange != 0) ||
788
(sortingByTime && timeChanged)) {
789
int32_t ourIndex = mParent->FindChild(this);
790
NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
791
if (ourIndex >= 0) EnsureItemPosition(static_cast<uint32_t>(ourIndex));
792
}
793
794
nsresult rv = mParent->ReverseUpdateStats(aAccessCountChange);
795
NS_ENSURE_SUCCESS(rv, rv);
796
}
797
798
return NS_OK;
799
}
800
801
/**
802
* This walks up the tree until we find a query result node or the root to get
803
* the sorting type.
804
*/
805
uint16_t nsNavHistoryContainerResultNode::GetSortType() {
806
if (mParent) return mParent->GetSortType();
807
if (mResult) return mResult->mSortingMode;
808
809
// This is a detached container, just use natural order.
810
return nsINavHistoryQueryOptions::SORT_BY_NONE;
811
}
812
813
nsresult nsNavHistoryContainerResultNode::Refresh() {
814
NS_WARNING(
815
"Refresh() is supported by queries or folders, not generic containers.");
816
return NS_OK;
817
}
818
819
/**
820
* @return the sorting comparator function for the give sort type, or null if
821
* there is no comparator.
822
*/
823
nsNavHistoryContainerResultNode::SortComparator
824
nsNavHistoryContainerResultNode::GetSortingComparator(uint16_t aSortType) {
825
switch (aSortType) {
826
case nsINavHistoryQueryOptions::SORT_BY_NONE:
827
return &SortComparison_Bookmark;
828
case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
829
return &SortComparison_TitleLess;
830
case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
831
return &SortComparison_TitleGreater;
832
case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
833
return &SortComparison_DateLess;
834
case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
835
return &SortComparison_DateGreater;
836
case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
837
return &SortComparison_URILess;
838
case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
839
return &SortComparison_URIGreater;
840
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
841
return &SortComparison_VisitCountLess;
842
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
843
return &SortComparison_VisitCountGreater;
844
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
845
return &SortComparison_DateAddedLess;
846
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
847
return &SortComparison_DateAddedGreater;
848
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
849
return &SortComparison_LastModifiedLess;
850
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
851
return &SortComparison_LastModifiedGreater;
852
case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
853
return &SortComparison_TagsLess;
854
case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
855
return &SortComparison_TagsGreater;
856
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
857
return &SortComparison_FrecencyLess;
858
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
859
return &SortComparison_FrecencyGreater;
860
default:
861
MOZ_ASSERT_UNREACHABLE("Bad sorting type");
862
return nullptr;
863
}
864
}
865
866
/**
867
* This is used by Result::SetSortingMode and QueryResultNode::FillChildren to
868
* sort the child list.
869
*
870
* This does NOT update any visibility or tree information. The caller will
871
* have to completely rebuild the visible list after this.
872
*/
873
void nsNavHistoryContainerResultNode::RecursiveSort(
874
SortComparator aComparator) {
875
mChildren.Sort(aComparator, nullptr);
876
for (int32_t i = 0; i < mChildren.Count(); ++i) {
877
if (mChildren[i]->IsContainer())
878
mChildren[i]->GetAsContainer()->RecursiveSort(aComparator);
879
}
880
}
881
882
/**
883
* @return the index that the given item would fall on if it were to be
884
* inserted using the given sorting.
885
*/
886
uint32_t nsNavHistoryContainerResultNode::FindInsertionPoint(
887
nsNavHistoryResultNode* aNode, SortComparator aComparator,
888
bool* aItemExists) {
889
if (aItemExists) (*aItemExists) = false;
890
891
if (mChildren.Count() == 0) return 0;
892
893
// The common case is the beginning or the end because this is used to insert
894
// new items that are added to history, which is usually sorted by date.
895
int32_t res;
896
res = aComparator(aNode, mChildren[0], nullptr);
897
if (res <= 0) {
898
if (aItemExists && res == 0) (*aItemExists) = true;
899
return 0;
900
}
901
res = aComparator(aNode, mChildren[mChildren.Count() - 1], nullptr);
902
if (res >= 0) {
903
if (aItemExists && res == 0) (*aItemExists) = true;
904
return mChildren.Count();
905
}
906
907
uint32_t beginRange = 0; // inclusive
908
uint32_t endRange = mChildren.Count(); // exclusive
909
while (1) {
910
if (beginRange == endRange) return endRange;
911
uint32_t center = beginRange + (endRange - beginRange) / 2;
912
int32_t res = aComparator(aNode, mChildren[center], nullptr);
913
if (res <= 0) {
914
endRange = center; // left side
915
if (aItemExists && res == 0) (*aItemExists) = true;
916
} else {
917
beginRange = center + 1; // right site
918
}
919
}
920
}
921
922
/**
923
* This checks the child node at the given index to see if its sorting is
924
* correct. This is called when nodes are updated and we need to see whether
925
* we need to move it.
926
*
927
* @returns true if not and it should be resorted.
928
*/
929
bool nsNavHistoryContainerResultNode::DoesChildNeedResorting(
930
uint32_t aIndex, SortComparator aComparator) {
931
NS_ASSERTION(aIndex < uint32_t(mChildren.Count()),
932
"Input index out of range");
933
if (mChildren.Count() == 1) return false;
934
935
if (aIndex > 0) {
936
// compare to previous item
937
if (aComparator(mChildren[aIndex - 1], mChildren[aIndex], nullptr) > 0)
938
return true;
939
}
940
if (aIndex < uint32_t(mChildren.Count()) - 1) {
941
// compare to next item
942
if (aComparator(mChildren[aIndex], mChildren[aIndex + 1], nullptr) > 0)
943
return true;
944
}
945
return false;
946
}
947
948
/* static */
949
int32_t nsNavHistoryContainerResultNode::SortComparison_StringLess(
950
const nsAString& a, const nsAString& b) {
951
nsNavHistory* history = nsNavHistory::GetHistoryService();
952
NS_ENSURE_TRUE(history, 0);
953
nsICollation* collation = history->GetCollation();
954
NS_ENSURE_TRUE(collation, 0);
955
956
int32_t res = 0;
957
collation->CompareString(nsICollation::kCollationCaseInSensitive, a, b, &res);
958
return res;
959
}
960
961
/**
962
* When there are bookmark indices, we should never have ties, so we don't
963
* need to worry about tiebreaking. When there are no bookmark indices,
964
* everything will be -1 and we don't worry about sorting.
965
*/
966
int32_t nsNavHistoryContainerResultNode::SortComparison_Bookmark(
967
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
968
return a->mBookmarkIndex - b->mBookmarkIndex;
969
}
970
971
/**
972
* These are a little more complicated because they do a localization
973
* conversion. If this is too slow, we can compute the sort keys once in
974
* advance, sort that array, and then reorder the real array accordingly.
975
* This would save some key generations.
976
*
977
* The collation object must be allocated before sorting on title!
978
*/
979
int32_t nsNavHistoryContainerResultNode::SortComparison_TitleLess(
980
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
981
uint32_t aType;
982
a->GetType(&aType);
983
984
int32_t value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
985
NS_ConvertUTF8toUTF16(b->mTitle));
986
if (value == 0) {
987
// resolve by URI
988
if (a->IsURI()) {
989
value = a->mURI.Compare(b->mURI.get());
990
}
991
if (value == 0) {
992
// resolve by date
993
value = ComparePRTime(a->mTime, b->mTime);
994
if (value == 0)
995
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(
996
a, b, closure);
997
}
998
}
999
return value;
1000
}
1001
int32_t nsNavHistoryContainerResultNode::SortComparison_TitleGreater(
1002
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1003
return -SortComparison_TitleLess(a, b, closure);
1004
}
1005
1006
/**
1007
* Equal times will be very unusual, but it is important that there is some
1008
* deterministic ordering of the results so they don't move around.
1009
*/
1010
int32_t nsNavHistoryContainerResultNode::SortComparison_DateLess(
1011
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1012
int32_t value = ComparePRTime(a->mTime, b->mTime);
1013
if (value == 0) {
1014
value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1015
NS_ConvertUTF8toUTF16(b->mTitle));
1016
if (value == 0)
1017
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1018
closure);
1019
}
1020
return value;
1021
}
1022
int32_t nsNavHistoryContainerResultNode::SortComparison_DateGreater(
1023
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1024
return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b,
1025
closure);
1026
}
1027
1028
int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
1029
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1030
int32_t value = ComparePRTime(a->mDateAdded, b->mDateAdded);
1031
if (value == 0) {
1032
value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1033
NS_ConvertUTF8toUTF16(b->mTitle));
1034
if (value == 0)
1035
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1036
closure);
1037
}
1038
return value;
1039
}
1040
int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
1041
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1042
return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
1043
a, b, closure);
1044
}
1045
1046
int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
1047
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1048
int32_t value = ComparePRTime(a->mLastModified, b->mLastModified);
1049
if (value == 0) {
1050
value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1051
NS_ConvertUTF8toUTF16(b->mTitle));
1052
if (value == 0)
1053
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1054
closure);
1055
}
1056
return value;
1057
}
1058
int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
1059
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1060
return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
1061
a, b, closure);
1062
}
1063
1064
/**
1065
* Certain types of parent nodes are treated specially because URIs are not
1066
* valid (like days or hosts).
1067
*/
1068
int32_t nsNavHistoryContainerResultNode::SortComparison_URILess(
1069
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1070
int32_t value;
1071
if (a->IsURI() && b->IsURI()) {
1072
// normal URI or visit
1073
value = a->mURI.Compare(b->mURI.get());
1074
} else if (a->IsContainer() && !b->IsContainer()) {
1075
// Containers appear before entries with a uri.
1076
return -1;
1077
} else if (b->IsContainer() && !a->IsContainer()) {
1078
return 1;
1079
} else {
1080
// For everything else, use title sorting.
1081
value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
1082
NS_ConvertUTF8toUTF16(b->mTitle));
1083
}
1084
1085
if (value == 0) {
1086
value = ComparePRTime(a->mTime, b->mTime);
1087
if (value == 0)
1088
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1089
closure);
1090
}
1091
return value;
1092
}
1093
int32_t nsNavHistoryContainerResultNode::SortComparison_URIGreater(
1094
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1095
return -SortComparison_URILess(a, b, closure);
1096
}
1097
1098
/**
1099
* Fall back on dates for conflict resolution
1100
*/
1101
int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
1102
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1103
int32_t value = CompareIntegers(a->mAccessCount, b->mAccessCount);
1104
if (value == 0) {
1105
value = ComparePRTime(a->mTime, b->mTime);
1106
if (value == 0)
1107
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1108
closure);
1109
}
1110
return value;
1111
}
1112
int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountGreater(
1113
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1114
return -nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
1115
a, b, closure);
1116
}
1117
1118
int32_t nsNavHistoryContainerResultNode::SortComparison_TagsLess(
1119
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1120
int32_t value = 0;
1121
nsAutoString aTags, bTags;
1122
1123
nsresult rv = a->GetTags(aTags);
1124
NS_ENSURE_SUCCESS(rv, 0);
1125
1126
rv = b->GetTags(bTags);
1127
NS_ENSURE_SUCCESS(rv, 0);
1128
1129
value = SortComparison_StringLess(aTags, bTags);
1130
1131
// fall back to title sorting
1132
if (value == 0) value = SortComparison_TitleLess(a, b, closure);
1133
1134
return value;
1135
}
1136
1137
int32_t nsNavHistoryContainerResultNode::SortComparison_TagsGreater(
1138
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1139
return -SortComparison_TagsLess(a, b, closure);
1140
}
1141
1142
/**
1143
* Fall back on date and bookmarked status, for conflict resolution.
1144
*/
1145
int32_t nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(
1146
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1147
int32_t value = CompareIntegers(a->mFrecency, b->mFrecency);
1148
if (value == 0) {
1149
value = ComparePRTime(a->mTime, b->mTime);
1150
if (value == 0) {
1151
value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b,
1152
closure);
1153
}
1154
}
1155
return value;
1156
}
1157
int32_t nsNavHistoryContainerResultNode::SortComparison_FrecencyGreater(
1158
nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure) {
1159
return -nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(a, b,
1160
closure);
1161
}
1162
1163
/**
1164
* Searches this folder for a node with the given URI. Returns null if not
1165
* found.
1166
*
1167
* @note Does not addref the node!
1168
*/
1169
nsNavHistoryResultNode* nsNavHistoryContainerResultNode::FindChildURI(
1170
const nsACString& aSpec, uint32_t* aNodeIndex) {
1171
for (int32_t i = 0; i < mChildren.Count(); ++i) {
1172
if (mChildren[i]->IsURI()) {
1173
if (aSpec.Equals(mChildren[i]->mURI)) {
1174
*aNodeIndex = i;
1175
return mChildren[i];
1176
}
1177
}
1178
}
1179
return nullptr;
1180
}
1181
1182
/**
1183
* Searches this folder for a node with the given guid/target-folder-guid.
1184
*
1185
* @return the node if found, null otherwise.
1186
* @note Does not addref the node!
1187
*/
1188
nsNavHistoryResultNode* nsNavHistoryContainerResultNode::FindChildByGuid(
1189
const nsACString& guid, int32_t* nodeIndex) {
1190
*nodeIndex = -1;
1191
for (int32_t i = 0; i < mChildren.Count(); ++i) {
1192
if (mChildren[i]->mBookmarkGuid == guid ||
1193
mChildren[i]->mPageGuid == guid ||
1194
(mChildren[i]->IsFolder() &&
1195
mChildren[i]->GetAsFolder()->mTargetFolderGuid == guid)) {
1196
*nodeIndex = i;
1197
return mChildren[i];
1198
}
1199
}
1200
return nullptr;
1201
}
1202
1203
/**
1204
* This does the work of adding a child to the container. The child can be
1205
* either a container or or a single item that may even be collapsed with the
1206
* adjacent ones.
1207
*/
1208
nsresult nsNavHistoryContainerResultNode::InsertChildAt(
1209
nsNavHistoryResultNode* aNode, int32_t aIndex) {
1210
nsNavHistoryResult* result = GetResult();
1211
NS_ENSURE_STATE(result);
1212
1213
SetAsParentOfNode(aNode);
1214
1215
if (!mChildren.InsertObjectAt(aNode, aIndex)) return NS_ERROR_OUT_OF_MEMORY;
1216
1217
// Update our stats and notify the result's observers.
1218
uint32_t oldAccessCount = mAccessCount;
1219
PRTime oldTime = mTime;
1220
1221
mAccessCount += aNode->mAccessCount;
1222
if (mTime < aNode->mTime) mTime = aNode->mTime;
1223
if (!mParent || mParent->AreChildrenVisible()) {
1224
NOTIFY_RESULT_OBSERVERS(
1225
result, NodeHistoryDetailsChanged(TO_ICONTAINER(this), oldTime,
1226
oldAccessCount));
1227
}
1228
1229
nsresult rv = ReverseUpdateStats(aNode->mAccessCount);
1230
NS_ENSURE_SUCCESS(rv, rv);
1231
1232
// Update tree if we are visible. Note that we could be here and not
1233
// expanded, like when there is a bookmark folder being updated because its
1234
// parent is visible.
1235
if (AreChildrenVisible())
1236
NOTIFY_RESULT_OBSERVERS(result, NodeInserted(this, aNode, aIndex));
1237
1238
return NS_OK;
1239
}
1240
1241
/**
1242
* This locates the proper place for insertion according to the current sort
1243
* and calls InsertChildAt
1244
*/
1245
nsresult nsNavHistoryContainerResultNode::InsertSortedChild(
1246
nsNavHistoryResultNode* aNode, bool aIgnoreDuplicates) {
1247
if (mChildren.Count() == 0) return InsertChildAt(aNode, 0);
1248
1249
SortComparator comparator = GetSortingComparator(GetSortType());
1250
if (comparator) {
1251
// When inserting a new node, it must have proper statistics because we use
1252
// them to find the correct insertion point. The insert function will then
1253
// recompute these statistics and fill in the proper parents and hierarchy
1254
// level. Doing this twice shouldn't be a large performance penalty because
1255
// when we are inserting new containers, they typically contain only one
1256
// item (because we've browsed a new page).
1257
if (aNode->IsContainer()) {
1258
// need to update all the new item's children
1259
nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
1260
container->mResult = mResult;
1261
container->FillStats();
1262
}
1263
1264
bool itemExists;
1265
uint32_t position = FindInsertionPoint(aNode, comparator, &itemExists);
1266
if (aIgnoreDuplicates && itemExists) return NS_OK;
1267
1268
return InsertChildAt(aNode, position);
1269
}
1270
return InsertChildAt(aNode, mChildren.Count());
1271
}
1272
1273
/**
1274
* This checks if the item at aIndex is located correctly given the sorting
1275
* move. If it's not, the item is moved, and the result's observers are
1276
* notified.
1277
*
1278
* @return true if the item position has been changed, false otherwise.
1279
*/
1280
bool nsNavHistoryContainerResultNode::EnsureItemPosition(uint32_t aIndex) {
1281
NS_ASSERTION(aIndex < (uint32_t)mChildren.Count(), "Invalid index");
1282
if (aIndex >= (uint32_t)mChildren.Count()) return false;
1283
1284
SortComparator comparator = GetSortingComparator(GetSortType());
1285
if (!comparator) return false;
1286
1287
if (!DoesChildNeedResorting(aIndex, comparator)) return false;
1288
1289
RefPtr<nsNavHistoryResultNode> node(mChildren[aIndex]);
1290
mChildren.RemoveObjectAt(aIndex);
1291
1292
uint32_t newIndex = FindInsertionPoint(node, comparator, nullptr);
1293
mChildren.InsertObjectAt(node.get(), newIndex);
1294
1295
if (AreChildrenVisible()) {
1296
nsNavHistoryResult* result = GetResult();
1297
NOTIFY_RESULT_OBSERVERS_RET(
1298
result, NodeMoved(node, this, aIndex, this, newIndex), false);
1299
}
1300
1301
return true;
1302
}
1303
1304
/**
1305
* This does all the work of removing a child from this container, including
1306
* updating the tree if necessary. Note that we do not need to be open for
1307
* this to work.
1308
*/
1309
nsresult nsNavHistoryContainerResultNode::RemoveChildAt(int32_t aIndex) {
1310
NS_ASSERTION(aIndex >= 0 && aIndex < mChildren.Count(), "Invalid index");
1311
1312
// Hold an owning reference to keep from expiring while we work with it.
1313
RefPtr<nsNavHistoryResultNode> oldNode = mChildren[aIndex];
1314
1315
// Update stats.
1316
// XXX This assertion does not reliably pass -- investigate!! (bug 1049797)
1317
// MOZ_ASSERT(mAccessCount >= mChildren[aIndex]->mAccessCount,
1318
// "Invalid access count while updating!");
1319
uint32_t oldAccessCount = mAccessCount;
1320
mAccessCount -= mChildren[aIndex]->mAccessCount;
1321
1322
// Remove it from our list and notify the result's observers.
1323
mChildren.RemoveObjectAt(aIndex);
1324
if (AreChildrenVisible()) {
1325
nsNavHistoryResult* result = GetResult();
1326
NOTIFY_RESULT_OBSERVERS(result, NodeRemoved(this, oldNode, aIndex));
1327
}
1328
1329
nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
1330
NS_ENSURE_SUCCESS(rv, rv);
1331
oldNode->OnRemoving();
1332
return NS_OK;
1333
}
1334
1335
/**
1336
* Searches for matches for the given URI. If aOnlyOne is set, it will
1337
* terminate as soon as it finds a single match. This would be used when there
1338
* are URI results so there will only ever be one copy of any URI.
1339
*
1340
* When aOnlyOne is false, it will check all elements. This is for visit
1341
* style results that may have multiple copies of any given URI.
1342
*/
1343
void nsNavHistoryContainerResultNode::RecursiveFindURIs(
1344
bool aOnlyOne, nsNavHistoryContainerResultNode* aContainer,
1345
const nsCString& aSpec, nsCOMArray<nsNavHistoryResultNode>* aMatches) {
1346
for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
1347
uint32_t type;
1348
aContainer->mChildren[child]->GetType(&type);
1349
if (nsNavHistoryResultNode::IsTypeURI(type)) {
1350
// compare URIs
1351
nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
1352
if (uriNode->mURI.Equals(aSpec)) {
1353
// found
1354
aMatches->AppendObject(uriNode);
1355
if (aOnlyOne) return;
1356
}
1357
}
1358
}
1359
}
1360
1361
/**
1362
* If aUpdateSort is true, we will also update the sorting of this item.
1363
* Normally you want this to be true, but it can be false if the thing you are
1364
* changing can not affect sorting (like favicons).
1365
*
1366
* You should NOT change any child lists as part of the callback function.
1367
*/
1368
bool nsNavHistoryContainerResultNode::UpdateURIs(
1369
bool aRecursive, bool aOnlyOne, bool aUpdateSort, const nsCString& aSpec,
1370
nsresult (*aCallback)(nsNavHistoryResultNode*, const void*,
1371
const nsNavHistoryResult*),
1372
const void* aClosure) {
1373
const nsNavHistoryResult* result = GetResult();
1374
if (!result) {
1375
MOZ_ASSERT(false, "Should have a result");
1376
return false;
1377
}
1378
1379
// this needs to be owning since sometimes we remove and re-insert nodes
1380
// in their parents and we don't want them to go away.
1381
nsCOMArray<nsNavHistoryResultNode> matches;
1382
1383
if (aRecursive) {
1384
RecursiveFindURIs(aOnlyOne, this, aSpec, &matches);
1385
} else if (aOnlyOne) {
1386
uint32_t nodeIndex;
1387
nsNavHistoryResultNode* node = FindChildURI(aSpec, &nodeIndex);
1388
if (node) matches.AppendObject(node);
1389
} else {
1390
MOZ_ASSERT(
1391
false,
1392
"UpdateURIs does not handle nonrecursive updates of multiple items.");
1393
// this case easy to add if you need it, just find all the matching URIs
1394
// at this level. However, this isn't currently used. History uses
1395
// recursive, Bookmarks uses one level and knows that the match is unique.
1396
return false;
1397
}
1398
1399
if (matches.Count() == 0) return false;
1400
1401
// PERFORMANCE: This updates each container for each child in it that
1402
// changes. In some cases, many elements have changed inside the same
1403
// container. It would be better to compose a list of containers, and
1404
// update each one only once for all the items that have changed in it.
1405
for (int32_t i = 0; i < matches.Count(); ++i) {
1406
nsNavHistoryResultNode* node = matches[i];
1407
nsNavHistoryContainerResultNode* parent = node->mParent;
1408
if (!parent) {
1409
MOZ_ASSERT(false, "All URI nodes being updated must have parents");
1410
continue;
1411
}
1412
1413
uint32_t oldAccessCount = node->mAccessCount;
1414
PRTime oldTime = node->mTime;
1415
uint32_t parentOldAccessCount = parent->mAccessCount;
1416
PRTime parentOldTime = parent->mTime;
1417
1418
aCallback(node, aClosure, result);
1419
1420
if (oldAccessCount != node->mAccessCount || oldTime != node->mTime) {
1421
parent->mAccessCount += node->mAccessCount - oldAccessCount;
1422
if (node->mTime > parent->mTime) parent->mTime = node->mTime;
1423
if (parent->AreChildrenVisible()) {
1424
NOTIFY_RESULT_OBSERVERS_RET(
1425
result,
1426
NodeHistoryDetailsChanged(TO_ICONTAINER(parent), parentOldTime,
1427
parentOldAccessCount),
1428
true);
1429
}
1430
DebugOnly<nsresult> rv =
1431
parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount);
1432
MOZ_ASSERT(NS_SUCCEEDED(rv), "should be able to ReverseUpdateStats");
1433
}
1434
1435
if (aUpdateSort) {
1436
int32_t childIndex = parent->FindChild(node);
1437
MOZ_ASSERT(childIndex >= 0,
1438
"Could not find child we just got a reference to");
1439
if (childIndex >= 0) parent->EnsureItemPosition(childIndex);
1440
}
1441
}
1442
1443
return true;
1444
}
1445
1446
/**
1447
* This is used to update the titles in the tree. This is called from both
1448
* query and bookmark folder containers to update the tree. Bookmark folders
1449
* should be sure to set recursive to false, since child folders will have
1450
* their own callbacks registered.
1451
*/
1452
static nsresult setTitleCallback(nsNavHistoryResultNode* aNode,
1453
const void* aClosure,
1454
const nsNavHistoryResult* aResult) {
1455
const nsACString* newTitle = static_cast<const nsACString*>(aClosure);
1456
aNode->mTitle = *newTitle;
1457
1458
if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
1459
NOTIFY_RESULT_OBSERVERS(aResult, NodeTitleChanged(aNode, *newTitle));
1460
1461
return NS_OK;
1462
}
1463
nsresult nsNavHistoryContainerResultNode::ChangeTitles(
1464
nsIURI* aURI, const nsACString& aNewTitle, bool aRecursive, bool aOnlyOne) {
1465
// uri string
1466
nsAutoCString uriString;
1467
nsresult rv = aURI->GetSpec(uriString);
1468
NS_ENSURE_SUCCESS(rv, rv);
1469
1470
// The recursive function will update the result's tree nodes, but only if we
1471
// give it a non-null pointer. So if there isn't a tree, just pass nullptr
1472
// so it doesn't bother trying to call the result.
1473
nsNavHistoryResult* result = GetResult();
1474
NS_ENSURE_STATE(result);
1475
1476
uint16_t sortType = GetSortType();
1477
bool updateSorting =
1478
(sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
1479
sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING);
1480
1481
UpdateURIs(aRecursive, aOnlyOne, updateSorting, uriString, setTitleCallback,
1482
static_cast<const void*>(&aNewTitle));
1483
1484
return NS_OK;
1485
}
1486
1487
/**
1488
* Complex containers (folders and queries) will override this. Here, we
1489
* handle the case of simple containers (like host groups) where the children
1490
* are always stored.
1491
*/
1492
NS_IMETHODIMP
1493
nsNavHistoryContainerResultNode::GetHasChildren(bool* aHasChildren) {
1494
*aHasChildren = (mChildren.Count() > 0);
1495
return NS_OK;
1496
}
1497
1498
/**
1499
* @throws if this node is closed.
1500
*/
1501
NS_IMETHODIMP
1502
nsNavHistoryContainerResultNode::GetChildCount(uint32_t* aChildCount) {
1503
if (!mExpanded) return NS_ERROR_NOT_AVAILABLE;
1504
*aChildCount = mChildren.Count();
1505
return NS_OK;
1506
}
1507
1508
NS_IMETHODIMP
1509
nsNavHistoryContainerResultNode::GetChild(uint32_t aIndex,
1510
nsINavHistoryResultNode** _child) {
1511
if (!mExpanded) return NS_ERROR_NOT_AVAILABLE;
1512
if (aIndex >= uint32_t(mChildren.Count())) return NS_ERROR_INVALID_ARG;
1513
nsCOMPtr<nsINavHistoryResultNode> child = mChildren[aIndex];
1514
child.forget(_child);
1515
return NS_OK;
1516
}
1517
1518
NS_IMETHODIMP
1519
nsNavHistoryContainerResultNode::GetChildIndex(nsINavHistoryResultNode* aNode,
1520
uint32_t* _retval) {
1521
if (!mExpanded) return NS_ERROR_NOT_AVAILABLE;
1522
1523
int32_t nodeIndex = FindChild(static_cast<nsNavHistoryResultNode*>(aNode));
1524
if (nodeIndex == -1) return NS_ERROR_INVALID_ARG;
1525
1526
*_retval = nodeIndex;
1527
return NS_OK;
1528
}
1529
1530
/**
1531
* HOW QUERY UPDATING WORKS
1532
*
1533
* Queries are different than bookmark folders in that we can not always do
1534
* dynamic updates (easily) and updates are more expensive. Therefore, we do
1535
* NOT query if we are not open and want to see if we have any children (for
1536
* drawing a twisty) and always assume we will.
1537
*
1538
* When the container is opened, we execute the query and register the
1539
* listeners. Like bookmark folders, we stay registered even when closed, and
1540
* clear ourselves as soon as a message comes in. This lets us respond quickly
1541
* if the user closes and reopens the container.
1542
*
1543
* We try to handle the most common notifications for the most common query
1544
* types dynamically, that is, figuring out what should happen in response to
1545
* a message without doing a requery. For complex changes or complex queries,
1546
* we give up and requery.
1547
*/
1548
NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
1549
nsNavHistoryContainerResultNode,
1550
nsINavHistoryQueryResultNode)
1551
1552
nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
1553
const nsACString& aTitle, PRTime aTime, const nsACString& aQueryURI,
1554
const RefPtr<nsNavHistoryQuery>& aQuery,
1555
const RefPtr<nsNavHistoryQueryOptions>& aOptions)
1556
: nsNavHistoryContainerResultNode(aQueryURI, aTitle, aTime,
1557
nsNavHistoryResultNode::RESULT_TYPE_QUERY,
1558
aOptions),
1559
mQuery(aQuery),
1560
mLiveUpdate(getUpdateRequirements(aQuery, aOptions, &mHasSearchTerms)),
1561
mContentsValid(false),
1562
mBatchChanges(0),
1563
mTransitions(aQuery->Transitions()) {}
1564
1565
nsNavHistoryQueryResultNode::~nsNavHistoryQueryResultNode() {
1566
// Remove this node from result's observers. We don't need to be notified
1567
// anymore.
1568
if (mResult && mResult->mAllBookmarksObservers.Contains(this))
1569
mResult->RemoveAllBookmarksObserver(this);
1570
if (mResult && mResult->mHistoryObservers.Contains(this))
1571
mResult->RemoveHistoryObserver(this);
1572
if (mResult && mResult->mMobilePrefObservers.Contains(this))
1573
mResult->RemoveMobilePrefsObserver(this);
1574
}
1575
1576
/**
1577
* Whoever made us may want non-expanding queries. However, we always expand
1578
* when we are the root node, or else asking for non-expanding queries would be
1579
* useless. A query node is not expandable if excludeItems is set or if
1580
* expandQueries is unset.
1581
*/
1582
bool nsNavHistoryQueryResultNode::CanExpand() {
1583
// The root node and containersQueries can always expand;
1584
if ((mResult && mResult->mRootNode == this) || IsContainersQuery()) {
1585
return true;
1586
}
1587
1588
if (mOptions->ExcludeItems()) {
1589
return false;
1590
}
1591
if (mOptions->ExpandQueries()) {
1592
return true;
1593
}
1594
1595
return false;
1596
}
1597
1598
/**
1599
* Some query with a particular result type can contain other queries. They
1600
* must be always expandable
1601
*/
1602
bool nsNavHistoryQueryResultNode::IsContainersQuery() {
1603
uint16_t resultType = Options()->ResultType();
1604
return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
1605
resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
1606
resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT ||
1607
resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY ||
1608
resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
1609
resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY;
1610
}
1611
1612
/**
1613
* Here we do not want to call ContainerResultNode::OnRemoving since our own
1614
* ClearChildren will do the same thing and more (unregister the observers).
1615
* The base ResultNode::OnRemoving will clear some regular node stats, so it
1616
* is OK.
1617
*/
1618
void nsNavHistoryQueryResultNode::OnRemoving() {
1619
nsNavHistoryResultNode::OnRemoving();
1620
ClearChildren(true);
1621
mResult = nullptr;
1622
}
1623
1624
/**
1625
* Marks the container as open, rebuilding results if they are invalid. We
1626
* may still have valid results if the container was previously open and
1627
* nothing happened since closing it.
1628
*
1629
* We do not handle CloseContainer specially. The default one just marks the
1630
* container as closed, but doesn't actually mark the results as invalid.
1631
* The results will be invalidated by the next history or bookmark
1632
* notification that comes in. This means if you open and close the item
1633
* without anything happening in between, it will be fast (this actually
1634
* happens when results are used as menus).
1635
*/
1636
nsresult nsNavHistoryQueryResultNode::OpenContainer() {
1637
NS_ASSERTION(!mExpanded, "Container must be closed to open it");
1638
mExpanded = true;
1639
1640
nsresult rv;
1641
1642
if (!CanExpand()) return NS_OK;
1643
if (!mContentsValid) {
1644
rv = FillChildren();
1645
NS_ENSURE_SUCCESS(rv, rv);
1646
}
1647
1648
rv = NotifyOnStateChange(STATE_CLOSED);
1649
NS_ENSURE_SUCCESS(rv, rv);
1650
1651
return NS_OK;
1652
}
1653
1654
/**
1655
* When we have valid results we can always give an exact answer. When we
1656
* don't we just assume we'll have results, since actually doing the query
1657
* might be hard. This is used to draw twisties on the tree, so precise results
1658
* don't matter.
1659
*/
1660
NS_IMETHODIMP
1661
nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren) {
1662
*aHasChildren = false;
1663
1664
if (!CanExpand()) {
1665
return NS_OK;
1666
}
1667
1668
uint16_t resultType = mOptions->ResultType();
1669
1670
// Tags are always populated, otherwise they are removed.
1671
if (mQuery->Tags().Length() == 1 && mParent &&
1672
mParent->mOptions->ResultType() ==
1673
nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT) {
1674
*aHasChildren = true;
1675
return NS_OK;
1676
}
1677
1678
// AllBookmarks and the left pane folder also always have children.
1679
if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
1680
resultType == nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
1681
*aHasChildren = true;
1682
return NS_OK;
1683
}
1684
1685
// For history containers query we must check if we have any history
1686
if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
1687
resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
1688
resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
1689
nsNavHistory* history = nsNavHistory::GetHistoryService();
1690
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1691
*aHasChildren = history->hasHistoryEntries();
1692
return NS_OK;
1693
}
1694
1695
// TODO (Bug 1477934): We don't have a good synchronous way to fetch whether
1696
// we have tags or not, to properly reply to the hasChildren request on the
1697
// tags root. Potentially we could pass this information when we create the
1698
// container.
1699
1700
// If the container is open and populated, this is trivial.
1701
if (mContentsValid) {
1702
*aHasChildren = (mChildren.Count() > 0);
1703
return NS_OK;
1704
}
1705
1706
// Fallback to assume we have children.
1707
*aHasChildren = true;
1708
return NS_OK;
1709
}
1710
1711
/**
1712
* This doesn't just return mURI because in the case of queries that may
1713
* be lazily constructed from the query objects.
1714
*/
1715
NS_IMETHODIMP
1716
nsNavHistoryQueryResultNode::GetUri(nsACString& aURI) {
1717
aURI = mURI;
1718
return NS_OK;
1719
}
1720
1721
NS_IMETHODIMP
1722
nsNavHistoryQueryResultNode::GetFolderItemId(int64_t* aItemId) {
1723
*aItemId = -1;
1724
return NS_OK;
1725
}
1726
1727
NS_IMETHODIMP
1728
nsNavHistoryQueryResultNode::GetTargetFolderGuid(nsACString& aGuid) {
1729
aGuid = EmptyCString();
1730
return NS_OK;
1731
}
1732
1733
NS_IMETHODIMP
1734
nsNavHistoryQueryResultNode::GetQuery(nsINavHistoryQuery** _query) {
1735
RefPtr<nsNavHistoryQuery> query = mQuery;
1736
query.forget(_query);
1737
return NS_OK;
1738
}
1739
1740
NS_IMETHODIMP
1741
nsNavHistoryQueryResultNode::GetQueryOptions(
1742
nsINavHistoryQueryOptions** _options) {
1743
MOZ_ASSERT(mOptions, "Options should be valid");