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