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