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