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