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