|
1 //* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include <stdio.h> |
|
7 |
|
8 #include "mozilla/DebugOnly.h" |
|
9 |
|
10 #include "nsNavHistory.h" |
|
11 |
|
12 #include "mozIPlacesAutoComplete.h" |
|
13 #include "nsNavBookmarks.h" |
|
14 #include "nsAnnotationService.h" |
|
15 #include "nsFaviconService.h" |
|
16 #include "nsPlacesMacros.h" |
|
17 #include "History.h" |
|
18 #include "Helpers.h" |
|
19 |
|
20 #include "nsTArray.h" |
|
21 #include "nsCollationCID.h" |
|
22 #include "nsILocaleService.h" |
|
23 #include "nsNetUtil.h" |
|
24 #include "nsPrintfCString.h" |
|
25 #include "nsPromiseFlatString.h" |
|
26 #include "nsString.h" |
|
27 #include "nsUnicharUtils.h" |
|
28 #include "prsystem.h" |
|
29 #include "prtime.h" |
|
30 #include "nsEscape.h" |
|
31 #include "nsIEffectiveTLDService.h" |
|
32 #include "nsIClassInfoImpl.h" |
|
33 #include "nsThreadUtils.h" |
|
34 #include "nsAppDirectoryServiceDefs.h" |
|
35 #include "nsMathUtils.h" |
|
36 #include "mozilla/storage.h" |
|
37 #include "mozilla/Preferences.h" |
|
38 #include <algorithm> |
|
39 |
|
40 #ifdef MOZ_XUL |
|
41 #include "nsIAutoCompleteInput.h" |
|
42 #include "nsIAutoCompletePopup.h" |
|
43 #endif |
|
44 |
|
45 using namespace mozilla; |
|
46 using namespace mozilla::places; |
|
47 |
|
48 // The maximum number of things that we will store in the recent events list |
|
49 // before calling ExpireNonrecentEvents. This number should be big enough so it |
|
50 // is very difficult to get that many unconsumed events (for example, typed but |
|
51 // never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start |
|
52 // checking each one for every page visit, which will be somewhat slower. |
|
53 #define RECENT_EVENT_QUEUE_MAX_LENGTH 128 |
|
54 |
|
55 // preference ID strings |
|
56 #define PREF_HISTORY_ENABLED "places.history.enabled" |
|
57 |
|
58 #define PREF_FREC_NUM_VISITS "places.frecency.numVisits" |
|
59 #define PREF_FREC_NUM_VISITS_DEF 10 |
|
60 #define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff" |
|
61 #define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4 |
|
62 #define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff" |
|
63 #define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14 |
|
64 #define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff" |
|
65 #define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31 |
|
66 #define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff" |
|
67 #define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90 |
|
68 #define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight" |
|
69 #define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100 |
|
70 #define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight" |
|
71 #define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70 |
|
72 #define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight" |
|
73 #define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50 |
|
74 #define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight" |
|
75 #define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30 |
|
76 #define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight" |
|
77 #define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10 |
|
78 #define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus" |
|
79 #define PREF_FREC_EMBED_VISIT_BONUS_DEF 0 |
|
80 #define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus" |
|
81 #define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0 |
|
82 #define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus" |
|
83 #define PREF_FREC_LINK_VISIT_BONUS_DEF 100 |
|
84 #define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus" |
|
85 #define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000 |
|
86 #define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus" |
|
87 #define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75 |
|
88 #define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus" |
|
89 #define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0 |
|
90 #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS "places.frecency.permRedirectVisitBonus" |
|
91 #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0 |
|
92 #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS "places.frecency.tempRedirectVisitBonus" |
|
93 #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0 |
|
94 #define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus" |
|
95 #define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0 |
|
96 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS "places.frecency.unvisitedBookmarkBonus" |
|
97 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140 |
|
98 #define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus" |
|
99 #define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200 |
|
100 |
|
101 // In order to avoid calling PR_now() too often we use a cached "now" value |
|
102 // for repeating stuff. These are milliseconds between "now" cache refreshes. |
|
103 #define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC) |
|
104 |
|
105 static const int64_t USECS_PER_DAY = (int64_t)PR_USEC_PER_SEC * 60 * 60 * 24; |
|
106 |
|
107 // character-set annotation |
|
108 #define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet") |
|
109 |
|
110 // These macros are used when splitting history by date. |
|
111 // These are the day containers and catch-all final container. |
|
112 #define HISTORY_ADDITIONAL_DATE_CONT_NUM 3 |
|
113 // We use a guess of the number of months considering all of them 30 days |
|
114 // long, but we split only the last 6 months. |
|
115 #define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \ |
|
116 (HISTORY_ADDITIONAL_DATE_CONT_NUM + \ |
|
117 std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit/30))) |
|
118 // Max number of containers, used to initialize the params hash. |
|
119 #define HISTORY_DATE_CONT_MAX 10 |
|
120 |
|
121 // Initial size of the embed visits cache. |
|
122 #define EMBED_VISITS_INITIAL_CACHE_SIZE 128 |
|
123 |
|
124 // Initial size of the recent events caches. |
|
125 #define RECENT_EVENTS_INITIAL_CACHE_SIZE 128 |
|
126 |
|
127 // Observed topics. |
|
128 #ifdef MOZ_XUL |
|
129 #define TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING "autocomplete-will-enter-text" |
|
130 #endif |
|
131 #define TOPIC_IDLE_DAILY "idle-daily" |
|
132 #define TOPIC_PREF_CHANGED "nsPref:changed" |
|
133 #define TOPIC_PROFILE_TEARDOWN "profile-change-teardown" |
|
134 #define TOPIC_PROFILE_CHANGE "profile-before-change" |
|
135 |
|
136 static const char* kObservedPrefs[] = { |
|
137 PREF_HISTORY_ENABLED |
|
138 , PREF_FREC_NUM_VISITS |
|
139 , PREF_FREC_FIRST_BUCKET_CUTOFF |
|
140 , PREF_FREC_SECOND_BUCKET_CUTOFF |
|
141 , PREF_FREC_THIRD_BUCKET_CUTOFF |
|
142 , PREF_FREC_FOURTH_BUCKET_CUTOFF |
|
143 , PREF_FREC_FIRST_BUCKET_WEIGHT |
|
144 , PREF_FREC_SECOND_BUCKET_WEIGHT |
|
145 , PREF_FREC_THIRD_BUCKET_WEIGHT |
|
146 , PREF_FREC_FOURTH_BUCKET_WEIGHT |
|
147 , PREF_FREC_DEFAULT_BUCKET_WEIGHT |
|
148 , PREF_FREC_EMBED_VISIT_BONUS |
|
149 , PREF_FREC_FRAMED_LINK_VISIT_BONUS |
|
150 , PREF_FREC_LINK_VISIT_BONUS |
|
151 , PREF_FREC_TYPED_VISIT_BONUS |
|
152 , PREF_FREC_BOOKMARK_VISIT_BONUS |
|
153 , PREF_FREC_DOWNLOAD_VISIT_BONUS |
|
154 , PREF_FREC_PERM_REDIRECT_VISIT_BONUS |
|
155 , PREF_FREC_TEMP_REDIRECT_VISIT_BONUS |
|
156 , PREF_FREC_DEFAULT_VISIT_BONUS |
|
157 , PREF_FREC_UNVISITED_BOOKMARK_BONUS |
|
158 , PREF_FREC_UNVISITED_TYPED_BONUS |
|
159 , nullptr |
|
160 }; |
|
161 |
|
162 NS_IMPL_ADDREF(nsNavHistory) |
|
163 NS_IMPL_RELEASE(nsNavHistory) |
|
164 |
|
165 NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON, |
|
166 NS_NAVHISTORYSERVICE_CID) |
|
167 NS_INTERFACE_MAP_BEGIN(nsNavHistory) |
|
168 NS_INTERFACE_MAP_ENTRY(nsINavHistoryService) |
|
169 NS_INTERFACE_MAP_ENTRY(nsIBrowserHistory) |
|
170 NS_INTERFACE_MAP_ENTRY(nsIObserver) |
|
171 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
|
172 NS_INTERFACE_MAP_ENTRY(nsPIPlacesDatabase) |
|
173 NS_INTERFACE_MAP_ENTRY(nsPIPlacesHistoryListenersNotifier) |
|
174 NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant) |
|
175 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService) |
|
176 NS_IMPL_QUERY_CLASSINFO(nsNavHistory) |
|
177 NS_INTERFACE_MAP_END |
|
178 |
|
179 // We don't care about flattening everything |
|
180 NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, |
|
181 nsINavHistoryService, |
|
182 nsIBrowserHistory) |
|
183 |
|
184 namespace { |
|
185 |
|
186 static int64_t GetSimpleBookmarksQueryFolder( |
|
187 const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
188 nsNavHistoryQueryOptions* aOptions); |
|
189 static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
190 nsTArray<nsTArray<nsString>*>* aTerms); |
|
191 |
|
192 void GetTagsSqlFragment(int64_t aTagsFolder, |
|
193 const nsACString& aRelation, |
|
194 bool aHasSearchTerms, |
|
195 nsACString& _sqlFragment) { |
|
196 if (!aHasSearchTerms) |
|
197 _sqlFragment.AssignLiteral("null"); |
|
198 else { |
|
199 // This subquery DOES NOT order tags for performance reasons. |
|
200 _sqlFragment.Assign(NS_LITERAL_CSTRING( |
|
201 "(SELECT GROUP_CONCAT(t_t.title, ',') " |
|
202 "FROM moz_bookmarks b_t " |
|
203 "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent " |
|
204 "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" " |
|
205 "AND t_t.parent = ") + |
|
206 nsPrintfCString("%lld", aTagsFolder) + NS_LITERAL_CSTRING(" " |
|
207 ")")); |
|
208 } |
|
209 |
|
210 _sqlFragment.AppendLiteral(" AS tags "); |
|
211 } |
|
212 |
|
213 /** |
|
214 * This class sets begin/end of batch updates to correspond to C++ scopes so |
|
215 * we can be sure end always gets called. |
|
216 */ |
|
217 class UpdateBatchScoper |
|
218 { |
|
219 public: |
|
220 UpdateBatchScoper(nsNavHistory& aNavHistory) : mNavHistory(aNavHistory) |
|
221 { |
|
222 mNavHistory.BeginUpdateBatch(); |
|
223 } |
|
224 ~UpdateBatchScoper() |
|
225 { |
|
226 mNavHistory.EndUpdateBatch(); |
|
227 } |
|
228 protected: |
|
229 nsNavHistory& mNavHistory; |
|
230 }; |
|
231 |
|
232 } // anonymouse namespace |
|
233 |
|
234 |
|
235 // Queries rows indexes to bind or get values, if adding a new one, be sure to |
|
236 // update nsNavBookmarks statements and its kGetChildrenIndex_* constants |
|
237 const int32_t nsNavHistory::kGetInfoIndex_PageID = 0; |
|
238 const int32_t nsNavHistory::kGetInfoIndex_URL = 1; |
|
239 const int32_t nsNavHistory::kGetInfoIndex_Title = 2; |
|
240 const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3; |
|
241 const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4; |
|
242 const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5; |
|
243 const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6; |
|
244 const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7; |
|
245 const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8; |
|
246 const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9; |
|
247 const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10; |
|
248 const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11; |
|
249 const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12; |
|
250 const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13; |
|
251 const int32_t nsNavHistory::kGetInfoIndex_Guid = 14; |
|
252 |
|
253 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService) |
|
254 |
|
255 |
|
256 nsNavHistory::nsNavHistory() |
|
257 : mBatchLevel(0) |
|
258 , mBatchDBTransaction(nullptr) |
|
259 , mCachedNow(0) |
|
260 , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
|
261 , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
|
262 , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_SIZE) |
|
263 , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_SIZE) |
|
264 , mHistoryEnabled(true) |
|
265 , mNumVisitsForFrecency(10) |
|
266 , mTagsFolder(-1) |
|
267 , mDaysOfHistory(-1) |
|
268 , mLastCachedStartOfDay(INT64_MAX) |
|
269 , mLastCachedEndOfDay(0) |
|
270 , mCanNotify(true) |
|
271 , mCacheObservers("history-observers") |
|
272 { |
|
273 NS_ASSERTION(!gHistoryService, |
|
274 "Attempting to create two instances of the service!"); |
|
275 gHistoryService = this; |
|
276 } |
|
277 |
|
278 |
|
279 nsNavHistory::~nsNavHistory() |
|
280 { |
|
281 // remove the static reference to the service. Check to make sure its us |
|
282 // in case somebody creates an extra instance of the service. |
|
283 NS_ASSERTION(gHistoryService == this, |
|
284 "Deleting a non-singleton instance of the service"); |
|
285 if (gHistoryService == this) |
|
286 gHistoryService = nullptr; |
|
287 } |
|
288 |
|
289 |
|
290 nsresult |
|
291 nsNavHistory::Init() |
|
292 { |
|
293 LoadPrefs(); |
|
294 |
|
295 mDB = Database::GetDatabase(); |
|
296 NS_ENSURE_STATE(mDB); |
|
297 |
|
298 /***************************************************************************** |
|
299 *** IMPORTANT NOTICE! |
|
300 *** |
|
301 *** Nothing after these add observer calls should return anything but NS_OK. |
|
302 *** If a failure code is returned, this nsNavHistory object will be held onto |
|
303 *** by the observer service and the preference service. |
|
304 ****************************************************************************/ |
|
305 |
|
306 // Observe preferences changes. |
|
307 Preferences::AddWeakObservers(this, kObservedPrefs); |
|
308 |
|
309 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); |
|
310 if (obsSvc) { |
|
311 (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true); |
|
312 (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true); |
|
313 #ifdef MOZ_XUL |
|
314 (void)obsSvc->AddObserver(this, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING, true); |
|
315 #endif |
|
316 } |
|
317 |
|
318 // Don't add code that can fail here! Do it up above, before we add our |
|
319 // observers. |
|
320 |
|
321 return NS_OK; |
|
322 } |
|
323 |
|
324 NS_IMETHODIMP |
|
325 nsNavHistory::GetDatabaseStatus(uint16_t *aDatabaseStatus) |
|
326 { |
|
327 NS_ENSURE_ARG_POINTER(aDatabaseStatus); |
|
328 *aDatabaseStatus = mDB->GetDatabaseStatus(); |
|
329 return NS_OK; |
|
330 } |
|
331 |
|
332 uint32_t |
|
333 nsNavHistory::GetRecentFlags(nsIURI *aURI) |
|
334 { |
|
335 uint32_t result = 0; |
|
336 nsAutoCString spec; |
|
337 nsresult rv = aURI->GetSpec(spec); |
|
338 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to get aURI's spec"); |
|
339 |
|
340 if (NS_SUCCEEDED(rv)) { |
|
341 if (CheckIsRecentEvent(&mRecentTyped, spec)) |
|
342 result |= RECENT_TYPED; |
|
343 if (CheckIsRecentEvent(&mRecentLink, spec)) |
|
344 result |= RECENT_ACTIVATED; |
|
345 if (CheckIsRecentEvent(&mRecentBookmark, spec)) |
|
346 result |= RECENT_BOOKMARKED; |
|
347 } |
|
348 |
|
349 return result; |
|
350 } |
|
351 |
|
352 nsresult |
|
353 nsNavHistory::GetIdForPage(nsIURI* aURI, |
|
354 int64_t* _pageId, |
|
355 nsCString& _GUID) |
|
356 { |
|
357 *_pageId = 0; |
|
358 |
|
359 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
|
360 "SELECT id, url, title, rev_host, visit_count, guid " |
|
361 "FROM moz_places " |
|
362 "WHERE url = :page_url " |
|
363 ); |
|
364 NS_ENSURE_STATE(stmt); |
|
365 mozStorageStatementScoper scoper(stmt); |
|
366 |
|
367 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
|
368 NS_ENSURE_SUCCESS(rv, rv); |
|
369 |
|
370 bool hasEntry = false; |
|
371 rv = stmt->ExecuteStep(&hasEntry); |
|
372 NS_ENSURE_SUCCESS(rv, rv); |
|
373 |
|
374 if (hasEntry) { |
|
375 rv = stmt->GetInt64(0, _pageId); |
|
376 NS_ENSURE_SUCCESS(rv, rv); |
|
377 rv = stmt->GetUTF8String(5, _GUID); |
|
378 NS_ENSURE_SUCCESS(rv, rv); |
|
379 } |
|
380 |
|
381 return NS_OK; |
|
382 } |
|
383 |
|
384 nsresult |
|
385 nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, |
|
386 int64_t* _pageId, |
|
387 nsCString& _GUID) |
|
388 { |
|
389 nsresult rv = GetIdForPage(aURI, _pageId, _GUID); |
|
390 NS_ENSURE_SUCCESS(rv, rv); |
|
391 |
|
392 if (*_pageId != 0) { |
|
393 return NS_OK; |
|
394 } |
|
395 |
|
396 // Create a new hidden, untyped and unvisited entry. |
|
397 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
|
398 "INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) " |
|
399 "VALUES (:page_url, :rev_host, :hidden, :frecency, GENERATE_GUID()) " |
|
400 ); |
|
401 NS_ENSURE_STATE(stmt); |
|
402 mozStorageStatementScoper scoper(stmt); |
|
403 |
|
404 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
|
405 NS_ENSURE_SUCCESS(rv, rv); |
|
406 // host (reversed with trailing period) |
|
407 nsAutoString revHost; |
|
408 rv = GetReversedHostname(aURI, revHost); |
|
409 // Not all URI types have hostnames, so this is optional. |
|
410 if (NS_SUCCEEDED(rv)) { |
|
411 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost); |
|
412 } else { |
|
413 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host")); |
|
414 } |
|
415 NS_ENSURE_SUCCESS(rv, rv); |
|
416 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1); |
|
417 NS_ENSURE_SUCCESS(rv, rv); |
|
418 nsAutoCString spec; |
|
419 rv = aURI->GetSpec(spec); |
|
420 NS_ENSURE_SUCCESS(rv, rv); |
|
421 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), |
|
422 IsQueryURI(spec) ? 0 : -1); |
|
423 NS_ENSURE_SUCCESS(rv, rv); |
|
424 |
|
425 rv = stmt->Execute(); |
|
426 NS_ENSURE_SUCCESS(rv, rv); |
|
427 |
|
428 { |
|
429 nsCOMPtr<mozIStorageStatement> getIdStmt = mDB->GetStatement( |
|
430 "SELECT id, guid FROM moz_places WHERE url = :page_url " |
|
431 ); |
|
432 NS_ENSURE_STATE(getIdStmt); |
|
433 mozStorageStatementScoper getIdScoper(getIdStmt); |
|
434 |
|
435 rv = URIBinder::Bind(getIdStmt, NS_LITERAL_CSTRING("page_url"), aURI); |
|
436 NS_ENSURE_SUCCESS(rv, rv); |
|
437 |
|
438 bool hasResult = false; |
|
439 rv = getIdStmt->ExecuteStep(&hasResult); |
|
440 NS_ENSURE_SUCCESS(rv, rv); |
|
441 NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?"); |
|
442 *_pageId = getIdStmt->AsInt64(0); |
|
443 rv = getIdStmt->GetUTF8String(1, _GUID); |
|
444 NS_ENSURE_SUCCESS(rv, rv); |
|
445 } |
|
446 |
|
447 return NS_OK; |
|
448 } |
|
449 |
|
450 |
|
451 void |
|
452 nsNavHistory::LoadPrefs() |
|
453 { |
|
454 // History preferences. |
|
455 mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true); |
|
456 |
|
457 // Frecency preferences. |
|
458 #define FRECENCY_PREF(_prop, _pref) \ |
|
459 _prop = Preferences::GetInt(_pref, _pref##_DEF) |
|
460 |
|
461 FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS); |
|
462 FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF); |
|
463 FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF); |
|
464 FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF); |
|
465 FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF); |
|
466 FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS); |
|
467 FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS); |
|
468 FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS); |
|
469 FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS); |
|
470 FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS); |
|
471 FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS); |
|
472 FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS); |
|
473 FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS); |
|
474 FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS); |
|
475 FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS); |
|
476 FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS); |
|
477 FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT); |
|
478 FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT); |
|
479 FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT); |
|
480 FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT); |
|
481 FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT); |
|
482 |
|
483 #undef FRECENCY_PREF |
|
484 } |
|
485 |
|
486 |
|
487 void |
|
488 nsNavHistory::NotifyOnVisit(nsIURI* aURI, |
|
489 int64_t aVisitID, |
|
490 PRTime aTime, |
|
491 int64_t referringVisitID, |
|
492 int32_t aTransitionType, |
|
493 const nsACString& aGUID, |
|
494 bool aHidden) |
|
495 { |
|
496 MOZ_ASSERT(!aGUID.IsEmpty()); |
|
497 // If there's no history, this visit will surely add a day. If the visit is |
|
498 // added before or after the last cached day, the day count may have changed. |
|
499 // Otherwise adding multiple visits in the same day should not invalidate |
|
500 // the cache. |
|
501 if (mDaysOfHistory == 0) { |
|
502 mDaysOfHistory = 1; |
|
503 } else if (aTime > mLastCachedEndOfDay || aTime < mLastCachedStartOfDay) { |
|
504 mDaysOfHistory = -1; |
|
505 } |
|
506 |
|
507 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
508 nsINavHistoryObserver, |
|
509 OnVisit(aURI, aVisitID, aTime, 0, |
|
510 referringVisitID, aTransitionType, aGUID, aHidden)); |
|
511 } |
|
512 |
|
513 void |
|
514 nsNavHistory::NotifyTitleChange(nsIURI* aURI, |
|
515 const nsString& aTitle, |
|
516 const nsACString& aGUID) |
|
517 { |
|
518 MOZ_ASSERT(!aGUID.IsEmpty()); |
|
519 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
520 nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID)); |
|
521 } |
|
522 |
|
523 void |
|
524 nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI, |
|
525 int32_t aNewFrecency, |
|
526 const nsACString& aGUID, |
|
527 bool aHidden, |
|
528 PRTime aLastVisitDate) |
|
529 { |
|
530 MOZ_ASSERT(!aGUID.IsEmpty()); |
|
531 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
532 nsINavHistoryObserver, |
|
533 OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, |
|
534 aLastVisitDate)); |
|
535 } |
|
536 |
|
537 void |
|
538 nsNavHistory::NotifyManyFrecenciesChanged() |
|
539 { |
|
540 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
541 nsINavHistoryObserver, |
|
542 OnManyFrecenciesChanged()); |
|
543 } |
|
544 |
|
545 namespace { |
|
546 |
|
547 class FrecencyNotification : public nsRunnable |
|
548 { |
|
549 public: |
|
550 FrecencyNotification(const nsACString& aSpec, |
|
551 int32_t aNewFrecency, |
|
552 const nsACString& aGUID, |
|
553 bool aHidden, |
|
554 PRTime aLastVisitDate) |
|
555 : mSpec(aSpec) |
|
556 , mNewFrecency(aNewFrecency) |
|
557 , mGUID(aGUID) |
|
558 , mHidden(aHidden) |
|
559 , mLastVisitDate(aLastVisitDate) |
|
560 { |
|
561 } |
|
562 |
|
563 NS_IMETHOD Run() |
|
564 { |
|
565 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); |
|
566 nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); |
|
567 if (navHistory) { |
|
568 nsCOMPtr<nsIURI> uri; |
|
569 (void)NS_NewURI(getter_AddRefs(uri), mSpec); |
|
570 navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden, |
|
571 mLastVisitDate); |
|
572 } |
|
573 return NS_OK; |
|
574 } |
|
575 |
|
576 private: |
|
577 nsCString mSpec; |
|
578 int32_t mNewFrecency; |
|
579 nsCString mGUID; |
|
580 bool mHidden; |
|
581 PRTime mLastVisitDate; |
|
582 }; |
|
583 |
|
584 } // anonymous namespace |
|
585 |
|
586 void |
|
587 nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec, |
|
588 int32_t aNewFrecency, |
|
589 const nsACString& aGUID, |
|
590 bool aHidden, |
|
591 PRTime aLastVisitDate) const |
|
592 { |
|
593 nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency, |
|
594 aGUID, aHidden, |
|
595 aLastVisitDate); |
|
596 (void)NS_DispatchToMainThread(notif); |
|
597 } |
|
598 |
|
599 int32_t |
|
600 nsNavHistory::GetDaysOfHistory() { |
|
601 MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); |
|
602 |
|
603 if (mDaysOfHistory != -1) |
|
604 return mDaysOfHistory; |
|
605 |
|
606 // SQLite doesn't have a CEIL() function, so we must do that later. |
|
607 // We should also take into account timers resolution, that may be as bad as |
|
608 // 16ms on Windows, so in some cases the difference may be 0, if the |
|
609 // check is done near the visit. Thus remember to check for NULL separately. |
|
610 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
|
611 "SELECT CAST(( " |
|
612 "strftime('%s','now','localtime','utc') - " |
|
613 "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) " |
|
614 ") AS DOUBLE) " |
|
615 "/86400, " |
|
616 "strftime('%s','now','localtime','+1 day','start of day','utc') * 1000000" |
|
617 ); |
|
618 NS_ENSURE_TRUE(stmt, 0); |
|
619 mozStorageStatementScoper scoper(stmt); |
|
620 |
|
621 bool hasResult; |
|
622 if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { |
|
623 // If we get NULL, then there are no visits, otherwise there must always be |
|
624 // at least 1 day of history. |
|
625 bool hasNoVisits; |
|
626 (void)stmt->GetIsNull(0, &hasNoVisits); |
|
627 mDaysOfHistory = hasNoVisits ? |
|
628 0 : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0)))); |
|
629 mLastCachedStartOfDay = |
|
630 NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0); |
|
631 mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1. |
|
632 } |
|
633 |
|
634 return mDaysOfHistory; |
|
635 } |
|
636 |
|
637 PRTime |
|
638 nsNavHistory::GetNow() |
|
639 { |
|
640 if (!mCachedNow) { |
|
641 mCachedNow = PR_Now(); |
|
642 if (!mExpireNowTimer) |
|
643 mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1"); |
|
644 if (mExpireNowTimer) |
|
645 mExpireNowTimer->InitWithFuncCallback(expireNowTimerCallback, this, |
|
646 RENEW_CACHED_NOW_TIMEOUT, |
|
647 nsITimer::TYPE_ONE_SHOT); |
|
648 } |
|
649 return mCachedNow; |
|
650 } |
|
651 |
|
652 |
|
653 void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) |
|
654 { |
|
655 nsNavHistory *history = static_cast<nsNavHistory *>(aClosure); |
|
656 if (history) { |
|
657 history->mCachedNow = 0; |
|
658 history->mExpireNowTimer = 0; |
|
659 } |
|
660 } |
|
661 |
|
662 |
|
663 /** |
|
664 * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp |
|
665 * Pass in a pre-normalized now and a date, and we'll find the difference since |
|
666 * midnight on each of the days. |
|
667 */ |
|
668 static PRTime |
|
669 NormalizeTimeRelativeToday(PRTime aTime) |
|
670 { |
|
671 // round to midnight this morning |
|
672 PRExplodedTime explodedTime; |
|
673 PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); |
|
674 |
|
675 // set to midnight (0:00) |
|
676 explodedTime.tm_min = |
|
677 explodedTime.tm_hour = |
|
678 explodedTime.tm_sec = |
|
679 explodedTime.tm_usec = 0; |
|
680 |
|
681 return PR_ImplodeTime(&explodedTime); |
|
682 } |
|
683 |
|
684 // nsNavHistory::NormalizeTime |
|
685 // |
|
686 // Converts a nsINavHistoryQuery reference+offset time into a PRTime |
|
687 // relative to the epoch. |
|
688 // |
|
689 // It is important that this function NOT use the current time optimization. |
|
690 // It is called to update queries, and we really need to know what right |
|
691 // now is because those incoming values will also have current times that |
|
692 // we will have to compare against. |
|
693 |
|
694 PRTime // static |
|
695 nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) |
|
696 { |
|
697 PRTime ref; |
|
698 switch (aRelative) |
|
699 { |
|
700 case nsINavHistoryQuery::TIME_RELATIVE_EPOCH: |
|
701 return aOffset; |
|
702 case nsINavHistoryQuery::TIME_RELATIVE_TODAY: |
|
703 ref = NormalizeTimeRelativeToday(PR_Now()); |
|
704 break; |
|
705 case nsINavHistoryQuery::TIME_RELATIVE_NOW: |
|
706 ref = PR_Now(); |
|
707 break; |
|
708 default: |
|
709 NS_NOTREACHED("Invalid relative time"); |
|
710 return 0; |
|
711 } |
|
712 return ref + aOffset; |
|
713 } |
|
714 |
|
715 // nsNavHistory::GetUpdateRequirements |
|
716 // |
|
717 // Returns conditions for query update. |
|
718 // |
|
719 // QUERYUPDATE_TIME: |
|
720 // This query is only limited by an inclusive time range on the first |
|
721 // query object. The caller can quickly evaluate the time itself if it |
|
722 // chooses. This is even simpler than "simple" below. |
|
723 // QUERYUPDATE_SIMPLE: |
|
724 // This query is evaluatable using EvaluateQueryForNode to do live |
|
725 // updating. |
|
726 // QUERYUPDATE_COMPLEX: |
|
727 // This query is not evaluatable using EvaluateQueryForNode. When something |
|
728 // happens that this query updates, you will need to re-run the query. |
|
729 // QUERYUPDATE_COMPLEX_WITH_BOOKMARKS: |
|
730 // A complex query that additionally has dependence on bookmarks. All |
|
731 // bookmark-dependent queries fall under this category. |
|
732 // |
|
733 // aHasSearchTerms will be set to true if the query has any dependence on |
|
734 // keywords. When there is no dependence on keywords, we can handle title |
|
735 // change operations as simple instead of complex. |
|
736 |
|
737 uint32_t |
|
738 nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
739 nsNavHistoryQueryOptions* aOptions, |
|
740 bool* aHasSearchTerms) |
|
741 { |
|
742 NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query"); |
|
743 |
|
744 // first check if there are search terms |
|
745 *aHasSearchTerms = false; |
|
746 int32_t i; |
|
747 for (i = 0; i < aQueries.Count(); i ++) { |
|
748 aQueries[i]->GetHasSearchTerms(aHasSearchTerms); |
|
749 if (*aHasSearchTerms) |
|
750 break; |
|
751 } |
|
752 |
|
753 bool nonTimeBasedItems = false; |
|
754 bool domainBasedItems = false; |
|
755 |
|
756 for (i = 0; i < aQueries.Count(); i ++) { |
|
757 nsNavHistoryQuery* query = aQueries[i]; |
|
758 |
|
759 if (query->Folders().Length() > 0 || |
|
760 query->OnlyBookmarked() || |
|
761 query->Tags().Length() > 0) { |
|
762 return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; |
|
763 } |
|
764 |
|
765 // Note: we don't currently have any complex non-bookmarked items, but these |
|
766 // are expected to be added. Put detection of these items here. |
|
767 if (!query->SearchTerms().IsEmpty() || |
|
768 !query->Domain().IsVoid() || |
|
769 query->Uri() != nullptr) |
|
770 nonTimeBasedItems = true; |
|
771 |
|
772 if (! query->Domain().IsVoid()) |
|
773 domainBasedItems = true; |
|
774 } |
|
775 |
|
776 if (aOptions->ResultType() == |
|
777 nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) |
|
778 return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; |
|
779 |
|
780 // Whenever there is a maximum number of results, |
|
781 // and we are not a bookmark query we must requery. This |
|
782 // is because we can't generally know if any given addition/change causes |
|
783 // the item to be in the top N items in the database. |
|
784 if (aOptions->MaxResults() > 0) |
|
785 return QUERYUPDATE_COMPLEX; |
|
786 |
|
787 if (aQueries.Count() == 1 && domainBasedItems) |
|
788 return QUERYUPDATE_HOST; |
|
789 if (aQueries.Count() == 1 && !nonTimeBasedItems) |
|
790 return QUERYUPDATE_TIME; |
|
791 |
|
792 return QUERYUPDATE_SIMPLE; |
|
793 } |
|
794 |
|
795 |
|
796 // nsNavHistory::EvaluateQueryForNode |
|
797 // |
|
798 // This runs the node through the given queries to see if satisfies the |
|
799 // query conditions. Not every query parameters are handled by this code, |
|
800 // but we handle the most common ones so that performance is better. |
|
801 // |
|
802 // We assume that the time on the node is the time that we want to compare. |
|
803 // This is not necessarily true because URL nodes have the last access time, |
|
804 // which is not necessarily the same. However, since this is being called |
|
805 // to update the list, we assume that the last access time is the current |
|
806 // access time that we are being asked to compare so it works out. |
|
807 // |
|
808 // Returns true if node matches the query, false if not. |
|
809 |
|
810 bool |
|
811 nsNavHistory::EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
812 nsNavHistoryQueryOptions* aOptions, |
|
813 nsNavHistoryResultNode* aNode) |
|
814 { |
|
815 // lazily created from the node's string when we need to match URIs |
|
816 nsCOMPtr<nsIURI> nodeUri; |
|
817 |
|
818 // --- hidden --- |
|
819 if (aNode->mHidden && !aOptions->IncludeHidden()) |
|
820 return false; |
|
821 |
|
822 for (int32_t i = 0; i < aQueries.Count(); i ++) { |
|
823 bool hasIt; |
|
824 nsCOMPtr<nsNavHistoryQuery> query = aQueries[i]; |
|
825 |
|
826 // --- begin time --- |
|
827 query->GetHasBeginTime(&hasIt); |
|
828 if (hasIt) { |
|
829 PRTime beginTime = NormalizeTime(query->BeginTimeReference(), |
|
830 query->BeginTime()); |
|
831 if (aNode->mTime < beginTime) |
|
832 continue; // before our time range |
|
833 } |
|
834 |
|
835 // --- end time --- |
|
836 query->GetHasEndTime(&hasIt); |
|
837 if (hasIt) { |
|
838 PRTime endTime = NormalizeTime(query->EndTimeReference(), |
|
839 query->EndTime()); |
|
840 if (aNode->mTime > endTime) |
|
841 continue; // after our time range |
|
842 } |
|
843 |
|
844 // --- search terms --- |
|
845 if (! query->SearchTerms().IsEmpty()) { |
|
846 // we can use the existing filtering code, just give it our one object in |
|
847 // an array. |
|
848 nsCOMArray<nsNavHistoryResultNode> inputSet; |
|
849 inputSet.AppendObject(aNode); |
|
850 nsCOMArray<nsNavHistoryQuery> queries; |
|
851 queries.AppendObject(query); |
|
852 nsCOMArray<nsNavHistoryResultNode> filteredSet; |
|
853 nsresult rv = FilterResultSet(nullptr, inputSet, &filteredSet, queries, aOptions); |
|
854 if (NS_FAILED(rv)) |
|
855 continue; |
|
856 if (! filteredSet.Count()) |
|
857 continue; // did not make it through the filter, doesn't match |
|
858 } |
|
859 |
|
860 // --- domain/host matching --- |
|
861 query->GetHasDomain(&hasIt); |
|
862 if (hasIt) { |
|
863 if (! nodeUri) { |
|
864 // lazy creation of nodeUri, which might be checked for multiple queries |
|
865 if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) |
|
866 continue; |
|
867 } |
|
868 nsAutoCString asciiRequest; |
|
869 if (NS_FAILED(AsciiHostNameFromHostString(query->Domain(), asciiRequest))) |
|
870 continue; |
|
871 |
|
872 if (query->DomainIsHost()) { |
|
873 nsAutoCString host; |
|
874 if (NS_FAILED(nodeUri->GetAsciiHost(host))) |
|
875 continue; |
|
876 |
|
877 if (! asciiRequest.Equals(host)) |
|
878 continue; // host names don't match |
|
879 } |
|
880 // check domain names |
|
881 nsAutoCString domain; |
|
882 DomainNameFromURI(nodeUri, domain); |
|
883 if (! asciiRequest.Equals(domain)) |
|
884 continue; // domain names don't match |
|
885 } |
|
886 |
|
887 // --- URI matching --- |
|
888 if (query->Uri()) { |
|
889 if (! nodeUri) { // lazy creation of nodeUri |
|
890 if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) |
|
891 continue; |
|
892 } |
|
893 if (! query->UriIsPrefix()) { |
|
894 // easy case: the URI is an exact match |
|
895 bool equals; |
|
896 nsresult rv = query->Uri()->Equals(nodeUri, &equals); |
|
897 NS_ENSURE_SUCCESS(rv, false); |
|
898 if (! equals) |
|
899 continue; |
|
900 } else { |
|
901 // harder case: match prefix, note that we need to get the ASCII string |
|
902 // from the node's parsed URI instead of using the node's mUrl string, |
|
903 // because that might not be normalized |
|
904 nsAutoCString nodeUriString; |
|
905 nodeUri->GetAsciiSpec(nodeUriString); |
|
906 nsAutoCString queryUriString; |
|
907 query->Uri()->GetAsciiSpec(queryUriString); |
|
908 if (queryUriString.Length() > nodeUriString.Length()) |
|
909 continue; // not long enough to match as prefix |
|
910 nodeUriString.SetLength(queryUriString.Length()); |
|
911 if (! nodeUriString.Equals(queryUriString)) |
|
912 continue; // prefixes don't match |
|
913 } |
|
914 } |
|
915 |
|
916 // Transitions matching. |
|
917 const nsTArray<uint32_t>& transitions = query->Transitions(); |
|
918 if (aNode->mTransitionType > 0 && |
|
919 transitions.Length() && |
|
920 !transitions.Contains(aNode->mTransitionType)) { |
|
921 continue; // transition doesn't match. |
|
922 } |
|
923 |
|
924 // If we ever make it to the bottom of this loop, that means it passed all |
|
925 // tests for the given query. Since queries are ORed together, that means |
|
926 // it passed everything and we are done. |
|
927 return true; |
|
928 } |
|
929 |
|
930 // didn't match any query |
|
931 return false; |
|
932 } |
|
933 |
|
934 |
|
935 // nsNavHistory::AsciiHostNameFromHostString |
|
936 // |
|
937 // We might have interesting encodings and different case in the host name. |
|
938 // This will convert that host name into an ASCII host name by sending it |
|
939 // through the URI canonicalization. The result can be used for comparison |
|
940 // with other ASCII host name strings. |
|
941 nsresult // static |
|
942 nsNavHistory::AsciiHostNameFromHostString(const nsACString& aHostName, |
|
943 nsACString& aAscii) |
|
944 { |
|
945 // To properly generate a uri we must provide a protocol. |
|
946 nsAutoCString fakeURL("http://"); |
|
947 fakeURL.Append(aHostName); |
|
948 nsCOMPtr<nsIURI> uri; |
|
949 nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL); |
|
950 NS_ENSURE_SUCCESS(rv, rv); |
|
951 rv = uri->GetAsciiHost(aAscii); |
|
952 NS_ENSURE_SUCCESS(rv, rv); |
|
953 return NS_OK; |
|
954 } |
|
955 |
|
956 |
|
957 // nsNavHistory::DomainNameFromURI |
|
958 // |
|
959 // This does the www.mozilla.org -> mozilla.org and |
|
960 // foo.theregister.co.uk -> theregister.co.uk conversion |
|
961 void |
|
962 nsNavHistory::DomainNameFromURI(nsIURI *aURI, |
|
963 nsACString& aDomainName) |
|
964 { |
|
965 // lazily get the effective tld service |
|
966 if (!mTLDService) |
|
967 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); |
|
968 |
|
969 if (mTLDService) { |
|
970 // get the base domain for a given hostname. |
|
971 // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk". |
|
972 nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName); |
|
973 if (NS_SUCCEEDED(rv)) |
|
974 return; |
|
975 } |
|
976 |
|
977 // just return the original hostname |
|
978 // (it's also possible the host is an IP address) |
|
979 aURI->GetAsciiHost(aDomainName); |
|
980 } |
|
981 |
|
982 |
|
983 NS_IMETHODIMP |
|
984 nsNavHistory::GetHasHistoryEntries(bool* aHasEntries) |
|
985 { |
|
986 NS_ENSURE_ARG_POINTER(aHasEntries); |
|
987 *aHasEntries = GetDaysOfHistory() > 0; |
|
988 return NS_OK; |
|
989 } |
|
990 |
|
991 |
|
992 namespace { |
|
993 |
|
994 class InvalidateAllFrecenciesCallback : public AsyncStatementCallback |
|
995 { |
|
996 public: |
|
997 InvalidateAllFrecenciesCallback() |
|
998 { |
|
999 } |
|
1000 |
|
1001 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
1002 { |
|
1003 if (aReason == REASON_FINISHED) { |
|
1004 nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
|
1005 NS_ENSURE_STATE(navHistory); |
|
1006 navHistory->NotifyManyFrecenciesChanged(); |
|
1007 } |
|
1008 return NS_OK; |
|
1009 } |
|
1010 }; |
|
1011 |
|
1012 } // anonymous namespace |
|
1013 |
|
1014 nsresult |
|
1015 nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString) |
|
1016 { |
|
1017 // Exclude place: queries by setting their frecency to zero. |
|
1018 nsCString invalidFrecenciesSQLFragment( |
|
1019 "UPDATE moz_places SET frecency = " |
|
1020 ); |
|
1021 if (!aPlaceIdsQueryString.IsEmpty()) |
|
1022 invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY("); |
|
1023 invalidFrecenciesSQLFragment.AppendLiteral( |
|
1024 "(CASE " |
|
1025 "WHEN url BETWEEN 'place:' AND 'place;' " |
|
1026 "THEN 0 " |
|
1027 "ELSE -1 " |
|
1028 "END) " |
|
1029 ); |
|
1030 if (!aPlaceIdsQueryString.IsEmpty()) { |
|
1031 invalidFrecenciesSQLFragment.AppendLiteral( |
|
1032 ", url, guid, hidden, last_visit_date) " |
|
1033 ); |
|
1034 } |
|
1035 invalidFrecenciesSQLFragment.AppendLiteral( |
|
1036 "WHERE frecency > 0 " |
|
1037 ); |
|
1038 if (!aPlaceIdsQueryString.IsEmpty()) { |
|
1039 invalidFrecenciesSQLFragment.AppendLiteral("AND id IN("); |
|
1040 invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString); |
|
1041 invalidFrecenciesSQLFragment.AppendLiteral(")"); |
|
1042 } |
|
1043 nsRefPtr<InvalidateAllFrecenciesCallback> cb = |
|
1044 aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback() |
|
1045 : nullptr; |
|
1046 |
|
1047 nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
|
1048 invalidFrecenciesSQLFragment |
|
1049 ); |
|
1050 NS_ENSURE_STATE(stmt); |
|
1051 |
|
1052 nsCOMPtr<mozIStoragePendingStatement> ps; |
|
1053 nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps)); |
|
1054 NS_ENSURE_SUCCESS(rv, rv); |
|
1055 |
|
1056 return NS_OK; |
|
1057 } |
|
1058 |
|
1059 |
|
1060 // Call this method before visiting a URL in order to help determine the |
|
1061 // transition type of the visit. |
|
1062 // |
|
1063 // @see MarkPageAsTyped |
|
1064 |
|
1065 NS_IMETHODIMP |
|
1066 nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) |
|
1067 { |
|
1068 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1069 NS_ENSURE_ARG(aURI); |
|
1070 |
|
1071 // don't add when history is disabled |
|
1072 if (IsHistoryDisabled()) |
|
1073 return NS_OK; |
|
1074 |
|
1075 nsAutoCString uriString; |
|
1076 nsresult rv = aURI->GetSpec(uriString); |
|
1077 NS_ENSURE_SUCCESS(rv, rv); |
|
1078 |
|
1079 // if URL is already in the bookmark queue, then we need to remove the old one |
|
1080 int64_t unusedEventTime; |
|
1081 if (mRecentBookmark.Get(uriString, &unusedEventTime)) |
|
1082 mRecentBookmark.Remove(uriString); |
|
1083 |
|
1084 if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
|
1085 ExpireNonrecentEvents(&mRecentBookmark); |
|
1086 |
|
1087 mRecentBookmark.Put(uriString, GetNow()); |
|
1088 return NS_OK; |
|
1089 } |
|
1090 |
|
1091 |
|
1092 // nsNavHistory::CanAddURI |
|
1093 // |
|
1094 // Filter out unwanted URIs such as "chrome:", "mailbox:", etc. |
|
1095 // |
|
1096 // The model is if we don't know differently then add which basically means |
|
1097 // we are suppose to try all the things we know not to allow in and then if |
|
1098 // we don't bail go on and allow it in. |
|
1099 |
|
1100 NS_IMETHODIMP |
|
1101 nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) |
|
1102 { |
|
1103 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1104 NS_ENSURE_ARG(aURI); |
|
1105 NS_ENSURE_ARG_POINTER(canAdd); |
|
1106 |
|
1107 // If history is disabled, don't add any entry. |
|
1108 if (IsHistoryDisabled()) { |
|
1109 *canAdd = false; |
|
1110 return NS_OK; |
|
1111 } |
|
1112 |
|
1113 nsAutoCString scheme; |
|
1114 nsresult rv = aURI->GetScheme(scheme); |
|
1115 NS_ENSURE_SUCCESS(rv, rv); |
|
1116 |
|
1117 // first check the most common cases (HTTP, HTTPS) to allow in to avoid most |
|
1118 // of the work |
|
1119 if (scheme.EqualsLiteral("http")) { |
|
1120 *canAdd = true; |
|
1121 return NS_OK; |
|
1122 } |
|
1123 if (scheme.EqualsLiteral("https")) { |
|
1124 *canAdd = true; |
|
1125 return NS_OK; |
|
1126 } |
|
1127 |
|
1128 // now check for all bad things |
|
1129 if (scheme.EqualsLiteral("about") || |
|
1130 scheme.EqualsLiteral("imap") || |
|
1131 scheme.EqualsLiteral("news") || |
|
1132 scheme.EqualsLiteral("mailbox") || |
|
1133 scheme.EqualsLiteral("moz-anno") || |
|
1134 scheme.EqualsLiteral("view-source") || |
|
1135 scheme.EqualsLiteral("chrome") || |
|
1136 scheme.EqualsLiteral("resource") || |
|
1137 scheme.EqualsLiteral("data") || |
|
1138 scheme.EqualsLiteral("wyciwyg") || |
|
1139 scheme.EqualsLiteral("javascript") || |
|
1140 scheme.EqualsLiteral("blob")) { |
|
1141 *canAdd = false; |
|
1142 return NS_OK; |
|
1143 } |
|
1144 *canAdd = true; |
|
1145 return NS_OK; |
|
1146 } |
|
1147 |
|
1148 // nsNavHistory::GetNewQuery |
|
1149 |
|
1150 NS_IMETHODIMP |
|
1151 nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval) |
|
1152 { |
|
1153 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1154 NS_ENSURE_ARG_POINTER(_retval); |
|
1155 |
|
1156 nsRefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery(); |
|
1157 query.forget(_retval); |
|
1158 return NS_OK; |
|
1159 } |
|
1160 |
|
1161 // nsNavHistory::GetNewQueryOptions |
|
1162 |
|
1163 NS_IMETHODIMP |
|
1164 nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions **_retval) |
|
1165 { |
|
1166 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1167 NS_ENSURE_ARG_POINTER(_retval); |
|
1168 |
|
1169 nsRefPtr<nsNavHistoryQueryOptions> queryOptions = new nsNavHistoryQueryOptions(); |
|
1170 queryOptions.forget(_retval); |
|
1171 return NS_OK; |
|
1172 } |
|
1173 |
|
1174 // nsNavHistory::ExecuteQuery |
|
1175 // |
|
1176 |
|
1177 NS_IMETHODIMP |
|
1178 nsNavHistory::ExecuteQuery(nsINavHistoryQuery *aQuery, nsINavHistoryQueryOptions *aOptions, |
|
1179 nsINavHistoryResult** _retval) |
|
1180 { |
|
1181 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1182 NS_ENSURE_ARG(aQuery); |
|
1183 NS_ENSURE_ARG(aOptions); |
|
1184 NS_ENSURE_ARG_POINTER(_retval); |
|
1185 |
|
1186 return ExecuteQueries(&aQuery, 1, aOptions, _retval); |
|
1187 } |
|
1188 |
|
1189 |
|
1190 // nsNavHistory::ExecuteQueries |
|
1191 // |
|
1192 // This function is actually very simple, we just create the proper root node (either |
|
1193 // a bookmark folder or a complex query node) and assign it to the result. The node |
|
1194 // will then populate itself accordingly. |
|
1195 // |
|
1196 // Quick overview of query operation: When you call this function, we will construct |
|
1197 // the correct container node and set the options you give it. This node will then |
|
1198 // fill itself. Folder nodes will call nsNavBookmarks::QueryFolderChildren, and |
|
1199 // all other queries will call GetQueryResults. If these results contain other |
|
1200 // queries, those will be populated when the container is opened. |
|
1201 |
|
1202 NS_IMETHODIMP |
|
1203 nsNavHistory::ExecuteQueries(nsINavHistoryQuery** aQueries, uint32_t aQueryCount, |
|
1204 nsINavHistoryQueryOptions *aOptions, |
|
1205 nsINavHistoryResult** _retval) |
|
1206 { |
|
1207 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
1208 NS_ENSURE_ARG(aQueries); |
|
1209 NS_ENSURE_ARG(aOptions); |
|
1210 NS_ENSURE_ARG(aQueryCount); |
|
1211 NS_ENSURE_ARG_POINTER(_retval); |
|
1212 |
|
1213 nsresult rv; |
|
1214 // concrete options |
|
1215 nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); |
|
1216 NS_ENSURE_TRUE(options, NS_ERROR_INVALID_ARG); |
|
1217 |
|
1218 // concrete queries array |
|
1219 nsCOMArray<nsNavHistoryQuery> queries; |
|
1220 for (uint32_t i = 0; i < aQueryCount; i ++) { |
|
1221 nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i], &rv); |
|
1222 NS_ENSURE_SUCCESS(rv, rv); |
|
1223 queries.AppendObject(query); |
|
1224 } |
|
1225 |
|
1226 // Create the root node. |
|
1227 nsRefPtr<nsNavHistoryContainerResultNode> rootNode; |
|
1228 int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); |
|
1229 if (folderId) { |
|
1230 // In the simple case where we're just querying children of a single |
|
1231 // bookmark folder, we can more efficiently generate results. |
|
1232 nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
1233 NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
|
1234 nsRefPtr<nsNavHistoryResultNode> tempRootNode; |
|
1235 rv = bookmarks->ResultNodeForContainer(folderId, options, |
|
1236 getter_AddRefs(tempRootNode)); |
|
1237 if (NS_SUCCEEDED(rv)) { |
|
1238 rootNode = tempRootNode->GetAsContainer(); |
|
1239 } |
|
1240 else { |
|
1241 NS_WARNING("Generating a generic empty node for a broken query!"); |
|
1242 // This is a perf hack to generate an empty query that skips filtering. |
|
1243 options->SetExcludeItems(true); |
|
1244 } |
|
1245 } |
|
1246 |
|
1247 if (!rootNode) { |
|
1248 // Either this is not a folder shortcut, or is a broken one. In both cases |
|
1249 // just generate a query node. |
|
1250 rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), EmptyCString(), |
|
1251 queries, options); |
|
1252 } |
|
1253 |
|
1254 // Create the result that will hold nodes. Inject batching status into it. |
|
1255 nsRefPtr<nsNavHistoryResult> result; |
|
1256 rv = nsNavHistoryResult::NewHistoryResult(aQueries, aQueryCount, options, |
|
1257 rootNode, isBatching(), |
|
1258 getter_AddRefs(result)); |
|
1259 NS_ENSURE_SUCCESS(rv, rv); |
|
1260 |
|
1261 result.forget(_retval); |
|
1262 return NS_OK; |
|
1263 } |
|
1264 |
|
1265 // determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions |
|
1266 // if this is the place query from the history menu. |
|
1267 // from browser-menubar.inc, our history menu query is: |
|
1268 // place:sort=4&maxResults=10 |
|
1269 // note, any maxResult > 0 will still be considered a history menu query |
|
1270 // or if this is the place query from the "Most Visited" item in the |
|
1271 // "Smart Bookmarks" folder: place:sort=8&maxResults=10 |
|
1272 // note, any maxResult > 0 will still be considered a Most Visited menu query |
|
1273 static |
|
1274 bool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
1275 nsNavHistoryQueryOptions *aOptions, |
|
1276 uint16_t aSortMode) |
|
1277 { |
|
1278 if (aQueries.Count() != 1) |
|
1279 return false; |
|
1280 |
|
1281 nsNavHistoryQuery *aQuery = aQueries[0]; |
|
1282 |
|
1283 if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) |
|
1284 return false; |
|
1285 |
|
1286 if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI) |
|
1287 return false; |
|
1288 |
|
1289 if (aOptions->SortingMode() != aSortMode) |
|
1290 return false; |
|
1291 |
|
1292 if (aOptions->MaxResults() <= 0) |
|
1293 return false; |
|
1294 |
|
1295 if (aOptions->ExcludeItems()) |
|
1296 return false; |
|
1297 |
|
1298 if (aOptions->IncludeHidden()) |
|
1299 return false; |
|
1300 |
|
1301 if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) |
|
1302 return false; |
|
1303 |
|
1304 if (aQuery->BeginTime() || aQuery->BeginTimeReference()) |
|
1305 return false; |
|
1306 |
|
1307 if (aQuery->EndTime() || aQuery->EndTimeReference()) |
|
1308 return false; |
|
1309 |
|
1310 if (!aQuery->SearchTerms().IsEmpty()) |
|
1311 return false; |
|
1312 |
|
1313 if (aQuery->OnlyBookmarked()) |
|
1314 return false; |
|
1315 |
|
1316 if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) |
|
1317 return false; |
|
1318 |
|
1319 if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty()) |
|
1320 return false; |
|
1321 |
|
1322 if (aQuery->UriIsPrefix() || aQuery->Uri()) |
|
1323 return false; |
|
1324 |
|
1325 if (aQuery->Folders().Length() > 0) |
|
1326 return false; |
|
1327 |
|
1328 if (aQuery->Tags().Length() > 0) |
|
1329 return false; |
|
1330 |
|
1331 if (aQuery->Transitions().Length() > 0) |
|
1332 return false; |
|
1333 |
|
1334 return true; |
|
1335 } |
|
1336 |
|
1337 static |
|
1338 bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
1339 nsNavHistoryQueryOptions *aOptions) |
|
1340 { |
|
1341 uint16_t resultType = aOptions->ResultType(); |
|
1342 return resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS; |
|
1343 } |
|
1344 |
|
1345 // ** Helper class for ConstructQueryString **/ |
|
1346 |
|
1347 class PlacesSQLQueryBuilder |
|
1348 { |
|
1349 public: |
|
1350 PlacesSQLQueryBuilder(const nsCString& aConditions, |
|
1351 nsNavHistoryQueryOptions* aOptions, |
|
1352 bool aUseLimit, |
|
1353 nsNavHistory::StringHash& aAddParams, |
|
1354 bool aHasSearchTerms); |
|
1355 |
|
1356 nsresult GetQueryString(nsCString& aQueryString); |
|
1357 |
|
1358 private: |
|
1359 nsresult Select(); |
|
1360 |
|
1361 nsresult SelectAsURI(); |
|
1362 nsresult SelectAsVisit(); |
|
1363 nsresult SelectAsDay(); |
|
1364 nsresult SelectAsSite(); |
|
1365 nsresult SelectAsTag(); |
|
1366 |
|
1367 nsresult Where(); |
|
1368 nsresult GroupBy(); |
|
1369 nsresult OrderBy(); |
|
1370 nsresult Limit(); |
|
1371 |
|
1372 void OrderByColumnIndexAsc(int32_t aIndex); |
|
1373 void OrderByColumnIndexDesc(int32_t aIndex); |
|
1374 // Use these if you want a case insensitive sorting. |
|
1375 void OrderByTextColumnIndexAsc(int32_t aIndex); |
|
1376 void OrderByTextColumnIndexDesc(int32_t aIndex); |
|
1377 |
|
1378 const nsCString& mConditions; |
|
1379 bool mUseLimit; |
|
1380 bool mHasSearchTerms; |
|
1381 |
|
1382 uint16_t mResultType; |
|
1383 uint16_t mQueryType; |
|
1384 bool mIncludeHidden; |
|
1385 uint16_t mSortingMode; |
|
1386 uint32_t mMaxResults; |
|
1387 |
|
1388 nsCString mQueryString; |
|
1389 nsCString mGroupBy; |
|
1390 bool mHasDateColumns; |
|
1391 bool mSkipOrderBy; |
|
1392 nsNavHistory::StringHash& mAddParams; |
|
1393 }; |
|
1394 |
|
1395 PlacesSQLQueryBuilder::PlacesSQLQueryBuilder( |
|
1396 const nsCString& aConditions, |
|
1397 nsNavHistoryQueryOptions* aOptions, |
|
1398 bool aUseLimit, |
|
1399 nsNavHistory::StringHash& aAddParams, |
|
1400 bool aHasSearchTerms) |
|
1401 : mConditions(aConditions) |
|
1402 , mUseLimit(aUseLimit) |
|
1403 , mHasSearchTerms(aHasSearchTerms) |
|
1404 , mResultType(aOptions->ResultType()) |
|
1405 , mQueryType(aOptions->QueryType()) |
|
1406 , mIncludeHidden(aOptions->IncludeHidden()) |
|
1407 , mSortingMode(aOptions->SortingMode()) |
|
1408 , mMaxResults(aOptions->MaxResults()) |
|
1409 , mSkipOrderBy(false) |
|
1410 , mAddParams(aAddParams) |
|
1411 { |
|
1412 mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS); |
|
1413 } |
|
1414 |
|
1415 nsresult |
|
1416 PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) |
|
1417 { |
|
1418 nsresult rv = Select(); |
|
1419 NS_ENSURE_SUCCESS(rv, rv); |
|
1420 rv = Where(); |
|
1421 NS_ENSURE_SUCCESS(rv, rv); |
|
1422 rv = GroupBy(); |
|
1423 NS_ENSURE_SUCCESS(rv, rv); |
|
1424 rv = OrderBy(); |
|
1425 NS_ENSURE_SUCCESS(rv, rv); |
|
1426 rv = Limit(); |
|
1427 NS_ENSURE_SUCCESS(rv, rv); |
|
1428 |
|
1429 aQueryString = mQueryString; |
|
1430 return NS_OK; |
|
1431 } |
|
1432 |
|
1433 nsresult |
|
1434 PlacesSQLQueryBuilder::Select() |
|
1435 { |
|
1436 nsresult rv; |
|
1437 |
|
1438 switch (mResultType) |
|
1439 { |
|
1440 case nsINavHistoryQueryOptions::RESULTS_AS_URI: |
|
1441 case nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS: |
|
1442 rv = SelectAsURI(); |
|
1443 NS_ENSURE_SUCCESS(rv, rv); |
|
1444 break; |
|
1445 |
|
1446 case nsINavHistoryQueryOptions::RESULTS_AS_VISIT: |
|
1447 case nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: |
|
1448 rv = SelectAsVisit(); |
|
1449 NS_ENSURE_SUCCESS(rv, rv); |
|
1450 break; |
|
1451 |
|
1452 case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY: |
|
1453 case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY: |
|
1454 rv = SelectAsDay(); |
|
1455 NS_ENSURE_SUCCESS(rv, rv); |
|
1456 break; |
|
1457 |
|
1458 case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY: |
|
1459 rv = SelectAsSite(); |
|
1460 NS_ENSURE_SUCCESS(rv, rv); |
|
1461 break; |
|
1462 |
|
1463 case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY: |
|
1464 rv = SelectAsTag(); |
|
1465 NS_ENSURE_SUCCESS(rv, rv); |
|
1466 break; |
|
1467 |
|
1468 default: |
|
1469 NS_NOTREACHED("Invalid result type"); |
|
1470 } |
|
1471 return NS_OK; |
|
1472 } |
|
1473 |
|
1474 nsresult |
|
1475 PlacesSQLQueryBuilder::SelectAsURI() |
|
1476 { |
|
1477 nsNavHistory *history = nsNavHistory::GetHistoryService(); |
|
1478 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); |
|
1479 nsAutoCString tagsSqlFragment; |
|
1480 |
|
1481 switch (mQueryType) { |
|
1482 case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY: |
|
1483 GetTagsSqlFragment(history->GetTagsFolder(), |
|
1484 NS_LITERAL_CSTRING("h.id"), |
|
1485 mHasSearchTerms, |
|
1486 tagsSqlFragment); |
|
1487 |
|
1488 mQueryString = NS_LITERAL_CSTRING( |
|
1489 "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " |
|
1490 "h.last_visit_date, f.url, null, null, null, null, ") + |
|
1491 tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
1492 "FROM moz_places h " |
|
1493 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
1494 // WHERE 1 is a no-op since additonal conditions will start with AND. |
|
1495 "WHERE 1 " |
|
1496 "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " |
|
1497 "{ADDITIONAL_CONDITIONS} "); |
|
1498 break; |
|
1499 |
|
1500 case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS: |
|
1501 if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { |
|
1502 // Order-by clause is hardcoded because we need to discard duplicates |
|
1503 // in FilterResultSet. We will retain only the last modified item, |
|
1504 // so we are ordering by place id and last modified to do a faster |
|
1505 // filtering. |
|
1506 mSkipOrderBy = true; |
|
1507 |
|
1508 GetTagsSqlFragment(history->GetTagsFolder(), |
|
1509 NS_LITERAL_CSTRING("b2.fk"), |
|
1510 mHasSearchTerms, |
|
1511 tagsSqlFragment); |
|
1512 |
|
1513 mQueryString = NS_LITERAL_CSTRING( |
|
1514 "SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, " |
|
1515 "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, " |
|
1516 "b2.dateAdded, b2.lastModified, b2.parent, ") + |
|
1517 tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
1518 "FROM moz_bookmarks b2 " |
|
1519 "JOIN (SELECT b.fk " |
|
1520 "FROM moz_bookmarks b " |
|
1521 // ADDITIONAL_CONDITIONS will filter on parent. |
|
1522 "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} " |
|
1523 ") AS seed ON b2.fk = seed.fk " |
|
1524 "JOIN moz_places h ON h.id = b2.fk " |
|
1525 "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
|
1526 "WHERE NOT EXISTS ( " |
|
1527 "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") + |
|
1528 nsPrintfCString("%lld", history->GetTagsFolder()) + |
|
1529 NS_LITERAL_CSTRING(") " |
|
1530 "ORDER BY b2.fk DESC, b2.lastModified DESC"); |
|
1531 } |
|
1532 else { |
|
1533 GetTagsSqlFragment(history->GetTagsFolder(), |
|
1534 NS_LITERAL_CSTRING("b.fk"), |
|
1535 mHasSearchTerms, |
|
1536 tagsSqlFragment); |
|
1537 mQueryString = NS_LITERAL_CSTRING( |
|
1538 "SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, " |
|
1539 "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " |
|
1540 "b.dateAdded, b.lastModified, b.parent, ") + |
|
1541 tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
1542 "FROM moz_bookmarks b " |
|
1543 "JOIN moz_places h ON b.fk = h.id " |
|
1544 "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
|
1545 "WHERE NOT EXISTS " |
|
1546 "(SELECT id FROM moz_bookmarks " |
|
1547 "WHERE id = b.parent AND parent = ") + |
|
1548 nsPrintfCString("%lld", history->GetTagsFolder()) + |
|
1549 NS_LITERAL_CSTRING(") " |
|
1550 "{ADDITIONAL_CONDITIONS}"); |
|
1551 } |
|
1552 break; |
|
1553 |
|
1554 default: |
|
1555 return NS_ERROR_NOT_IMPLEMENTED; |
|
1556 } |
|
1557 return NS_OK; |
|
1558 } |
|
1559 |
|
1560 nsresult |
|
1561 PlacesSQLQueryBuilder::SelectAsVisit() |
|
1562 { |
|
1563 nsNavHistory *history = nsNavHistory::GetHistoryService(); |
|
1564 NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); |
|
1565 nsAutoCString tagsSqlFragment; |
|
1566 GetTagsSqlFragment(history->GetTagsFolder(), |
|
1567 NS_LITERAL_CSTRING("h.id"), |
|
1568 mHasSearchTerms, |
|
1569 tagsSqlFragment); |
|
1570 mQueryString = NS_LITERAL_CSTRING( |
|
1571 "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " |
|
1572 "v.visit_date, f.url, null, null, null, null, ") + |
|
1573 tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
1574 "FROM moz_places h " |
|
1575 "JOIN moz_historyvisits v ON h.id = v.place_id " |
|
1576 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
1577 // WHERE 1 is a no-op since additonal conditions will start with AND. |
|
1578 "WHERE 1 " |
|
1579 "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " |
|
1580 "{ADDITIONAL_CONDITIONS} "); |
|
1581 |
|
1582 return NS_OK; |
|
1583 } |
|
1584 |
|
1585 nsresult |
|
1586 PlacesSQLQueryBuilder::SelectAsDay() |
|
1587 { |
|
1588 mSkipOrderBy = true; |
|
1589 |
|
1590 // Sort child queries based on sorting mode if it's provided, otherwise |
|
1591 // fallback to default sort by title ascending. |
|
1592 uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING; |
|
1593 if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE && |
|
1594 mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY) |
|
1595 sortingMode = mSortingMode; |
|
1596 |
|
1597 uint16_t resultType = |
|
1598 mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ? |
|
1599 (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI : |
|
1600 (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY; |
|
1601 |
|
1602 // beginTime will become the node's time property, we don't use endTime |
|
1603 // because it could overlap, and we use time to sort containers and find |
|
1604 // insert position in a result. |
|
1605 mQueryString = nsPrintfCString( |
|
1606 "SELECT null, " |
|
1607 "'place:type=%ld&sort=%ld&beginTime='||beginTime||'&endTime='||endTime, " |
|
1608 "dayTitle, null, null, beginTime, null, null, null, null, null, null " |
|
1609 "FROM (", // TOUTER BEGIN |
|
1610 resultType, |
|
1611 sortingMode); |
|
1612 |
|
1613 nsNavHistory *history = nsNavHistory::GetHistoryService(); |
|
1614 NS_ENSURE_STATE(history); |
|
1615 |
|
1616 int32_t daysOfHistory = history->GetDaysOfHistory(); |
|
1617 for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) { |
|
1618 nsAutoCString dateName; |
|
1619 // Timeframes are calculated as BeginTime <= container < EndTime. |
|
1620 // Notice times can't be relative to now, since to recognize a query we |
|
1621 // must ensure it won't change based on the time it is built. |
|
1622 // So, to select till now, we really select till start of tomorrow, that is |
|
1623 // a fixed timestamp. |
|
1624 // These are used as limits for the inside containers. |
|
1625 nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime; |
|
1626 // These are used to query if the container should be visible. |
|
1627 nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime; |
|
1628 switch(i) { |
|
1629 case 0: |
|
1630 // Today |
|
1631 history->GetStringFromName( |
|
1632 MOZ_UTF16("finduri-AgeInDays-is-0"), dateName); |
|
1633 // From start of today |
|
1634 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1635 "(strftime('%s','now','localtime','start of day','utc')*1000000)"); |
|
1636 // To now (tomorrow) |
|
1637 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1638 "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
|
1639 // Search for the same timeframe. |
|
1640 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1641 sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
|
1642 break; |
|
1643 case 1: |
|
1644 // Yesterday |
|
1645 history->GetStringFromName( |
|
1646 MOZ_UTF16("finduri-AgeInDays-is-1"), dateName); |
|
1647 // From start of yesterday |
|
1648 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1649 "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)"); |
|
1650 // To start of today |
|
1651 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1652 "(strftime('%s','now','localtime','start of day','utc')*1000000)"); |
|
1653 // Search for the same timeframe. |
|
1654 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1655 sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
|
1656 break; |
|
1657 case 2: |
|
1658 // Last 7 days |
|
1659 history->GetAgeInDaysString(7, |
|
1660 MOZ_UTF16("finduri-AgeInDays-last-is"), dateName); |
|
1661 // From start of 7 days ago |
|
1662 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1663 "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); |
|
1664 // To now (tomorrow) |
|
1665 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1666 "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
|
1667 // This is an overlapped container, but we show it only if there are |
|
1668 // visits older than yesterday. |
|
1669 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1670 sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( |
|
1671 "(strftime('%s','now','localtime','start of day','-2 days','utc')*1000000)"); |
|
1672 break; |
|
1673 case 3: |
|
1674 // This month |
|
1675 history->GetStringFromName( |
|
1676 MOZ_UTF16("finduri-AgeInMonths-is-0"), dateName); |
|
1677 // From start of this month |
|
1678 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1679 "(strftime('%s','now','localtime','start of month','utc')*1000000)"); |
|
1680 // To now (tomorrow) |
|
1681 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1682 "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); |
|
1683 // This is an overlapped container, but we show it only if there are |
|
1684 // visits older than 7 days ago. |
|
1685 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1686 sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( |
|
1687 "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); |
|
1688 break; |
|
1689 default: |
|
1690 if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) { |
|
1691 // Older than 6 months |
|
1692 history->GetAgeInDaysString(6, |
|
1693 MOZ_UTF16("finduri-AgeInMonths-isgreater"), dateName); |
|
1694 // From start of epoch |
|
1695 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1696 "(datetime(0, 'unixepoch')*1000000)"); |
|
1697 // To start of 6 months ago ( 5 months + this month). |
|
1698 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1699 "(strftime('%s','now','localtime','start of month','-5 months','utc')*1000000)"); |
|
1700 // Search for the same timeframe. |
|
1701 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1702 sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
|
1703 break; |
|
1704 } |
|
1705 int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM; |
|
1706 // Previous months' titles are month's name if inside this year, |
|
1707 // month's name and year for previous years. |
|
1708 PRExplodedTime tm; |
|
1709 PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm); |
|
1710 uint16_t currentYear = tm.tm_year; |
|
1711 // Set day before month, setting month without day could cause issues. |
|
1712 // For example setting month to February when today is 30, since |
|
1713 // February has not 30 days, will return March instead. |
|
1714 // Also, we use day 2 instead of day 1, so that the GMT month is always |
|
1715 // the same as the local month. (Bug 603002) |
|
1716 tm.tm_mday = 2; |
|
1717 tm.tm_month -= MonthIndex; |
|
1718 // Notice we use GMTParameters because we just want to get the first |
|
1719 // day of each month. Using LocalTimeParameters would instead force us |
|
1720 // to apply a DST correction that we don't really need here. |
|
1721 PR_NormalizeTime(&tm, PR_GMTParameters); |
|
1722 // If the container is for a past year, add the year to its title, |
|
1723 // otherwise just show the month name. |
|
1724 // Note that tm_month starts from 0, while we need a 1-based index. |
|
1725 if (tm.tm_year < currentYear) { |
|
1726 history->GetMonthYear(tm.tm_month + 1, tm.tm_year, dateName); |
|
1727 } |
|
1728 else { |
|
1729 history->GetMonthName(tm.tm_month + 1, dateName); |
|
1730 } |
|
1731 |
|
1732 // From start of MonthIndex + 1 months ago |
|
1733 sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( |
|
1734 "(strftime('%s','now','localtime','start of month','-"); |
|
1735 sqlFragmentContainerBeginTime.AppendInt(MonthIndex); |
|
1736 sqlFragmentContainerBeginTime.Append(NS_LITERAL_CSTRING( |
|
1737 " months','utc')*1000000)")); |
|
1738 // To start of MonthIndex months ago |
|
1739 sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( |
|
1740 "(strftime('%s','now','localtime','start of month','-"); |
|
1741 sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1); |
|
1742 sqlFragmentContainerEndTime.Append(NS_LITERAL_CSTRING( |
|
1743 " months','utc')*1000000)")); |
|
1744 // Search for the same timeframe. |
|
1745 sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; |
|
1746 sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; |
|
1747 break; |
|
1748 } |
|
1749 |
|
1750 nsPrintfCString dateParam("dayTitle%d", i); |
|
1751 mAddParams.Put(dateParam, dateName); |
|
1752 |
|
1753 nsPrintfCString dayRange( |
|
1754 "SELECT :%s AS dayTitle, " |
|
1755 "%s AS beginTime, " |
|
1756 "%s AS endTime " |
|
1757 "WHERE EXISTS ( " |
|
1758 "SELECT id FROM moz_historyvisits " |
|
1759 "WHERE visit_date >= %s " |
|
1760 "AND visit_date < %s " |
|
1761 "AND visit_type NOT IN (0,%d,%d) " |
|
1762 "{QUERY_OPTIONS_VISITS} " |
|
1763 "LIMIT 1 " |
|
1764 ") ", |
|
1765 dateParam.get(), |
|
1766 sqlFragmentContainerBeginTime.get(), |
|
1767 sqlFragmentContainerEndTime.get(), |
|
1768 sqlFragmentSearchBeginTime.get(), |
|
1769 sqlFragmentSearchEndTime.get(), |
|
1770 nsINavHistoryService::TRANSITION_EMBED, |
|
1771 nsINavHistoryService::TRANSITION_FRAMED_LINK |
|
1772 ); |
|
1773 |
|
1774 mQueryString.Append(dayRange); |
|
1775 |
|
1776 if (i < HISTORY_DATE_CONT_NUM(daysOfHistory)) |
|
1777 mQueryString.Append(NS_LITERAL_CSTRING(" UNION ALL ")); |
|
1778 } |
|
1779 |
|
1780 mQueryString.Append(NS_LITERAL_CSTRING(") ")); // TOUTER END |
|
1781 |
|
1782 return NS_OK; |
|
1783 } |
|
1784 |
|
1785 nsresult |
|
1786 PlacesSQLQueryBuilder::SelectAsSite() |
|
1787 { |
|
1788 nsAutoCString localFiles; |
|
1789 |
|
1790 nsNavHistory *history = nsNavHistory::GetHistoryService(); |
|
1791 NS_ENSURE_STATE(history); |
|
1792 |
|
1793 history->GetStringFromName(MOZ_UTF16("localhost"), localFiles); |
|
1794 mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles); |
|
1795 |
|
1796 // If there are additional conditions the query has to join on visits too. |
|
1797 nsAutoCString visitsJoin; |
|
1798 nsAutoCString additionalConditions; |
|
1799 nsAutoCString timeConstraints; |
|
1800 if (!mConditions.IsEmpty()) { |
|
1801 visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id "); |
|
1802 additionalConditions.AssignLiteral("{QUERY_OPTIONS_VISITS} " |
|
1803 "{QUERY_OPTIONS_PLACES} " |
|
1804 "{ADDITIONAL_CONDITIONS} "); |
|
1805 timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||" |
|
1806 "'&endTime='||:end_time"); |
|
1807 } |
|
1808 |
|
1809 mQueryString = nsPrintfCString( |
|
1810 "SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, " |
|
1811 ":localhost, :localhost, null, null, null, null, null, null, null " |
|
1812 "WHERE EXISTS ( " |
|
1813 "SELECT h.id FROM moz_places h " |
|
1814 "%s " |
|
1815 "WHERE h.hidden = 0 " |
|
1816 "AND h.visit_count > 0 " |
|
1817 "AND h.url BETWEEN 'file://' AND 'file:/~' " |
|
1818 "%s " |
|
1819 "LIMIT 1 " |
|
1820 ") " |
|
1821 "UNION ALL " |
|
1822 "SELECT null, " |
|
1823 "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, " |
|
1824 "host, host, null, null, null, null, null, null, null " |
|
1825 "FROM ( " |
|
1826 "SELECT get_unreversed_host(h.rev_host) AS host " |
|
1827 "FROM moz_places h " |
|
1828 "%s " |
|
1829 "WHERE h.hidden = 0 " |
|
1830 "AND h.rev_host <> '.' " |
|
1831 "AND h.visit_count > 0 " |
|
1832 "%s " |
|
1833 "GROUP BY h.rev_host " |
|
1834 "ORDER BY host ASC " |
|
1835 ") ", |
|
1836 nsINavHistoryQueryOptions::RESULTS_AS_URI, |
|
1837 mSortingMode, |
|
1838 timeConstraints.get(), |
|
1839 visitsJoin.get(), |
|
1840 additionalConditions.get(), |
|
1841 nsINavHistoryQueryOptions::RESULTS_AS_URI, |
|
1842 mSortingMode, |
|
1843 timeConstraints.get(), |
|
1844 visitsJoin.get(), |
|
1845 additionalConditions.get() |
|
1846 ); |
|
1847 |
|
1848 return NS_OK; |
|
1849 } |
|
1850 |
|
1851 nsresult |
|
1852 PlacesSQLQueryBuilder::SelectAsTag() |
|
1853 { |
|
1854 nsNavHistory *history = nsNavHistory::GetHistoryService(); |
|
1855 NS_ENSURE_STATE(history); |
|
1856 |
|
1857 // This allows sorting by date fields what is not possible with |
|
1858 // other history queries. |
|
1859 mHasDateColumns = true; |
|
1860 |
|
1861 mQueryString = nsPrintfCString( |
|
1862 "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', " |
|
1863 "title, null, null, null, null, null, dateAdded, " |
|
1864 "lastModified, null, null, null " |
|
1865 "FROM moz_bookmarks " |
|
1866 "WHERE parent = %lld", |
|
1867 nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS, |
|
1868 nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS, |
|
1869 history->GetTagsFolder() |
|
1870 ); |
|
1871 |
|
1872 return NS_OK; |
|
1873 } |
|
1874 |
|
1875 nsresult |
|
1876 PlacesSQLQueryBuilder::Where() |
|
1877 { |
|
1878 |
|
1879 // Set query options |
|
1880 nsAutoCString additionalVisitsConditions; |
|
1881 nsAutoCString additionalPlacesConditions; |
|
1882 |
|
1883 if (!mIncludeHidden) { |
|
1884 additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 "); |
|
1885 } |
|
1886 |
|
1887 if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) { |
|
1888 // last_visit_date is updated for any kind of visit, so it's a good |
|
1889 // indicator whether the page has visits. |
|
1890 additionalPlacesConditions += NS_LITERAL_CSTRING( |
|
1891 "AND last_visit_date NOTNULL " |
|
1892 ); |
|
1893 } |
|
1894 |
|
1895 if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI && |
|
1896 !additionalVisitsConditions.IsEmpty()) { |
|
1897 // URI results don't join on visits. |
|
1898 nsAutoCString tmp = additionalVisitsConditions; |
|
1899 additionalVisitsConditions = "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id "; |
|
1900 additionalVisitsConditions.Append(tmp); |
|
1901 additionalVisitsConditions.Append("LIMIT 1)"); |
|
1902 } |
|
1903 |
|
1904 mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}", |
|
1905 additionalVisitsConditions.get()); |
|
1906 mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}", |
|
1907 additionalPlacesConditions.get()); |
|
1908 |
|
1909 // If we used WHERE already, we inject the conditions |
|
1910 // in place of {ADDITIONAL_CONDITIONS} |
|
1911 if (mQueryString.Find("{ADDITIONAL_CONDITIONS}", 0) != kNotFound) { |
|
1912 nsAutoCString innerCondition; |
|
1913 // If we have condition AND it |
|
1914 if (!mConditions.IsEmpty()) { |
|
1915 innerCondition = " AND ("; |
|
1916 innerCondition += mConditions; |
|
1917 innerCondition += ")"; |
|
1918 } |
|
1919 mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}", |
|
1920 innerCondition.get()); |
|
1921 |
|
1922 } else if (!mConditions.IsEmpty()) { |
|
1923 |
|
1924 mQueryString += "WHERE "; |
|
1925 mQueryString += mConditions; |
|
1926 |
|
1927 } |
|
1928 return NS_OK; |
|
1929 } |
|
1930 |
|
1931 nsresult |
|
1932 PlacesSQLQueryBuilder::GroupBy() |
|
1933 { |
|
1934 mQueryString += mGroupBy; |
|
1935 return NS_OK; |
|
1936 } |
|
1937 |
|
1938 nsresult |
|
1939 PlacesSQLQueryBuilder::OrderBy() |
|
1940 { |
|
1941 if (mSkipOrderBy) |
|
1942 return NS_OK; |
|
1943 |
|
1944 // Sort clause: we will sort later, but if it comes out of the DB sorted, |
|
1945 // our later sort will be basically free. The DB can sort these for free |
|
1946 // most of the time anyway, because it has indices over these items. |
|
1947 switch(mSortingMode) |
|
1948 { |
|
1949 case nsINavHistoryQueryOptions::SORT_BY_NONE: |
|
1950 // Ensure sorting does not change based on tables status. |
|
1951 if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) { |
|
1952 if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS) |
|
1953 mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC "); |
|
1954 else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) |
|
1955 mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC "); |
|
1956 } |
|
1957 break; |
|
1958 case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING: |
|
1959 case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING: |
|
1960 // If the user wants few results, we limit them by date, necessitating |
|
1961 // a sort by date here (see the IDL definition for maxResults). |
|
1962 // Otherwise we will do actual sorting by title, but since we could need |
|
1963 // to special sort for some locale we will repeat a second sorting at the |
|
1964 // end in nsNavHistoryResult, that should be faster since the list will be |
|
1965 // almost ordered. |
|
1966 if (mMaxResults > 0) |
|
1967 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); |
|
1968 else if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING) |
|
1969 OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title); |
|
1970 else |
|
1971 OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title); |
|
1972 break; |
|
1973 case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING: |
|
1974 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate); |
|
1975 break; |
|
1976 case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING: |
|
1977 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); |
|
1978 break; |
|
1979 case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING: |
|
1980 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL); |
|
1981 break; |
|
1982 case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING: |
|
1983 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL); |
|
1984 break; |
|
1985 case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING: |
|
1986 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount); |
|
1987 break; |
|
1988 case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING: |
|
1989 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount); |
|
1990 break; |
|
1991 case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING: |
|
1992 if (mHasDateColumns) |
|
1993 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded); |
|
1994 break; |
|
1995 case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING: |
|
1996 if (mHasDateColumns) |
|
1997 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded); |
|
1998 break; |
|
1999 case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING: |
|
2000 if (mHasDateColumns) |
|
2001 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified); |
|
2002 break; |
|
2003 case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING: |
|
2004 if (mHasDateColumns) |
|
2005 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified); |
|
2006 break; |
|
2007 case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING: |
|
2008 case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING: |
|
2009 case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING: |
|
2010 case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING: |
|
2011 break; // Sort later in nsNavHistoryQueryResultNode::FillChildren() |
|
2012 case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING: |
|
2013 OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency); |
|
2014 break; |
|
2015 case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING: |
|
2016 OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency); |
|
2017 break; |
|
2018 default: |
|
2019 NS_NOTREACHED("Invalid sorting mode"); |
|
2020 } |
|
2021 return NS_OK; |
|
2022 } |
|
2023 |
|
2024 void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex) |
|
2025 { |
|
2026 mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex+1); |
|
2027 } |
|
2028 |
|
2029 void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex) |
|
2030 { |
|
2031 mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex+1); |
|
2032 } |
|
2033 |
|
2034 void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex) |
|
2035 { |
|
2036 mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC", |
|
2037 aIndex+1); |
|
2038 } |
|
2039 |
|
2040 void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex) |
|
2041 { |
|
2042 mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC", |
|
2043 aIndex+1); |
|
2044 } |
|
2045 |
|
2046 nsresult |
|
2047 PlacesSQLQueryBuilder::Limit() |
|
2048 { |
|
2049 if (mUseLimit && mMaxResults > 0) { |
|
2050 mQueryString += NS_LITERAL_CSTRING(" LIMIT "); |
|
2051 mQueryString.AppendInt(mMaxResults); |
|
2052 mQueryString.AppendLiteral(" "); |
|
2053 } |
|
2054 return NS_OK; |
|
2055 } |
|
2056 |
|
2057 nsresult |
|
2058 nsNavHistory::ConstructQueryString( |
|
2059 const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
2060 nsNavHistoryQueryOptions* aOptions, |
|
2061 nsCString& queryString, |
|
2062 bool& aParamsPresent, |
|
2063 nsNavHistory::StringHash& aAddParams) |
|
2064 { |
|
2065 // For information about visit_type see nsINavHistoryService.idl. |
|
2066 // visitType == 0 is undefined (see bug #375777 for details). |
|
2067 // Some sites, especially Javascript-heavy ones, load things in frames to |
|
2068 // display them, resulting in a lot of these entries. This is the reason |
|
2069 // why such visits are filtered out. |
|
2070 nsresult rv; |
|
2071 aParamsPresent = false; |
|
2072 |
|
2073 int32_t sortingMode = aOptions->SortingMode(); |
|
2074 NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE && |
|
2075 sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING, |
|
2076 "Invalid sortingMode found while building query!"); |
|
2077 |
|
2078 bool hasSearchTerms = false; |
|
2079 for (int32_t i = 0; i < aQueries.Count() && !hasSearchTerms; i++) { |
|
2080 aQueries[i]->GetHasSearchTerms(&hasSearchTerms); |
|
2081 } |
|
2082 |
|
2083 nsAutoCString tagsSqlFragment; |
|
2084 GetTagsSqlFragment(GetTagsFolder(), |
|
2085 NS_LITERAL_CSTRING("h.id"), |
|
2086 hasSearchTerms, |
|
2087 tagsSqlFragment); |
|
2088 |
|
2089 if (IsOptimizableHistoryQuery(aQueries, aOptions, |
|
2090 nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) || |
|
2091 IsOptimizableHistoryQuery(aQueries, aOptions, |
|
2092 nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) { |
|
2093 // Generate an optimized query for the history menu and most visited |
|
2094 // smart bookmark. |
|
2095 queryString = NS_LITERAL_CSTRING( |
|
2096 "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, " |
|
2097 "f.url, null, null, null, null, ") + |
|
2098 tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
2099 "FROM moz_places h " |
|
2100 "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " |
|
2101 "WHERE h.hidden = 0 " |
|
2102 "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id " |
|
2103 "AND visit_type NOT IN ") + |
|
2104 nsPrintfCString("(0,%d,%d) ", |
|
2105 nsINavHistoryService::TRANSITION_EMBED, |
|
2106 nsINavHistoryService::TRANSITION_FRAMED_LINK) + |
|
2107 NS_LITERAL_CSTRING("LIMIT 1) " |
|
2108 "{QUERY_OPTIONS} " |
|
2109 ); |
|
2110 |
|
2111 queryString.Append(NS_LITERAL_CSTRING("ORDER BY ")); |
|
2112 if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) |
|
2113 queryString.Append(NS_LITERAL_CSTRING("last_visit_date DESC ")); |
|
2114 else |
|
2115 queryString.Append(NS_LITERAL_CSTRING("visit_count DESC ")); |
|
2116 |
|
2117 queryString.Append(NS_LITERAL_CSTRING("LIMIT ")); |
|
2118 queryString.AppendInt(aOptions->MaxResults()); |
|
2119 |
|
2120 nsAutoCString additionalQueryOptions; |
|
2121 |
|
2122 queryString.ReplaceSubstring("{QUERY_OPTIONS}", |
|
2123 additionalQueryOptions.get()); |
|
2124 return NS_OK; |
|
2125 } |
|
2126 |
|
2127 nsAutoCString conditions; |
|
2128 for (int32_t i = 0; i < aQueries.Count(); i++) { |
|
2129 nsCString queryClause; |
|
2130 rv = QueryToSelectClause(aQueries[i], aOptions, i, &queryClause); |
|
2131 NS_ENSURE_SUCCESS(rv, rv); |
|
2132 if (! queryClause.IsEmpty()) { |
|
2133 aParamsPresent = true; |
|
2134 if (! conditions.IsEmpty()) // exists previous clause: multiple ones are ORed |
|
2135 conditions += NS_LITERAL_CSTRING(" OR "); |
|
2136 conditions += NS_LITERAL_CSTRING("(") + queryClause + |
|
2137 NS_LITERAL_CSTRING(")"); |
|
2138 } |
|
2139 } |
|
2140 |
|
2141 // Determine whether we can push maxResults constraints into the queries |
|
2142 // as LIMIT, or if we need to do result count clamping later |
|
2143 // using FilterResultSet() |
|
2144 bool useLimitClause = !NeedToFilterResultSet(aQueries, aOptions); |
|
2145 |
|
2146 PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions, |
|
2147 useLimitClause, aAddParams, |
|
2148 hasSearchTerms); |
|
2149 rv = queryStringBuilder.GetQueryString(queryString); |
|
2150 NS_ENSURE_SUCCESS(rv, rv); |
|
2151 |
|
2152 return NS_OK; |
|
2153 } |
|
2154 |
|
2155 PLDHashOperator BindAdditionalParameter(nsNavHistory::StringHash::KeyType aParamName, |
|
2156 nsCString aParamValue, |
|
2157 void* aStatement) |
|
2158 { |
|
2159 mozIStorageStatement* stmt = static_cast<mozIStorageStatement*>(aStatement); |
|
2160 |
|
2161 nsresult rv = stmt->BindUTF8StringByName(aParamName, aParamValue); |
|
2162 if (NS_FAILED(rv)) |
|
2163 return PL_DHASH_STOP; |
|
2164 |
|
2165 return PL_DHASH_NEXT; |
|
2166 } |
|
2167 |
|
2168 // nsNavHistory::GetQueryResults |
|
2169 // |
|
2170 // Call this to get the results from a complex query. This is used by |
|
2171 // nsNavHistoryQueryResultNode to populate its children. For simple bookmark |
|
2172 // queries, use nsNavBookmarks::QueryFolderChildren. |
|
2173 // |
|
2174 // THIS DOES NOT DO SORTING. You will need to sort the container yourself |
|
2175 // when you get the results. This is because sorting depends on tree |
|
2176 // statistics that will be built from the perspective of the tree. See |
|
2177 // nsNavHistoryQueryResultNode::FillChildren |
|
2178 // |
|
2179 // FIXME: This only does keyword searching for the first query, and does |
|
2180 // it ANDed with the all the rest of the queries. |
|
2181 |
|
2182 nsresult |
|
2183 nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode, |
|
2184 const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
2185 nsNavHistoryQueryOptions *aOptions, |
|
2186 nsCOMArray<nsNavHistoryResultNode>* aResults) |
|
2187 { |
|
2188 NS_ENSURE_ARG_POINTER(aOptions); |
|
2189 NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty"); |
|
2190 if (! aQueries.Count()) |
|
2191 return NS_ERROR_INVALID_ARG; |
|
2192 |
|
2193 nsCString queryString; |
|
2194 bool paramsPresent = false; |
|
2195 nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); |
|
2196 nsresult rv = ConstructQueryString(aQueries, aOptions, queryString, |
|
2197 paramsPresent, addParams); |
|
2198 NS_ENSURE_SUCCESS(rv,rv); |
|
2199 |
|
2200 // create statement |
|
2201 nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString); |
|
2202 #ifdef DEBUG |
|
2203 if (!statement) { |
|
2204 nsAutoCString lastErrorString; |
|
2205 (void)mDB->MainConn()->GetLastErrorString(lastErrorString); |
|
2206 int32_t lastError = 0; |
|
2207 (void)mDB->MainConn()->GetLastError(&lastError); |
|
2208 printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", |
|
2209 queryString.get(), lastError, lastErrorString.get()); |
|
2210 } |
|
2211 #endif |
|
2212 NS_ENSURE_STATE(statement); |
|
2213 mozStorageStatementScoper scoper(statement); |
|
2214 |
|
2215 if (paramsPresent) { |
|
2216 // bind parameters |
|
2217 int32_t i; |
|
2218 for (i = 0; i < aQueries.Count(); i++) { |
|
2219 rv = BindQueryClauseParameters(statement, i, aQueries[i], aOptions); |
|
2220 NS_ENSURE_SUCCESS(rv, rv); |
|
2221 } |
|
2222 } |
|
2223 |
|
2224 addParams.EnumerateRead(BindAdditionalParameter, statement.get()); |
|
2225 |
|
2226 // Optimize the case where there is no need for any post-query filtering. |
|
2227 if (NeedToFilterResultSet(aQueries, aOptions)) { |
|
2228 // Generate the top-level results. |
|
2229 nsCOMArray<nsNavHistoryResultNode> toplevel; |
|
2230 rv = ResultsAsList(statement, aOptions, &toplevel); |
|
2231 NS_ENSURE_SUCCESS(rv, rv); |
|
2232 |
|
2233 FilterResultSet(aResultNode, toplevel, aResults, aQueries, aOptions); |
|
2234 } else { |
|
2235 rv = ResultsAsList(statement, aOptions, aResults); |
|
2236 NS_ENSURE_SUCCESS(rv, rv); |
|
2237 } |
|
2238 |
|
2239 return NS_OK; |
|
2240 } |
|
2241 |
|
2242 |
|
2243 // nsNavHistory::AddObserver |
|
2244 |
|
2245 NS_IMETHODIMP |
|
2246 nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak) |
|
2247 { |
|
2248 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2249 NS_ENSURE_ARG(aObserver); |
|
2250 |
|
2251 return mObservers.AppendWeakElement(aObserver, aOwnsWeak); |
|
2252 } |
|
2253 |
|
2254 |
|
2255 // nsNavHistory::RemoveObserver |
|
2256 |
|
2257 NS_IMETHODIMP |
|
2258 nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver) |
|
2259 { |
|
2260 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2261 NS_ENSURE_ARG(aObserver); |
|
2262 |
|
2263 return mObservers.RemoveWeakElement(aObserver); |
|
2264 } |
|
2265 |
|
2266 // nsNavHistory::BeginUpdateBatch |
|
2267 // See RunInBatchMode |
|
2268 nsresult |
|
2269 nsNavHistory::BeginUpdateBatch() |
|
2270 { |
|
2271 if (mBatchLevel++ == 0) { |
|
2272 mBatchDBTransaction = new mozStorageTransaction(mDB->MainConn(), false); |
|
2273 |
|
2274 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
2275 nsINavHistoryObserver, OnBeginUpdateBatch()); |
|
2276 } |
|
2277 return NS_OK; |
|
2278 } |
|
2279 |
|
2280 // nsNavHistory::EndUpdateBatch |
|
2281 nsresult |
|
2282 nsNavHistory::EndUpdateBatch() |
|
2283 { |
|
2284 if (--mBatchLevel == 0) { |
|
2285 if (mBatchDBTransaction) { |
|
2286 DebugOnly<nsresult> rv = mBatchDBTransaction->Commit(); |
|
2287 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Batch failed to commit transaction"); |
|
2288 delete mBatchDBTransaction; |
|
2289 mBatchDBTransaction = nullptr; |
|
2290 } |
|
2291 |
|
2292 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
2293 nsINavHistoryObserver, OnEndUpdateBatch()); |
|
2294 } |
|
2295 return NS_OK; |
|
2296 } |
|
2297 |
|
2298 NS_IMETHODIMP |
|
2299 nsNavHistory::RunInBatchMode(nsINavHistoryBatchCallback* aCallback, |
|
2300 nsISupports* aUserData) |
|
2301 { |
|
2302 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2303 NS_ENSURE_ARG(aCallback); |
|
2304 |
|
2305 UpdateBatchScoper batch(*this); |
|
2306 return aCallback->RunBatched(aUserData); |
|
2307 } |
|
2308 |
|
2309 NS_IMETHODIMP |
|
2310 nsNavHistory::GetHistoryDisabled(bool *_retval) |
|
2311 { |
|
2312 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2313 NS_ENSURE_ARG_POINTER(_retval); |
|
2314 |
|
2315 *_retval = IsHistoryDisabled(); |
|
2316 return NS_OK; |
|
2317 } |
|
2318 |
|
2319 // Browser history ************************************************************* |
|
2320 |
|
2321 |
|
2322 // nsNavHistory::RemovePagesInternal |
|
2323 // |
|
2324 // Deletes a list of placeIds from history. |
|
2325 // This is an internal method used by RemovePages, RemovePagesFromHost and |
|
2326 // RemovePagesByTimeframe. |
|
2327 // Takes a comma separated list of place ids. |
|
2328 // This method does not do any observer notification. |
|
2329 |
|
2330 nsresult |
|
2331 nsNavHistory::RemovePagesInternal(const nsCString& aPlaceIdsQueryString) |
|
2332 { |
|
2333 // Return early if there is nothing to delete. |
|
2334 if (aPlaceIdsQueryString.IsEmpty()) |
|
2335 return NS_OK; |
|
2336 |
|
2337 mozStorageTransaction transaction(mDB->MainConn(), false); |
|
2338 |
|
2339 // Delete all visits for the specified place ids. |
|
2340 nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( |
|
2341 NS_LITERAL_CSTRING( |
|
2342 "DELETE FROM moz_historyvisits WHERE place_id IN (") + |
|
2343 aPlaceIdsQueryString + |
|
2344 NS_LITERAL_CSTRING(")") |
|
2345 ); |
|
2346 NS_ENSURE_SUCCESS(rv, rv); |
|
2347 |
|
2348 rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString); |
|
2349 NS_ENSURE_SUCCESS(rv, rv); |
|
2350 |
|
2351 // Invalidate the cached value for whether there's history or not. |
|
2352 mDaysOfHistory = -1; |
|
2353 |
|
2354 return transaction.Commit(); |
|
2355 } |
|
2356 |
|
2357 |
|
2358 /** |
|
2359 * Performs cleanup on places that just had all their visits removed, including |
|
2360 * deletion of those places. This is an internal method used by |
|
2361 * RemovePagesInternal and RemoveVisitsByTimeframe. This method does not |
|
2362 * execute in a transaction, so callers should make sure they begin one if |
|
2363 * needed. |
|
2364 * |
|
2365 * @param aPlaceIdsQueryString |
|
2366 * A comma-separated list of place IDs, each of which just had all its |
|
2367 * visits removed |
|
2368 */ |
|
2369 nsresult |
|
2370 nsNavHistory::CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString) |
|
2371 { |
|
2372 // Return early if there is nothing to delete. |
|
2373 if (aPlaceIdsQueryString.IsEmpty()) |
|
2374 return NS_OK; |
|
2375 |
|
2376 // Collect about-to-be-deleted URIs to notify onDeleteURI. |
|
2377 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
|
2378 "SELECT h.id, h.url, h.guid, " |
|
2379 "(SUBSTR(h.url, 1, 6) <> 'place:' " |
|
2380 " AND NOT EXISTS (SELECT b.id FROM moz_bookmarks b " |
|
2381 "WHERE b.fk = h.id LIMIT 1)) as whole_entry " |
|
2382 "FROM moz_places h " |
|
2383 "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(")") |
|
2384 ); |
|
2385 NS_ENSURE_STATE(stmt); |
|
2386 mozStorageStatementScoper scoper(stmt); |
|
2387 |
|
2388 nsCString filteredPlaceIds; |
|
2389 nsCOMArray<nsIURI> URIs; |
|
2390 nsTArray<nsCString> GUIDs; |
|
2391 bool hasMore; |
|
2392 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) { |
|
2393 int64_t placeId; |
|
2394 nsresult rv = stmt->GetInt64(0, &placeId); |
|
2395 NS_ENSURE_SUCCESS(rv, rv); |
|
2396 nsAutoCString URLString; |
|
2397 rv = stmt->GetUTF8String(1, URLString); |
|
2398 nsCString guid; |
|
2399 rv = stmt->GetUTF8String(2, guid); |
|
2400 int32_t wholeEntry; |
|
2401 rv = stmt->GetInt32(3, &wholeEntry); |
|
2402 nsCOMPtr<nsIURI> uri; |
|
2403 rv = NS_NewURI(getter_AddRefs(uri), URLString); |
|
2404 NS_ENSURE_SUCCESS(rv, rv); |
|
2405 if (wholeEntry) { |
|
2406 if (!filteredPlaceIds.IsEmpty()) { |
|
2407 filteredPlaceIds.AppendLiteral(","); |
|
2408 } |
|
2409 filteredPlaceIds.AppendInt(placeId); |
|
2410 URIs.AppendObject(uri); |
|
2411 GUIDs.AppendElement(guid); |
|
2412 } |
|
2413 else { |
|
2414 // Notify that we will delete all visits for this page, but not the page |
|
2415 // itself, since it's bookmarked or a place: query. |
|
2416 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
2417 nsINavHistoryObserver, |
|
2418 OnDeleteVisits(uri, 0, guid, nsINavHistoryObserver::REASON_DELETED, 0)); |
|
2419 } |
|
2420 } |
|
2421 |
|
2422 // if the entry is not bookmarked and is not a place: uri |
|
2423 // then we can remove it from moz_places. |
|
2424 // Note that we do NOT delete favicons. Any unreferenced favicons will be |
|
2425 // deleted next time the browser is shut down. |
|
2426 nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( |
|
2427 NS_LITERAL_CSTRING( |
|
2428 "DELETE FROM moz_places WHERE id IN ( " |
|
2429 ) + filteredPlaceIds + NS_LITERAL_CSTRING( |
|
2430 ") " |
|
2431 ) |
|
2432 ); |
|
2433 NS_ENSURE_SUCCESS(rv, rv); |
|
2434 |
|
2435 // Invalidate frecencies of touched places, since they need recalculation. |
|
2436 rv = invalidateFrecencies(aPlaceIdsQueryString); |
|
2437 NS_ENSURE_SUCCESS(rv, rv); |
|
2438 |
|
2439 // Finally notify about the removed URIs. |
|
2440 for (int32_t i = 0; i < URIs.Count(); ++i) { |
|
2441 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
2442 nsINavHistoryObserver, |
|
2443 OnDeleteURI(URIs[i], GUIDs[i], nsINavHistoryObserver::REASON_DELETED)); |
|
2444 } |
|
2445 |
|
2446 return NS_OK; |
|
2447 } |
|
2448 |
|
2449 |
|
2450 // nsNavHistory::RemovePages |
|
2451 // |
|
2452 // Removes a bunch of uris from history. |
|
2453 // Has better performance than RemovePage when deleting a lot of history. |
|
2454 // We don't do duplicates removal, URIs array should be cleaned-up before. |
|
2455 |
|
2456 NS_IMETHODIMP |
|
2457 nsNavHistory::RemovePages(nsIURI **aURIs, uint32_t aLength) |
|
2458 { |
|
2459 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2460 NS_ENSURE_ARG(aURIs); |
|
2461 |
|
2462 nsresult rv; |
|
2463 // build a list of place ids to delete |
|
2464 nsCString deletePlaceIdsQueryString; |
|
2465 for (uint32_t i = 0; i < aLength; i++) { |
|
2466 int64_t placeId; |
|
2467 nsAutoCString guid; |
|
2468 rv = GetIdForPage(aURIs[i], &placeId, guid); |
|
2469 NS_ENSURE_SUCCESS(rv, rv); |
|
2470 if (placeId != 0) { |
|
2471 if (!deletePlaceIdsQueryString.IsEmpty()) |
|
2472 deletePlaceIdsQueryString.AppendLiteral(","); |
|
2473 deletePlaceIdsQueryString.AppendInt(placeId); |
|
2474 } |
|
2475 } |
|
2476 |
|
2477 UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
|
2478 |
|
2479 rv = RemovePagesInternal(deletePlaceIdsQueryString); |
|
2480 NS_ENSURE_SUCCESS(rv, rv); |
|
2481 |
|
2482 // Clear the registered embed visits. |
|
2483 clearEmbedVisits(); |
|
2484 |
|
2485 return NS_OK; |
|
2486 } |
|
2487 |
|
2488 |
|
2489 // nsNavHistory::RemovePage |
|
2490 // |
|
2491 // Removes all visits and the main history entry for the given URI. |
|
2492 // Silently fails if we have no knowledge of the page. |
|
2493 |
|
2494 NS_IMETHODIMP |
|
2495 nsNavHistory::RemovePage(nsIURI *aURI) |
|
2496 { |
|
2497 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2498 NS_ENSURE_ARG(aURI); |
|
2499 |
|
2500 // Build a list of place ids to delete. |
|
2501 int64_t placeId; |
|
2502 nsAutoCString guid; |
|
2503 nsresult rv = GetIdForPage(aURI, &placeId, guid); |
|
2504 NS_ENSURE_SUCCESS(rv, rv); |
|
2505 if (placeId == 0) { |
|
2506 return NS_OK; |
|
2507 } |
|
2508 nsAutoCString deletePlaceIdQueryString; |
|
2509 deletePlaceIdQueryString.AppendInt(placeId); |
|
2510 |
|
2511 rv = RemovePagesInternal(deletePlaceIdQueryString); |
|
2512 NS_ENSURE_SUCCESS(rv, rv); |
|
2513 |
|
2514 // Clear the registered embed visits. |
|
2515 clearEmbedVisits(); |
|
2516 |
|
2517 return NS_OK; |
|
2518 } |
|
2519 |
|
2520 |
|
2521 // nsNavHistory::RemovePagesFromHost |
|
2522 // |
|
2523 // This function will delete all history information about pages from a |
|
2524 // given host. If aEntireDomain is set, we will also delete pages from |
|
2525 // sub hosts (so if we are passed in "microsoft.com" we delete |
|
2526 // "www.microsoft.com", "msdn.microsoft.com", etc.). An empty host name |
|
2527 // means local files and anything else with no host name. You can also pass |
|
2528 // in the localized "(local files)" title given to you from a history query. |
|
2529 // |
|
2530 // Silently fails if we have no knowledge of the host. |
|
2531 // |
|
2532 // This sends onBeginUpdateBatch/onEndUpdateBatch to observers |
|
2533 |
|
2534 NS_IMETHODIMP |
|
2535 nsNavHistory::RemovePagesFromHost(const nsACString& aHost, bool aEntireDomain) |
|
2536 { |
|
2537 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2538 |
|
2539 nsresult rv; |
|
2540 // Local files don't have any host name. We don't want to delete all files in |
|
2541 // history when we get passed an empty string, so force to exact match |
|
2542 if (aHost.IsEmpty()) |
|
2543 aEntireDomain = false; |
|
2544 |
|
2545 // translate "(local files)" to an empty host name |
|
2546 // be sure to use the TitleForDomain to get the localized name |
|
2547 nsCString localFiles; |
|
2548 TitleForDomain(EmptyCString(), localFiles); |
|
2549 nsAutoString host16; |
|
2550 if (!aHost.Equals(localFiles)) |
|
2551 CopyUTF8toUTF16(aHost, host16); |
|
2552 |
|
2553 // nsISupports version of the host string for passing to observers |
|
2554 nsCOMPtr<nsISupportsString> hostSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); |
|
2555 NS_ENSURE_SUCCESS(rv, rv); |
|
2556 rv = hostSupports->SetData(host16); |
|
2557 NS_ENSURE_SUCCESS(rv, rv); |
|
2558 |
|
2559 // see BindQueryClauseParameters for how this host selection works |
|
2560 nsAutoString revHostDot; |
|
2561 GetReversedHostname(host16, revHostDot); |
|
2562 NS_ASSERTION(revHostDot[revHostDot.Length() - 1] == '.', "Invalid rev. host"); |
|
2563 nsAutoString revHostSlash(revHostDot); |
|
2564 revHostSlash.Truncate(revHostSlash.Length() - 1); |
|
2565 revHostSlash.Append(NS_LITERAL_STRING("/")); |
|
2566 |
|
2567 // build condition string based on host selection type |
|
2568 nsAutoCString conditionString; |
|
2569 if (aEntireDomain) |
|
2570 conditionString.AssignLiteral("rev_host >= ?1 AND rev_host < ?2 "); |
|
2571 else |
|
2572 conditionString.AssignLiteral("rev_host = ?1 "); |
|
2573 |
|
2574 // create statement depending on delete type |
|
2575 nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement( |
|
2576 NS_LITERAL_CSTRING("SELECT id FROM moz_places WHERE ") + conditionString |
|
2577 ); |
|
2578 NS_ENSURE_STATE(statement); |
|
2579 mozStorageStatementScoper scoper(statement); |
|
2580 |
|
2581 rv = statement->BindStringByIndex(0, revHostDot); |
|
2582 NS_ENSURE_SUCCESS(rv, rv); |
|
2583 if (aEntireDomain) { |
|
2584 rv = statement->BindStringByIndex(1, revHostSlash); |
|
2585 NS_ENSURE_SUCCESS(rv, rv); |
|
2586 } |
|
2587 |
|
2588 nsCString hostPlaceIds; |
|
2589 bool hasMore = false; |
|
2590 while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { |
|
2591 if (!hostPlaceIds.IsEmpty()) |
|
2592 hostPlaceIds.AppendLiteral(","); |
|
2593 int64_t placeId; |
|
2594 rv = statement->GetInt64(0, &placeId); |
|
2595 NS_ENSURE_SUCCESS(rv, rv); |
|
2596 hostPlaceIds.AppendInt(placeId); |
|
2597 } |
|
2598 |
|
2599 // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
|
2600 UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
|
2601 |
|
2602 rv = RemovePagesInternal(hostPlaceIds); |
|
2603 NS_ENSURE_SUCCESS(rv, rv); |
|
2604 |
|
2605 // Clear the registered embed visits. |
|
2606 clearEmbedVisits(); |
|
2607 |
|
2608 return NS_OK; |
|
2609 } |
|
2610 |
|
2611 |
|
2612 // nsNavHistory::RemovePagesByTimeframe |
|
2613 // |
|
2614 // This function will delete all history information about |
|
2615 // pages for a given timeframe. |
|
2616 // Limits are included: aBeginTime <= timeframe <= aEndTime |
|
2617 // |
|
2618 // This method sends onBeginUpdateBatch/onEndUpdateBatch to observers |
|
2619 |
|
2620 NS_IMETHODIMP |
|
2621 nsNavHistory::RemovePagesByTimeframe(PRTime aBeginTime, PRTime aEndTime) |
|
2622 { |
|
2623 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2624 |
|
2625 nsresult rv; |
|
2626 // build a list of place ids to delete |
|
2627 nsCString deletePlaceIdsQueryString; |
|
2628 |
|
2629 // we only need to know if a place has a visit into the given timeframe |
|
2630 // this query is faster than actually selecting in moz_historyvisits |
|
2631 nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( |
|
2632 "SELECT h.id FROM moz_places h WHERE " |
|
2633 "EXISTS " |
|
2634 "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id " |
|
2635 "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)" |
|
2636 ); |
|
2637 NS_ENSURE_STATE(selectByTime); |
|
2638 mozStorageStatementScoper selectByTimeScoper(selectByTime); |
|
2639 |
|
2640 rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
|
2641 NS_ENSURE_SUCCESS(rv, rv); |
|
2642 rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
|
2643 NS_ENSURE_SUCCESS(rv, rv); |
|
2644 |
|
2645 bool hasMore = false; |
|
2646 while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { |
|
2647 int64_t placeId; |
|
2648 rv = selectByTime->GetInt64(0, &placeId); |
|
2649 NS_ENSURE_SUCCESS(rv, rv); |
|
2650 if (placeId != 0) { |
|
2651 if (!deletePlaceIdsQueryString.IsEmpty()) |
|
2652 deletePlaceIdsQueryString.AppendLiteral(","); |
|
2653 deletePlaceIdsQueryString.AppendInt(placeId); |
|
2654 } |
|
2655 } |
|
2656 |
|
2657 // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
|
2658 UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
|
2659 |
|
2660 rv = RemovePagesInternal(deletePlaceIdsQueryString); |
|
2661 NS_ENSURE_SUCCESS(rv, rv); |
|
2662 |
|
2663 // Clear the registered embed visits. |
|
2664 clearEmbedVisits(); |
|
2665 |
|
2666 return NS_OK; |
|
2667 } |
|
2668 |
|
2669 |
|
2670 /** |
|
2671 * Removes all visits in a given timeframe. Limits are included: |
|
2672 * aBeginTime <= timeframe <= aEndTime. Any place that becomes unvisited |
|
2673 * as a result will also be deleted. |
|
2674 * |
|
2675 * Note that removal is performed in batch, so observers will not be |
|
2676 * notified of individual places that are deleted. Instead they will be |
|
2677 * notified onBeginUpdateBatch and onEndUpdateBatch. |
|
2678 * |
|
2679 * @param aBeginTime |
|
2680 * The start of the timeframe, inclusive |
|
2681 * @param aEndTime |
|
2682 * The end of the timeframe, inclusive |
|
2683 */ |
|
2684 NS_IMETHODIMP |
|
2685 nsNavHistory::RemoveVisitsByTimeframe(PRTime aBeginTime, PRTime aEndTime) |
|
2686 { |
|
2687 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2688 |
|
2689 nsresult rv; |
|
2690 |
|
2691 // Build a list of place IDs whose visits fall entirely within the timespan. |
|
2692 // These places will be deleted by the call to CleanupPlacesOnVisitsDelete |
|
2693 // below. |
|
2694 nsCString deletePlaceIdsQueryString; |
|
2695 { |
|
2696 nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( |
|
2697 "SELECT place_id " |
|
2698 "FROM moz_historyvisits " |
|
2699 "WHERE :from_date <= visit_date AND visit_date <= :to_date " |
|
2700 "EXCEPT " |
|
2701 "SELECT place_id " |
|
2702 "FROM moz_historyvisits " |
|
2703 "WHERE visit_date < :from_date OR :to_date < visit_date" |
|
2704 ); |
|
2705 NS_ENSURE_STATE(selectByTime); |
|
2706 mozStorageStatementScoper selectByTimeScoper(selectByTime); |
|
2707 rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
|
2708 NS_ENSURE_SUCCESS(rv, rv); |
|
2709 rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
|
2710 NS_ENSURE_SUCCESS(rv, rv); |
|
2711 |
|
2712 bool hasMore = false; |
|
2713 while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { |
|
2714 int64_t placeId; |
|
2715 rv = selectByTime->GetInt64(0, &placeId); |
|
2716 NS_ENSURE_SUCCESS(rv, rv); |
|
2717 // placeId should not be <= 0, but be defensive. |
|
2718 if (placeId > 0) { |
|
2719 if (!deletePlaceIdsQueryString.IsEmpty()) |
|
2720 deletePlaceIdsQueryString.AppendLiteral(","); |
|
2721 deletePlaceIdsQueryString.AppendInt(placeId); |
|
2722 } |
|
2723 } |
|
2724 } |
|
2725 |
|
2726 // force a full refresh calling onEndUpdateBatch (will call Refresh()) |
|
2727 UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers |
|
2728 |
|
2729 mozStorageTransaction transaction(mDB->MainConn(), false); |
|
2730 |
|
2731 // Delete all visits within the timeframe. |
|
2732 nsCOMPtr<mozIStorageStatement> deleteVisitsStmt = mDB->GetStatement( |
|
2733 "DELETE FROM moz_historyvisits " |
|
2734 "WHERE :from_date <= visit_date AND visit_date <= :to_date" |
|
2735 ); |
|
2736 NS_ENSURE_STATE(deleteVisitsStmt); |
|
2737 mozStorageStatementScoper deletevisitsScoper(deleteVisitsStmt); |
|
2738 |
|
2739 rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); |
|
2740 NS_ENSURE_SUCCESS(rv, rv); |
|
2741 rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); |
|
2742 NS_ENSURE_SUCCESS(rv, rv); |
|
2743 rv = deleteVisitsStmt->Execute(); |
|
2744 NS_ENSURE_SUCCESS(rv, rv); |
|
2745 |
|
2746 rv = CleanupPlacesOnVisitsDelete(deletePlaceIdsQueryString); |
|
2747 NS_ENSURE_SUCCESS(rv, rv); |
|
2748 |
|
2749 rv = transaction.Commit(); |
|
2750 NS_ENSURE_SUCCESS(rv, rv); |
|
2751 |
|
2752 // Clear the registered embed visits. |
|
2753 clearEmbedVisits(); |
|
2754 |
|
2755 // Invalidate the cached value for whether there's history or not. |
|
2756 mDaysOfHistory = -1; |
|
2757 |
|
2758 return NS_OK; |
|
2759 } |
|
2760 |
|
2761 |
|
2762 // nsNavHistory::RemoveAllPages |
|
2763 // |
|
2764 // This function is used to clear history. |
|
2765 |
|
2766 NS_IMETHODIMP |
|
2767 nsNavHistory::RemoveAllPages() |
|
2768 { |
|
2769 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2770 |
|
2771 nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
2772 "DELETE FROM moz_historyvisits" |
|
2773 )); |
|
2774 NS_ENSURE_SUCCESS(rv, rv); |
|
2775 |
|
2776 // Clear the registered embed visits. |
|
2777 clearEmbedVisits(); |
|
2778 |
|
2779 // Update the cached value for whether there's history or not. |
|
2780 mDaysOfHistory = 0; |
|
2781 |
|
2782 // Expiration will take care of orphans. |
|
2783 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
2784 nsINavHistoryObserver, OnClearHistory()); |
|
2785 |
|
2786 // Invalidate frecencies for the remaining places. This must happen |
|
2787 // after the notification to ensure it runs enqueued to expiration. |
|
2788 rv = invalidateFrecencies(EmptyCString()); |
|
2789 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to fix invalid frecencies"); |
|
2790 |
|
2791 return NS_OK; |
|
2792 } |
|
2793 |
|
2794 |
|
2795 // Call this method before visiting a URL in order to help determine the |
|
2796 // transition type of the visit. |
|
2797 // |
|
2798 // @see MarkPageAsFollowedBookmark |
|
2799 |
|
2800 NS_IMETHODIMP |
|
2801 nsNavHistory::MarkPageAsTyped(nsIURI *aURI) |
|
2802 { |
|
2803 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2804 NS_ENSURE_ARG(aURI); |
|
2805 |
|
2806 // don't add when history is disabled |
|
2807 if (IsHistoryDisabled()) |
|
2808 return NS_OK; |
|
2809 |
|
2810 nsAutoCString uriString; |
|
2811 nsresult rv = aURI->GetSpec(uriString); |
|
2812 NS_ENSURE_SUCCESS(rv, rv); |
|
2813 |
|
2814 // if URL is already in the typed queue, then we need to remove the old one |
|
2815 int64_t unusedEventTime; |
|
2816 if (mRecentTyped.Get(uriString, &unusedEventTime)) |
|
2817 mRecentTyped.Remove(uriString); |
|
2818 |
|
2819 if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
|
2820 ExpireNonrecentEvents(&mRecentTyped); |
|
2821 |
|
2822 mRecentTyped.Put(uriString, GetNow()); |
|
2823 return NS_OK; |
|
2824 } |
|
2825 |
|
2826 |
|
2827 // Call this method before visiting a URL in order to help determine the |
|
2828 // transition type of the visit. |
|
2829 // |
|
2830 // @see MarkPageAsTyped |
|
2831 |
|
2832 NS_IMETHODIMP |
|
2833 nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI) |
|
2834 { |
|
2835 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2836 NS_ENSURE_ARG(aURI); |
|
2837 |
|
2838 // don't add when history is disabled |
|
2839 if (IsHistoryDisabled()) |
|
2840 return NS_OK; |
|
2841 |
|
2842 nsAutoCString uriString; |
|
2843 nsresult rv = aURI->GetSpec(uriString); |
|
2844 NS_ENSURE_SUCCESS(rv, rv); |
|
2845 |
|
2846 // if URL is already in the links queue, then we need to remove the old one |
|
2847 int64_t unusedEventTime; |
|
2848 if (mRecentLink.Get(uriString, &unusedEventTime)) |
|
2849 mRecentLink.Remove(uriString); |
|
2850 |
|
2851 if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) |
|
2852 ExpireNonrecentEvents(&mRecentLink); |
|
2853 |
|
2854 mRecentLink.Put(uriString, GetNow()); |
|
2855 return NS_OK; |
|
2856 } |
|
2857 |
|
2858 |
|
2859 // nsNavHistory::SetCharsetForURI |
|
2860 // |
|
2861 // Sets the character-set for a URI. |
|
2862 // If aCharset is empty remove character-set annotation for aURI. |
|
2863 |
|
2864 NS_IMETHODIMP |
|
2865 nsNavHistory::SetCharsetForURI(nsIURI* aURI, |
|
2866 const nsAString& aCharset) |
|
2867 { |
|
2868 PLACES_WARN_DEPRECATED(); |
|
2869 |
|
2870 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2871 NS_ENSURE_ARG(aURI); |
|
2872 |
|
2873 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); |
|
2874 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); |
|
2875 |
|
2876 if (aCharset.IsEmpty()) { |
|
2877 // remove the current page character-set annotation |
|
2878 nsresult rv = annosvc->RemovePageAnnotation(aURI, CHARSET_ANNO); |
|
2879 NS_ENSURE_SUCCESS(rv, rv); |
|
2880 } |
|
2881 else { |
|
2882 // Set page character-set annotation, silently overwrite if already exists |
|
2883 nsresult rv = annosvc->SetPageAnnotationString(aURI, CHARSET_ANNO, |
|
2884 aCharset, 0, |
|
2885 nsAnnotationService::EXPIRE_NEVER); |
|
2886 if (rv == NS_ERROR_INVALID_ARG) { |
|
2887 // We don't have this page. Silently fail. |
|
2888 return NS_OK; |
|
2889 } |
|
2890 else if (NS_FAILED(rv)) |
|
2891 return rv; |
|
2892 } |
|
2893 |
|
2894 return NS_OK; |
|
2895 } |
|
2896 |
|
2897 |
|
2898 // nsNavHistory::GetCharsetForURI |
|
2899 // |
|
2900 // Get the last saved character-set for a URI. |
|
2901 |
|
2902 NS_IMETHODIMP |
|
2903 nsNavHistory::GetCharsetForURI(nsIURI* aURI, |
|
2904 nsAString& aCharset) |
|
2905 { |
|
2906 PLACES_WARN_DEPRECATED(); |
|
2907 |
|
2908 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2909 NS_ENSURE_ARG(aURI); |
|
2910 |
|
2911 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); |
|
2912 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); |
|
2913 |
|
2914 nsAutoString charset; |
|
2915 nsresult rv = annosvc->GetPageAnnotationString(aURI, CHARSET_ANNO, aCharset); |
|
2916 if (NS_FAILED(rv)) { |
|
2917 // be sure to return an empty string if character-set is not found |
|
2918 aCharset.Truncate(); |
|
2919 } |
|
2920 return NS_OK; |
|
2921 } |
|
2922 |
|
2923 |
|
2924 NS_IMETHODIMP |
|
2925 nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle) |
|
2926 { |
|
2927 PLACES_WARN_DEPRECATED(); |
|
2928 |
|
2929 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
2930 NS_ENSURE_ARG(aURI); |
|
2931 |
|
2932 aTitle.Truncate(0); |
|
2933 |
|
2934 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( |
|
2935 "SELECT id, url, title, rev_host, visit_count, guid " |
|
2936 "FROM moz_places " |
|
2937 "WHERE url = :page_url " |
|
2938 ); |
|
2939 NS_ENSURE_STATE(stmt); |
|
2940 mozStorageStatementScoper scoper(stmt); |
|
2941 |
|
2942 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
|
2943 NS_ENSURE_SUCCESS(rv, rv); |
|
2944 |
|
2945 bool hasResults = false; |
|
2946 rv = stmt->ExecuteStep(&hasResults); |
|
2947 NS_ENSURE_SUCCESS(rv, rv); |
|
2948 |
|
2949 if (!hasResults) { |
|
2950 aTitle.SetIsVoid(true); |
|
2951 return NS_OK; // Not found, return a void string. |
|
2952 } |
|
2953 |
|
2954 rv = stmt->GetString(2, aTitle); |
|
2955 NS_ENSURE_SUCCESS(rv, rv); |
|
2956 |
|
2957 return NS_OK; |
|
2958 } |
|
2959 |
|
2960 |
|
2961 //////////////////////////////////////////////////////////////////////////////// |
|
2962 //// mozIStorageVacuumParticipant |
|
2963 |
|
2964 NS_IMETHODIMP |
|
2965 nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection) |
|
2966 { |
|
2967 return GetDBConnection(_DBConnection); |
|
2968 } |
|
2969 |
|
2970 |
|
2971 NS_IMETHODIMP |
|
2972 nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize) |
|
2973 { |
|
2974 NS_ENSURE_STATE(mDB); |
|
2975 NS_ENSURE_STATE(mDB->MainConn()); |
|
2976 return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize); |
|
2977 } |
|
2978 |
|
2979 |
|
2980 NS_IMETHODIMP |
|
2981 nsNavHistory::OnBeginVacuum(bool* _vacuumGranted) |
|
2982 { |
|
2983 // TODO: Check if we have to deny the vacuum in some heavy-load case. |
|
2984 // We could maybe want to do that during batches? |
|
2985 *_vacuumGranted = true; |
|
2986 return NS_OK; |
|
2987 } |
|
2988 |
|
2989 |
|
2990 NS_IMETHODIMP |
|
2991 nsNavHistory::OnEndVacuum(bool aSucceeded) |
|
2992 { |
|
2993 NS_WARN_IF_FALSE(aSucceeded, "Places.sqlite vacuum failed."); |
|
2994 return NS_OK; |
|
2995 } |
|
2996 |
|
2997 |
|
2998 //////////////////////////////////////////////////////////////////////////////// |
|
2999 //// nsPIPlacesDatabase |
|
3000 |
|
3001 NS_IMETHODIMP |
|
3002 nsNavHistory::GetDBConnection(mozIStorageConnection **_DBConnection) |
|
3003 { |
|
3004 NS_ENSURE_ARG_POINTER(_DBConnection); |
|
3005 nsRefPtr<mozIStorageConnection> connection = mDB->MainConn(); |
|
3006 connection.forget(_DBConnection); |
|
3007 |
|
3008 return NS_OK; |
|
3009 } |
|
3010 |
|
3011 NS_IMETHODIMP |
|
3012 nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries, |
|
3013 uint32_t aQueryCount, |
|
3014 nsINavHistoryQueryOptions* aOptions, |
|
3015 mozIStorageStatementCallback* aCallback, |
|
3016 mozIStoragePendingStatement** _stmt) |
|
3017 { |
|
3018 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
3019 NS_ENSURE_ARG(aQueries); |
|
3020 NS_ENSURE_ARG(aOptions); |
|
3021 NS_ENSURE_ARG(aCallback); |
|
3022 NS_ENSURE_ARG_POINTER(_stmt); |
|
3023 |
|
3024 nsCOMArray<nsNavHistoryQuery> queries; |
|
3025 for (uint32_t i = 0; i < aQueryCount; i ++) { |
|
3026 nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i]); |
|
3027 NS_ENSURE_STATE(query); |
|
3028 queries.AppendObject(query); |
|
3029 } |
|
3030 NS_ENSURE_ARG_MIN(queries.Count(), 1); |
|
3031 |
|
3032 nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); |
|
3033 NS_ENSURE_ARG(options); |
|
3034 |
|
3035 nsCString queryString; |
|
3036 bool paramsPresent = false; |
|
3037 nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); |
|
3038 nsresult rv = ConstructQueryString(queries, options, queryString, |
|
3039 paramsPresent, addParams); |
|
3040 NS_ENSURE_SUCCESS(rv,rv); |
|
3041 |
|
3042 nsCOMPtr<mozIStorageAsyncStatement> statement = |
|
3043 mDB->GetAsyncStatement(queryString); |
|
3044 NS_ENSURE_STATE(statement); |
|
3045 |
|
3046 #ifdef DEBUG |
|
3047 if (NS_FAILED(rv)) { |
|
3048 nsAutoCString lastErrorString; |
|
3049 (void)mDB->MainConn()->GetLastErrorString(lastErrorString); |
|
3050 int32_t lastError = 0; |
|
3051 (void)mDB->MainConn()->GetLastError(&lastError); |
|
3052 printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", |
|
3053 queryString.get(), lastError, lastErrorString.get()); |
|
3054 } |
|
3055 #endif |
|
3056 NS_ENSURE_SUCCESS(rv, rv); |
|
3057 |
|
3058 if (paramsPresent) { |
|
3059 // bind parameters |
|
3060 int32_t i; |
|
3061 for (i = 0; i < queries.Count(); i++) { |
|
3062 rv = BindQueryClauseParameters(statement, i, queries[i], options); |
|
3063 NS_ENSURE_SUCCESS(rv, rv); |
|
3064 } |
|
3065 } |
|
3066 addParams.EnumerateRead(BindAdditionalParameter, statement.get()); |
|
3067 |
|
3068 rv = statement->ExecuteAsync(aCallback, _stmt); |
|
3069 NS_ENSURE_SUCCESS(rv, rv); |
|
3070 |
|
3071 return NS_OK; |
|
3072 } |
|
3073 |
|
3074 |
|
3075 // nsPIPlacesHistoryListenersNotifier ****************************************** |
|
3076 |
|
3077 NS_IMETHODIMP |
|
3078 nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime, |
|
3079 bool aWholeEntry, const nsACString& aGUID, |
|
3080 uint16_t aReason, uint32_t aTransitionType) |
|
3081 { |
|
3082 // Invalidate the cached value for whether there's history or not. |
|
3083 mDaysOfHistory = -1; |
|
3084 |
|
3085 MOZ_ASSERT(!aGUID.IsEmpty()); |
|
3086 if (aWholeEntry) { |
|
3087 // Notify our observers that the page has been removed. |
|
3088 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
3089 nsINavHistoryObserver, OnDeleteURI(aURI, aGUID, aReason)); |
|
3090 } |
|
3091 else { |
|
3092 // Notify our observers that some visits for the page have been removed. |
|
3093 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
3094 nsINavHistoryObserver, |
|
3095 OnDeleteVisits(aURI, aVisitTime, aGUID, aReason, |
|
3096 aTransitionType)); |
|
3097 } |
|
3098 |
|
3099 return NS_OK; |
|
3100 } |
|
3101 |
|
3102 //////////////////////////////////////////////////////////////////////////////// |
|
3103 //// nsIObserver |
|
3104 |
|
3105 NS_IMETHODIMP |
|
3106 nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic, |
|
3107 const char16_t *aData) |
|
3108 { |
|
3109 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
3110 |
|
3111 if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 || |
|
3112 strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0) { |
|
3113 // These notifications are used by tests to simulate a Places shutdown. |
|
3114 // They should just be forwarded to the Database handle. |
|
3115 mDB->Observe(aSubject, aTopic, aData); |
|
3116 } |
|
3117 |
|
3118 else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) { |
|
3119 // Don't even try to notify observers from this point on, the category |
|
3120 // cache would init services that could try to use our APIs. |
|
3121 mCanNotify = false; |
|
3122 } |
|
3123 |
|
3124 #ifdef MOZ_XUL |
|
3125 else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) { |
|
3126 nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject); |
|
3127 if (!input) |
|
3128 return NS_OK; |
|
3129 |
|
3130 // If the source is a private window, don't add any input history. |
|
3131 bool isPrivate; |
|
3132 nsresult rv = input->GetInPrivateContext(&isPrivate); |
|
3133 NS_ENSURE_SUCCESS(rv, rv); |
|
3134 if (isPrivate) |
|
3135 return NS_OK; |
|
3136 |
|
3137 nsCOMPtr<nsIAutoCompletePopup> popup; |
|
3138 input->GetPopup(getter_AddRefs(popup)); |
|
3139 if (!popup) |
|
3140 return NS_OK; |
|
3141 |
|
3142 nsCOMPtr<nsIAutoCompleteController> controller; |
|
3143 input->GetController(getter_AddRefs(controller)); |
|
3144 if (!controller) |
|
3145 return NS_OK; |
|
3146 |
|
3147 // Don't bother if the popup is closed |
|
3148 bool open; |
|
3149 rv = popup->GetPopupOpen(&open); |
|
3150 NS_ENSURE_SUCCESS(rv, rv); |
|
3151 if (!open) |
|
3152 return NS_OK; |
|
3153 |
|
3154 // Ignore if nothing selected from the popup |
|
3155 int32_t selectedIndex; |
|
3156 rv = popup->GetSelectedIndex(&selectedIndex); |
|
3157 NS_ENSURE_SUCCESS(rv, rv); |
|
3158 if (selectedIndex == -1) |
|
3159 return NS_OK; |
|
3160 |
|
3161 rv = AutoCompleteFeedback(selectedIndex, controller); |
|
3162 NS_ENSURE_SUCCESS(rv, rv); |
|
3163 } |
|
3164 |
|
3165 #endif |
|
3166 else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) { |
|
3167 LoadPrefs(); |
|
3168 } |
|
3169 |
|
3170 else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) { |
|
3171 (void)DecayFrecency(); |
|
3172 } |
|
3173 |
|
3174 return NS_OK; |
|
3175 } |
|
3176 |
|
3177 |
|
3178 namespace { |
|
3179 |
|
3180 class DecayFrecencyCallback : public AsyncStatementTelemetryTimer |
|
3181 { |
|
3182 public: |
|
3183 DecayFrecencyCallback() |
|
3184 : AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS) |
|
3185 { |
|
3186 } |
|
3187 |
|
3188 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
3189 { |
|
3190 (void)AsyncStatementTelemetryTimer::HandleCompletion(aReason); |
|
3191 if (aReason == REASON_FINISHED) { |
|
3192 nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
|
3193 NS_ENSURE_STATE(navHistory); |
|
3194 navHistory->NotifyManyFrecenciesChanged(); |
|
3195 } |
|
3196 return NS_OK; |
|
3197 } |
|
3198 }; |
|
3199 |
|
3200 } // anonymous namespace |
|
3201 |
|
3202 nsresult |
|
3203 nsNavHistory::DecayFrecency() |
|
3204 { |
|
3205 nsresult rv = FixInvalidFrecencies(); |
|
3206 NS_ENSURE_SUCCESS(rv, rv); |
|
3207 |
|
3208 // Globally decay places frecency rankings to estimate reduced frecency |
|
3209 // values of pages that haven't been visited for a while, i.e., they do |
|
3210 // not get an updated frecency. A scaling factor of .975 results in .5 the |
|
3211 // original value after 28 days. |
|
3212 // When changing the scaling factor, ensure that the barrier in |
|
3213 // moz_places_afterupdate_frecency_trigger still ignores these changes. |
|
3214 nsCOMPtr<mozIStorageAsyncStatement> decayFrecency = mDB->GetAsyncStatement( |
|
3215 "UPDATE moz_places SET frecency = ROUND(frecency * .975) " |
|
3216 "WHERE frecency > 0" |
|
3217 ); |
|
3218 NS_ENSURE_STATE(decayFrecency); |
|
3219 |
|
3220 // Decay potentially unused adaptive entries (e.g. those that are at 1) |
|
3221 // to allow better chances for new entries that will start at 1. |
|
3222 nsCOMPtr<mozIStorageAsyncStatement> decayAdaptive = mDB->GetAsyncStatement( |
|
3223 "UPDATE moz_inputhistory SET use_count = use_count * .975" |
|
3224 ); |
|
3225 NS_ENSURE_STATE(decayAdaptive); |
|
3226 |
|
3227 // Delete any adaptive entries that won't help in ordering anymore. |
|
3228 nsCOMPtr<mozIStorageAsyncStatement> deleteAdaptive = mDB->GetAsyncStatement( |
|
3229 "DELETE FROM moz_inputhistory WHERE use_count < .01" |
|
3230 ); |
|
3231 NS_ENSURE_STATE(deleteAdaptive); |
|
3232 |
|
3233 mozIStorageBaseStatement *stmts[] = { |
|
3234 decayFrecency.get(), |
|
3235 decayAdaptive.get(), |
|
3236 deleteAdaptive.get() |
|
3237 }; |
|
3238 nsCOMPtr<mozIStoragePendingStatement> ps; |
|
3239 nsRefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback(); |
|
3240 rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, |
|
3241 getter_AddRefs(ps)); |
|
3242 NS_ENSURE_SUCCESS(rv, rv); |
|
3243 |
|
3244 return NS_OK; |
|
3245 } |
|
3246 |
|
3247 |
|
3248 // Query stuff ***************************************************************** |
|
3249 |
|
3250 // Helper class for QueryToSelectClause |
|
3251 // |
|
3252 // This class helps to build part of the WHERE clause. It supports |
|
3253 // multiple queries by appending the query index to the parameter name. |
|
3254 // For the query with index 0 the parameter name is not altered what |
|
3255 // allows using this parameter in other situations (see SelectAsSite). |
|
3256 |
|
3257 class ConditionBuilder |
|
3258 { |
|
3259 public: |
|
3260 |
|
3261 ConditionBuilder(int32_t aQueryIndex): mQueryIndex(aQueryIndex) |
|
3262 { } |
|
3263 |
|
3264 ConditionBuilder& Condition(const char* aStr) |
|
3265 { |
|
3266 if (!mClause.IsEmpty()) |
|
3267 mClause.AppendLiteral(" AND "); |
|
3268 Str(aStr); |
|
3269 return *this; |
|
3270 } |
|
3271 |
|
3272 ConditionBuilder& Str(const char* aStr) |
|
3273 { |
|
3274 mClause.Append(' '); |
|
3275 mClause.Append(aStr); |
|
3276 mClause.Append(' '); |
|
3277 return *this; |
|
3278 } |
|
3279 |
|
3280 ConditionBuilder& Param(const char* aParam) |
|
3281 { |
|
3282 mClause.Append(' '); |
|
3283 if (!mQueryIndex) |
|
3284 mClause.Append(aParam); |
|
3285 else |
|
3286 mClause += nsPrintfCString("%s%d", aParam, mQueryIndex); |
|
3287 |
|
3288 mClause.Append(' '); |
|
3289 return *this; |
|
3290 } |
|
3291 |
|
3292 void GetClauseString(nsCString& aResult) |
|
3293 { |
|
3294 aResult = mClause; |
|
3295 } |
|
3296 |
|
3297 private: |
|
3298 |
|
3299 int32_t mQueryIndex; |
|
3300 nsCString mClause; |
|
3301 }; |
|
3302 |
|
3303 |
|
3304 // nsNavHistory::QueryToSelectClause |
|
3305 // |
|
3306 // THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters |
|
3307 // |
|
3308 // I don't check return values from the query object getters because there's |
|
3309 // no way for those to fail. |
|
3310 |
|
3311 nsresult |
|
3312 nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const |
|
3313 nsNavHistoryQueryOptions* aOptions, |
|
3314 int32_t aQueryIndex, |
|
3315 nsCString* aClause) |
|
3316 { |
|
3317 bool hasIt; |
|
3318 bool excludeQueries = aOptions->ExcludeQueries(); |
|
3319 |
|
3320 ConditionBuilder clause(aQueryIndex); |
|
3321 |
|
3322 if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) || |
|
3323 (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) { |
|
3324 clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits " |
|
3325 "WHERE place_id = h.id"); |
|
3326 // begin time |
|
3327 if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) |
|
3328 clause.Condition("visit_date >=").Param(":begin_time"); |
|
3329 // end time |
|
3330 if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) |
|
3331 clause.Condition("visit_date <=").Param(":end_time"); |
|
3332 clause.Str(" LIMIT 1)"); |
|
3333 } |
|
3334 |
|
3335 // search terms |
|
3336 bool hasSearchTerms; |
|
3337 if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasSearchTerms)) && hasSearchTerms) { |
|
3338 // Re-use the autocomplete_match function. Setting the behavior to 0 |
|
3339 // it can match everything and work as a nice case insensitive comparator. |
|
3340 clause.Condition("AUTOCOMPLETE_MATCH(").Param(":search_string") |
|
3341 .Str(", h.url, page_title, tags, ") |
|
3342 .Str(nsPrintfCString("0, 0, 0, 0, %d, 0)", |
|
3343 mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED).get()); |
|
3344 // Serching by terms implicitly exclude queries. |
|
3345 excludeQueries = true; |
|
3346 } |
|
3347 |
|
3348 // min and max visit count |
|
3349 if (aQuery->MinVisits() >= 0) |
|
3350 clause.Condition("h.visit_count >=").Param(":min_visits"); |
|
3351 |
|
3352 if (aQuery->MaxVisits() >= 0) |
|
3353 clause.Condition("h.visit_count <=").Param(":max_visits"); |
|
3354 |
|
3355 // only bookmarked, has no affect on bookmarks-only queries |
|
3356 if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS && |
|
3357 aQuery->OnlyBookmarked()) |
|
3358 clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ") |
|
3359 .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get()) |
|
3360 .Str("AND b.fk = h.id)"); |
|
3361 |
|
3362 // domain |
|
3363 if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { |
|
3364 bool domainIsHost = false; |
|
3365 aQuery->GetDomainIsHost(&domainIsHost); |
|
3366 if (domainIsHost) |
|
3367 clause.Condition("h.rev_host =").Param(":domain_lower"); |
|
3368 else |
|
3369 // see domain setting in BindQueryClauseParameters for why we do this |
|
3370 clause.Condition("h.rev_host >=").Param(":domain_lower") |
|
3371 .Condition("h.rev_host <").Param(":domain_upper"); |
|
3372 } |
|
3373 |
|
3374 // URI |
|
3375 if (NS_SUCCEEDED(aQuery->GetHasUri(&hasIt)) && hasIt) { |
|
3376 if (aQuery->UriIsPrefix()) { |
|
3377 clause.Condition("h.url >= ").Param(":uri") |
|
3378 .Condition("h.url <= ").Param(":uri_upper"); |
|
3379 } |
|
3380 else |
|
3381 clause.Condition("h.url =").Param(":uri"); |
|
3382 } |
|
3383 |
|
3384 // annotation |
|
3385 aQuery->GetHasAnnotation(&hasIt); |
|
3386 if (hasIt) { |
|
3387 clause.Condition(""); |
|
3388 if (aQuery->AnnotationIsNot()) |
|
3389 clause.Str("NOT"); |
|
3390 clause.Str( |
|
3391 "EXISTS " |
|
3392 "(SELECT h.id " |
|
3393 "FROM moz_annos anno " |
|
3394 "JOIN moz_anno_attributes annoname " |
|
3395 "ON anno.anno_attribute_id = annoname.id " |
|
3396 "WHERE anno.place_id = h.id " |
|
3397 "AND annoname.name = ").Param(":anno").Str(")"); |
|
3398 // annotation-based queries don't get the common conditions, so you get |
|
3399 // all URLs with that annotation |
|
3400 } |
|
3401 |
|
3402 // tags |
|
3403 const nsTArray<nsString> &tags = aQuery->Tags(); |
|
3404 if (tags.Length() > 0) { |
|
3405 clause.Condition("h.id"); |
|
3406 if (aQuery->TagsAreNot()) |
|
3407 clause.Str("NOT"); |
|
3408 clause.Str( |
|
3409 "IN " |
|
3410 "(SELECT bms.fk " |
|
3411 "FROM moz_bookmarks bms " |
|
3412 "JOIN moz_bookmarks tags ON bms.parent = tags.id " |
|
3413 "WHERE tags.parent ="). |
|
3414 Param(":tags_folder"). |
|
3415 Str("AND tags.title IN ("); |
|
3416 for (uint32_t i = 0; i < tags.Length(); ++i) { |
|
3417 nsPrintfCString param(":tag%d_", i); |
|
3418 clause.Param(param.get()); |
|
3419 if (i < tags.Length() - 1) |
|
3420 clause.Str(","); |
|
3421 } |
|
3422 clause.Str(")"); |
|
3423 if (!aQuery->TagsAreNot()) |
|
3424 clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count"); |
|
3425 clause.Str(")"); |
|
3426 } |
|
3427 |
|
3428 // transitions |
|
3429 const nsTArray<uint32_t>& transitions = aQuery->Transitions(); |
|
3430 for (uint32_t i = 0; i < transitions.Length(); ++i) { |
|
3431 nsPrintfCString param(":transition%d_", i); |
|
3432 clause.Condition("h.id IN (SELECT place_id FROM moz_historyvisits " |
|
3433 "WHERE visit_type = ") |
|
3434 .Param(param.get()) |
|
3435 .Str(")"); |
|
3436 } |
|
3437 |
|
3438 // folders |
|
3439 const nsTArray<int64_t>& folders = aQuery->Folders(); |
|
3440 if (folders.Length() > 0) { |
|
3441 nsTArray<int64_t> includeFolders; |
|
3442 includeFolders.AppendElements(folders); |
|
3443 |
|
3444 nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
3445 NS_ENSURE_STATE(bookmarks); |
|
3446 |
|
3447 for (nsTArray<int64_t>::size_type i = 0; i < folders.Length(); ++i) { |
|
3448 nsTArray<int64_t> subFolders; |
|
3449 if (NS_FAILED(bookmarks->GetDescendantFolders(folders[i], subFolders))) |
|
3450 continue; |
|
3451 includeFolders.AppendElements(subFolders); |
|
3452 } |
|
3453 |
|
3454 clause.Condition("b.parent IN("); |
|
3455 for (nsTArray<int64_t>::size_type i = 0; i < includeFolders.Length(); ++i) { |
|
3456 clause.Str(nsPrintfCString("%lld", includeFolders[i]).get()); |
|
3457 if (i < includeFolders.Length() - 1) { |
|
3458 clause.Str(","); |
|
3459 } |
|
3460 } |
|
3461 clause.Str(")"); |
|
3462 } |
|
3463 |
|
3464 if (excludeQueries) { |
|
3465 // Serching by terms implicitly exclude queries. |
|
3466 clause.Condition("NOT h.url BETWEEN 'place:' AND 'place;'"); |
|
3467 } |
|
3468 |
|
3469 clause.GetClauseString(*aClause); |
|
3470 return NS_OK; |
|
3471 } |
|
3472 |
|
3473 |
|
3474 // nsNavHistory::BindQueryClauseParameters |
|
3475 // |
|
3476 // THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause |
|
3477 |
|
3478 nsresult |
|
3479 nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement, |
|
3480 int32_t aQueryIndex, |
|
3481 nsNavHistoryQuery* aQuery, // const |
|
3482 nsNavHistoryQueryOptions* aOptions) |
|
3483 { |
|
3484 nsresult rv; |
|
3485 |
|
3486 bool hasIt; |
|
3487 // Append numbered index to param names, to replace them correctly in |
|
3488 // case of multiple queries. If we have just one query we don't change the |
|
3489 // param name though. |
|
3490 nsAutoCString qIndex; |
|
3491 if (aQueryIndex > 0) |
|
3492 qIndex.AppendInt(aQueryIndex); |
|
3493 |
|
3494 // begin time |
|
3495 if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) { |
|
3496 PRTime time = NormalizeTime(aQuery->BeginTimeReference(), |
|
3497 aQuery->BeginTime()); |
|
3498 rv = statement->BindInt64ByName( |
|
3499 NS_LITERAL_CSTRING("begin_time") + qIndex, time); |
|
3500 NS_ENSURE_SUCCESS(rv, rv); |
|
3501 } |
|
3502 |
|
3503 // end time |
|
3504 if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) { |
|
3505 PRTime time = NormalizeTime(aQuery->EndTimeReference(), |
|
3506 aQuery->EndTime()); |
|
3507 rv = statement->BindInt64ByName( |
|
3508 NS_LITERAL_CSTRING("end_time") + qIndex, time |
|
3509 ); |
|
3510 NS_ENSURE_SUCCESS(rv, rv); |
|
3511 } |
|
3512 |
|
3513 // search terms |
|
3514 if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasIt)) && hasIt) { |
|
3515 rv = statement->BindStringByName( |
|
3516 NS_LITERAL_CSTRING("search_string") + qIndex, |
|
3517 aQuery->SearchTerms() |
|
3518 ); |
|
3519 NS_ENSURE_SUCCESS(rv, rv); |
|
3520 } |
|
3521 |
|
3522 // min and max visit count |
|
3523 int32_t visits = aQuery->MinVisits(); |
|
3524 if (visits >= 0) { |
|
3525 rv = statement->BindInt32ByName( |
|
3526 NS_LITERAL_CSTRING("min_visits") + qIndex, visits |
|
3527 ); |
|
3528 NS_ENSURE_SUCCESS(rv, rv); |
|
3529 } |
|
3530 |
|
3531 visits = aQuery->MaxVisits(); |
|
3532 if (visits >= 0) { |
|
3533 rv = statement->BindInt32ByName( |
|
3534 NS_LITERAL_CSTRING("max_visits") + qIndex, visits |
|
3535 ); |
|
3536 NS_ENSURE_SUCCESS(rv, rv); |
|
3537 } |
|
3538 |
|
3539 // domain (see GetReversedHostname for more info on reversed host names) |
|
3540 if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { |
|
3541 nsString revDomain; |
|
3542 GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain); |
|
3543 |
|
3544 if (aQuery->DomainIsHost()) { |
|
3545 rv = statement->BindStringByName( |
|
3546 NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain |
|
3547 ); |
|
3548 NS_ENSURE_SUCCESS(rv, rv); |
|
3549 } else { |
|
3550 // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/" |
|
3551 // which will get everything starting with "gro.allizom." while using the |
|
3552 // index (using SUBSTRING() causes indexes to be discarded). |
|
3553 NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.', "Invalid rev. host"); |
|
3554 rv = statement->BindStringByName( |
|
3555 NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain |
|
3556 ); |
|
3557 NS_ENSURE_SUCCESS(rv, rv); |
|
3558 revDomain.Truncate(revDomain.Length() - 1); |
|
3559 revDomain.Append(char16_t('/')); |
|
3560 rv = statement->BindStringByName( |
|
3561 NS_LITERAL_CSTRING("domain_upper") + qIndex, revDomain |
|
3562 ); |
|
3563 NS_ENSURE_SUCCESS(rv, rv); |
|
3564 } |
|
3565 } |
|
3566 |
|
3567 // URI |
|
3568 if (aQuery->Uri()) { |
|
3569 rv = URIBinder::Bind( |
|
3570 statement, NS_LITERAL_CSTRING("uri") + qIndex, aQuery->Uri() |
|
3571 ); |
|
3572 NS_ENSURE_SUCCESS(rv, rv); |
|
3573 if (aQuery->UriIsPrefix()) { |
|
3574 nsAutoCString uriString; |
|
3575 aQuery->Uri()->GetSpec(uriString); |
|
3576 uriString.Append(char(0x7F)); // MAX_UTF8 |
|
3577 rv = URIBinder::Bind( |
|
3578 statement, NS_LITERAL_CSTRING("uri_upper") + qIndex, uriString |
|
3579 ); |
|
3580 NS_ENSURE_SUCCESS(rv, rv); |
|
3581 } |
|
3582 } |
|
3583 |
|
3584 // annotation |
|
3585 if (!aQuery->Annotation().IsEmpty()) { |
|
3586 rv = statement->BindUTF8StringByName( |
|
3587 NS_LITERAL_CSTRING("anno") + qIndex, aQuery->Annotation() |
|
3588 ); |
|
3589 NS_ENSURE_SUCCESS(rv, rv); |
|
3590 } |
|
3591 |
|
3592 // tags |
|
3593 const nsTArray<nsString> &tags = aQuery->Tags(); |
|
3594 if (tags.Length() > 0) { |
|
3595 for (uint32_t i = 0; i < tags.Length(); ++i) { |
|
3596 nsPrintfCString paramName("tag%d_", i); |
|
3597 NS_ConvertUTF16toUTF8 tag(tags[i]); |
|
3598 rv = statement->BindUTF8StringByName(paramName + qIndex, tag); |
|
3599 NS_ENSURE_SUCCESS(rv, rv); |
|
3600 } |
|
3601 int64_t tagsFolder = GetTagsFolder(); |
|
3602 rv = statement->BindInt64ByName( |
|
3603 NS_LITERAL_CSTRING("tags_folder") + qIndex, tagsFolder |
|
3604 ); |
|
3605 NS_ENSURE_SUCCESS(rv, rv); |
|
3606 if (!aQuery->TagsAreNot()) { |
|
3607 rv = statement->BindInt32ByName( |
|
3608 NS_LITERAL_CSTRING("tag_count") + qIndex, tags.Length() |
|
3609 ); |
|
3610 NS_ENSURE_SUCCESS(rv, rv); |
|
3611 } |
|
3612 } |
|
3613 |
|
3614 // transitions |
|
3615 const nsTArray<uint32_t>& transitions = aQuery->Transitions(); |
|
3616 if (transitions.Length() > 0) { |
|
3617 for (uint32_t i = 0; i < transitions.Length(); ++i) { |
|
3618 nsPrintfCString paramName("transition%d_", i); |
|
3619 rv = statement->BindInt64ByName(paramName + qIndex, transitions[i]); |
|
3620 NS_ENSURE_SUCCESS(rv, rv); |
|
3621 } |
|
3622 } |
|
3623 |
|
3624 return NS_OK; |
|
3625 } |
|
3626 |
|
3627 |
|
3628 // nsNavHistory::ResultsAsList |
|
3629 // |
|
3630 |
|
3631 nsresult |
|
3632 nsNavHistory::ResultsAsList(mozIStorageStatement* statement, |
|
3633 nsNavHistoryQueryOptions* aOptions, |
|
3634 nsCOMArray<nsNavHistoryResultNode>* aResults) |
|
3635 { |
|
3636 nsresult rv; |
|
3637 nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); |
|
3638 NS_ENSURE_SUCCESS(rv, rv); |
|
3639 |
|
3640 bool hasMore = false; |
|
3641 while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { |
|
3642 nsRefPtr<nsNavHistoryResultNode> result; |
|
3643 rv = RowToResult(row, aOptions, getter_AddRefs(result)); |
|
3644 NS_ENSURE_SUCCESS(rv, rv); |
|
3645 aResults->AppendObject(result); |
|
3646 } |
|
3647 return NS_OK; |
|
3648 } |
|
3649 |
|
3650 const int64_t UNDEFINED_URN_VALUE = -1; |
|
3651 |
|
3652 // Create a urn (like |
|
3653 // urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29) |
|
3654 // to be used to persist the open state of this container in localstore.rdf |
|
3655 nsresult |
|
3656 CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode, |
|
3657 int64_t aValue, const nsCString& aTitle, nsCString& aURN) |
|
3658 { |
|
3659 nsAutoCString uri; |
|
3660 nsresult rv = aResultNode->GetUri(uri); |
|
3661 NS_ENSURE_SUCCESS(rv, rv); |
|
3662 |
|
3663 aURN.Assign(NS_LITERAL_CSTRING("urn:places-persist:")); |
|
3664 aURN.Append(uri); |
|
3665 |
|
3666 aURN.Append(NS_LITERAL_CSTRING(",")); |
|
3667 if (aValue != UNDEFINED_URN_VALUE) |
|
3668 aURN.AppendInt(aValue); |
|
3669 |
|
3670 aURN.Append(NS_LITERAL_CSTRING(",")); |
|
3671 if (!aTitle.IsEmpty()) { |
|
3672 nsAutoCString escapedTitle; |
|
3673 bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas); |
|
3674 NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); |
|
3675 aURN.Append(escapedTitle); |
|
3676 } |
|
3677 |
|
3678 return NS_OK; |
|
3679 } |
|
3680 |
|
3681 int64_t |
|
3682 nsNavHistory::GetTagsFolder() |
|
3683 { |
|
3684 // cache our tags folder |
|
3685 // note, we can't do this in nsNavHistory::Init(), |
|
3686 // as getting the bookmarks service would initialize it. |
|
3687 if (mTagsFolder == -1) { |
|
3688 nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
3689 NS_ENSURE_TRUE(bookmarks, -1); |
|
3690 |
|
3691 nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder); |
|
3692 NS_ENSURE_SUCCESS(rv, -1); |
|
3693 } |
|
3694 return mTagsFolder; |
|
3695 } |
|
3696 |
|
3697 // nsNavHistory::FilterResultSet |
|
3698 // |
|
3699 // This does some post-query-execution filtering: |
|
3700 // - searching on title, url and tags |
|
3701 // - limit count |
|
3702 // |
|
3703 // Note: changes to filtering in FilterResultSet() |
|
3704 // may require changes to NeedToFilterResultSet() |
|
3705 |
|
3706 nsresult |
|
3707 nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode, |
|
3708 const nsCOMArray<nsNavHistoryResultNode>& aSet, |
|
3709 nsCOMArray<nsNavHistoryResultNode>* aFiltered, |
|
3710 const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
3711 nsNavHistoryQueryOptions *aOptions) |
|
3712 { |
|
3713 // get the bookmarks service |
|
3714 nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
3715 NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
|
3716 |
|
3717 // parse the search terms |
|
3718 nsTArray<nsTArray<nsString>*> terms; |
|
3719 ParseSearchTermsFromQueries(aQueries, &terms); |
|
3720 |
|
3721 uint16_t resultType = aOptions->ResultType(); |
|
3722 for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) { |
|
3723 // exclude-queries is implicit when searching, we're only looking at |
|
3724 // plan URI nodes |
|
3725 if (!aSet[nodeIndex]->IsURI()) |
|
3726 continue; |
|
3727 |
|
3728 // RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and |
|
3729 // lastModified. So, to remove duplicates, we can retain the first result |
|
3730 // for each uri. |
|
3731 if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS && |
|
3732 nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI) |
|
3733 continue; |
|
3734 |
|
3735 if (aSet[nodeIndex]->mItemId != -1 && aQueryNode && |
|
3736 aQueryNode->mItemId == aSet[nodeIndex]->mItemId) { |
|
3737 continue; |
|
3738 } |
|
3739 |
|
3740 // Append the node only if it matches one of the queries. |
|
3741 bool appendNode = false; |
|
3742 for (int32_t queryIndex = 0; |
|
3743 queryIndex < aQueries.Count() && !appendNode; queryIndex++) { |
|
3744 |
|
3745 if (terms[queryIndex]->Length()) { |
|
3746 // Filter based on search terms. |
|
3747 // Convert title and url for the current node to UTF16 strings. |
|
3748 NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle); |
|
3749 // Unescape the URL for search terms matching. |
|
3750 nsAutoCString cNodeURL(aSet[nodeIndex]->mURI); |
|
3751 NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL)); |
|
3752 |
|
3753 // Determine if every search term matches anywhere in the title, url or |
|
3754 // tag. |
|
3755 bool matchAll = true; |
|
3756 for (int32_t termIndex = terms[queryIndex]->Length() - 1; |
|
3757 termIndex >= 0 && matchAll; |
|
3758 termIndex--) { |
|
3759 nsString& term = terms[queryIndex]->ElementAt(termIndex); |
|
3760 |
|
3761 // True if any of them match; false makes us quit the loop |
|
3762 matchAll = CaseInsensitiveFindInReadable(term, nodeTitle) || |
|
3763 CaseInsensitiveFindInReadable(term, nodeURL) || |
|
3764 CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags); |
|
3765 } |
|
3766 |
|
3767 // Skip the node if we don't match all terms in the title, url or tag |
|
3768 if (!matchAll) |
|
3769 continue; |
|
3770 } |
|
3771 |
|
3772 // We passed all filters, so we can append the node to filtered results. |
|
3773 appendNode = true; |
|
3774 } |
|
3775 |
|
3776 if (appendNode) |
|
3777 aFiltered->AppendObject(aSet[nodeIndex]); |
|
3778 |
|
3779 // Stop once we have reached max results. |
|
3780 if (aOptions->MaxResults() > 0 && |
|
3781 (uint32_t)aFiltered->Count() >= aOptions->MaxResults()) |
|
3782 break; |
|
3783 } |
|
3784 |
|
3785 // De-allocate the temporary matrixes. |
|
3786 for (int32_t i = 0; i < aQueries.Count(); i++) { |
|
3787 delete terms[i]; |
|
3788 } |
|
3789 |
|
3790 return NS_OK; |
|
3791 } |
|
3792 |
|
3793 void |
|
3794 nsNavHistory::registerEmbedVisit(nsIURI* aURI, |
|
3795 int64_t aTime) |
|
3796 { |
|
3797 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
3798 |
|
3799 VisitHashKey* visit = mEmbedVisits.PutEntry(aURI); |
|
3800 if (!visit) { |
|
3801 NS_WARNING("Unable to register a EMBED visit."); |
|
3802 return; |
|
3803 } |
|
3804 visit->visitTime = aTime; |
|
3805 } |
|
3806 |
|
3807 bool |
|
3808 nsNavHistory::hasEmbedVisit(nsIURI* aURI) { |
|
3809 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
3810 |
|
3811 return !!mEmbedVisits.GetEntry(aURI); |
|
3812 } |
|
3813 |
|
3814 void |
|
3815 nsNavHistory::clearEmbedVisits() { |
|
3816 NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); |
|
3817 |
|
3818 mEmbedVisits.Clear(); |
|
3819 } |
|
3820 |
|
3821 // nsNavHistory::CheckIsRecentEvent |
|
3822 // |
|
3823 // Sees if this URL happened "recently." |
|
3824 // |
|
3825 // It is always removed from our recent list no matter what. It only counts |
|
3826 // as "recent" if the event happened more recently than our event |
|
3827 // threshold ago. |
|
3828 |
|
3829 bool |
|
3830 nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable, |
|
3831 const nsACString& url) |
|
3832 { |
|
3833 PRTime eventTime; |
|
3834 if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) { |
|
3835 hashTable->Remove(url); |
|
3836 if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD) |
|
3837 return true; |
|
3838 return false; |
|
3839 } |
|
3840 return false; |
|
3841 } |
|
3842 |
|
3843 |
|
3844 // nsNavHistory::ExpireNonrecentEvents |
|
3845 // |
|
3846 // This goes through our |
|
3847 |
|
3848 static PLDHashOperator |
|
3849 ExpireNonrecentEventsCallback(nsCStringHashKey::KeyType aKey, |
|
3850 int64_t& aData, |
|
3851 void* userArg) |
|
3852 { |
|
3853 int64_t* threshold = reinterpret_cast<int64_t*>(userArg); |
|
3854 if (aData < *threshold) |
|
3855 return PL_DHASH_REMOVE; |
|
3856 return PL_DHASH_NEXT; |
|
3857 } |
|
3858 void |
|
3859 nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable) |
|
3860 { |
|
3861 int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD; |
|
3862 hashTable->Enumerate(ExpireNonrecentEventsCallback, |
|
3863 reinterpret_cast<void*>(&threshold)); |
|
3864 } |
|
3865 |
|
3866 |
|
3867 // nsNavHistory::RowToResult |
|
3868 // |
|
3869 // Here, we just have a generic row. It could be a query, URL, visit, |
|
3870 // or full visit. |
|
3871 |
|
3872 nsresult |
|
3873 nsNavHistory::RowToResult(mozIStorageValueArray* aRow, |
|
3874 nsNavHistoryQueryOptions* aOptions, |
|
3875 nsNavHistoryResultNode** aResult) |
|
3876 { |
|
3877 NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult"); |
|
3878 |
|
3879 // URL |
|
3880 nsAutoCString url; |
|
3881 nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url); |
|
3882 NS_ENSURE_SUCCESS(rv, rv); |
|
3883 |
|
3884 // title |
|
3885 nsAutoCString title; |
|
3886 rv = aRow->GetUTF8String(kGetInfoIndex_Title, title); |
|
3887 NS_ENSURE_SUCCESS(rv, rv); |
|
3888 |
|
3889 uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount); |
|
3890 PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate); |
|
3891 |
|
3892 // favicon |
|
3893 nsAutoCString favicon; |
|
3894 rv = aRow->GetUTF8String(kGetInfoIndex_FaviconURL, favicon); |
|
3895 NS_ENSURE_SUCCESS(rv, rv); |
|
3896 |
|
3897 // itemId |
|
3898 int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId); |
|
3899 int64_t parentId = -1; |
|
3900 if (itemId == 0) { |
|
3901 // This is not a bookmark. For non-bookmarks we use a -1 itemId value. |
|
3902 // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0. |
|
3903 itemId = -1; |
|
3904 } |
|
3905 else { |
|
3906 // This is a bookmark, so it has a parent. |
|
3907 int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId); |
|
3908 if (itemParentId > 0) { |
|
3909 // The Places root has parent == 0, but that item id does not really |
|
3910 // exist. We want to set the parent only if it's a real one. |
|
3911 parentId = itemParentId; |
|
3912 } |
|
3913 } |
|
3914 |
|
3915 if (IsQueryURI(url)) { |
|
3916 // special case "place:" URIs: turn them into containers |
|
3917 |
|
3918 // We should never expose the history title for query nodes if the |
|
3919 // bookmark-item's title is set to null (the history title may be the |
|
3920 // query string without the place: prefix). Thus we call getItemTitle |
|
3921 // explicitly. Doing this in the SQL query would be less performant since |
|
3922 // it should be done for all results rather than only for queries. |
|
3923 if (itemId != -1) { |
|
3924 nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
3925 NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
|
3926 |
|
3927 rv = bookmarks->GetItemTitle(itemId, title); |
|
3928 NS_ENSURE_SUCCESS(rv, rv); |
|
3929 } |
|
3930 |
|
3931 nsRefPtr<nsNavHistoryResultNode> resultNode; |
|
3932 rv = QueryRowToResult(itemId, url, title, accessCount, time, favicon, |
|
3933 getter_AddRefs(resultNode)); |
|
3934 NS_ENSURE_SUCCESS(rv,rv); |
|
3935 |
|
3936 if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) { |
|
3937 // RESULTS_AS_TAG_QUERY has date columns |
|
3938 resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); |
|
3939 resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); |
|
3940 } |
|
3941 else if (resultNode->IsFolder()) { |
|
3942 // If it's a simple folder node (i.e. a shortcut to another folder), apply |
|
3943 // our options for it. However, if the parent type was tag query, we do not |
|
3944 // apply them, because it would not yield any results. |
|
3945 resultNode->GetAsContainer()->mOptions = aOptions; |
|
3946 } |
|
3947 |
|
3948 resultNode.forget(aResult); |
|
3949 return rv; |
|
3950 } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI || |
|
3951 aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { |
|
3952 nsRefPtr<nsNavHistoryResultNode> resultNode = |
|
3953 new nsNavHistoryResultNode(url, title, accessCount, time, favicon); |
|
3954 |
|
3955 if (itemId != -1) { |
|
3956 resultNode->mItemId = itemId; |
|
3957 resultNode->mFolderId = parentId; |
|
3958 resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); |
|
3959 resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); |
|
3960 } |
|
3961 |
|
3962 resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency); |
|
3963 resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden); |
|
3964 |
|
3965 nsAutoString tags; |
|
3966 rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); |
|
3967 NS_ENSURE_SUCCESS(rv, rv); |
|
3968 if (!tags.IsVoid()) { |
|
3969 resultNode->mTags.Assign(tags); |
|
3970 } |
|
3971 |
|
3972 rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); |
|
3973 NS_ENSURE_SUCCESS(rv, rv); |
|
3974 |
|
3975 resultNode.forget(aResult); |
|
3976 return NS_OK; |
|
3977 } |
|
3978 |
|
3979 if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) { |
|
3980 nsRefPtr<nsNavHistoryResultNode> resultNode = |
|
3981 new nsNavHistoryResultNode(url, title, accessCount, time, favicon); |
|
3982 |
|
3983 nsAutoString tags; |
|
3984 rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); |
|
3985 if (!tags.IsVoid()) |
|
3986 resultNode->mTags.Assign(tags); |
|
3987 |
|
3988 rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); |
|
3989 NS_ENSURE_SUCCESS(rv, rv); |
|
3990 |
|
3991 resultNode.forget(aResult); |
|
3992 return NS_OK; |
|
3993 } |
|
3994 |
|
3995 return NS_ERROR_FAILURE; |
|
3996 } |
|
3997 |
|
3998 |
|
3999 // nsNavHistory::QueryRowToResult |
|
4000 // |
|
4001 // Called by RowToResult when the URI is a place: URI to generate the proper |
|
4002 // folder or query node. |
|
4003 |
|
4004 nsresult |
|
4005 nsNavHistory::QueryRowToResult(int64_t itemId, const nsACString& aURI, |
|
4006 const nsACString& aTitle, |
|
4007 uint32_t aAccessCount, PRTime aTime, |
|
4008 const nsACString& aFavicon, |
|
4009 nsNavHistoryResultNode** aNode) |
|
4010 { |
|
4011 nsCOMArray<nsNavHistoryQuery> queries; |
|
4012 nsCOMPtr<nsNavHistoryQueryOptions> options; |
|
4013 nsresult rv = QueryStringToQueryArray(aURI, &queries, |
|
4014 getter_AddRefs(options)); |
|
4015 |
|
4016 nsRefPtr<nsNavHistoryResultNode> resultNode; |
|
4017 // If this failed the query does not parse correctly, let the error pass and |
|
4018 // handle it later. |
|
4019 if (NS_SUCCEEDED(rv)) { |
|
4020 // Check if this is a folder shortcut, so we can take a faster path. |
|
4021 int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); |
|
4022 if (folderId) { |
|
4023 nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); |
|
4024 NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); |
|
4025 |
|
4026 rv = bookmarks->ResultNodeForContainer(folderId, options, |
|
4027 getter_AddRefs(resultNode)); |
|
4028 // If this failed the shortcut is pointing to nowhere, let the error pass |
|
4029 // and handle it later. |
|
4030 if (NS_SUCCEEDED(rv)) { |
|
4031 // This is the query itemId, and is what is exposed by node.itemId. |
|
4032 resultNode->GetAsFolder()->mQueryItemId = itemId; |
|
4033 |
|
4034 // Use the query item title, unless it's void (in that case use the |
|
4035 // concrete folder title). |
|
4036 if (!aTitle.IsVoid()) { |
|
4037 resultNode->mTitle = aTitle; |
|
4038 } |
|
4039 } |
|
4040 } |
|
4041 else { |
|
4042 // This is a regular query. |
|
4043 resultNode = new nsNavHistoryQueryResultNode(aTitle, EmptyCString(), |
|
4044 aTime, queries, options); |
|
4045 resultNode->mItemId = itemId; |
|
4046 } |
|
4047 } |
|
4048 |
|
4049 if (NS_FAILED(rv)) { |
|
4050 NS_WARNING("Generating a generic empty node for a broken query!"); |
|
4051 // This is a broken query, that either did not parse or points to not |
|
4052 // existing data. We don't want to return failure since that will kill the |
|
4053 // whole result. Instead make a generic empty query node. |
|
4054 resultNode = new nsNavHistoryQueryResultNode(aTitle, aFavicon, aURI); |
|
4055 resultNode->mItemId = itemId; |
|
4056 // This is a perf hack to generate an empty query that skips filtering. |
|
4057 resultNode->GetAsQuery()->Options()->SetExcludeItems(true); |
|
4058 } |
|
4059 |
|
4060 resultNode.forget(aNode); |
|
4061 return NS_OK; |
|
4062 } |
|
4063 |
|
4064 |
|
4065 // nsNavHistory::VisitIdToResultNode |
|
4066 // |
|
4067 // Used by the query results to create new nodes on the fly when |
|
4068 // notifications come in. This just creates a node for the given visit ID. |
|
4069 |
|
4070 nsresult |
|
4071 nsNavHistory::VisitIdToResultNode(int64_t visitId, |
|
4072 nsNavHistoryQueryOptions* aOptions, |
|
4073 nsNavHistoryResultNode** aResult) |
|
4074 { |
|
4075 nsAutoCString tagsFragment; |
|
4076 GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
|
4077 true, tagsFragment); |
|
4078 |
|
4079 nsCOMPtr<mozIStorageStatement> statement; |
|
4080 switch (aOptions->ResultType()) |
|
4081 { |
|
4082 case nsNavHistoryQueryOptions::RESULTS_AS_VISIT: |
|
4083 case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: |
|
4084 // visit query - want exact visit time |
|
4085 // Should match kGetInfoIndex_* (see GetQueryResults) |
|
4086 statement = mDB->GetStatement(NS_LITERAL_CSTRING( |
|
4087 "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " |
|
4088 "v.visit_date, f.url, null, null, null, null, " |
|
4089 ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
4090 "FROM moz_places h " |
|
4091 "JOIN moz_historyvisits v ON h.id = v.place_id " |
|
4092 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
4093 "WHERE v.id = :visit_id ") |
|
4094 ); |
|
4095 break; |
|
4096 |
|
4097 case nsNavHistoryQueryOptions::RESULTS_AS_URI: |
|
4098 // URL results - want last visit time |
|
4099 // Should match kGetInfoIndex_* (see GetQueryResults) |
|
4100 statement = mDB->GetStatement(NS_LITERAL_CSTRING( |
|
4101 "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " |
|
4102 "h.last_visit_date, f.url, null, null, null, null, " |
|
4103 ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
4104 "FROM moz_places h " |
|
4105 "JOIN moz_historyvisits v ON h.id = v.place_id " |
|
4106 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
4107 "WHERE v.id = :visit_id ") |
|
4108 ); |
|
4109 break; |
|
4110 |
|
4111 default: |
|
4112 // Query base types like RESULTS_AS_*_QUERY handle additions |
|
4113 // by registering their own observers when they are expanded. |
|
4114 return NS_OK; |
|
4115 } |
|
4116 NS_ENSURE_STATE(statement); |
|
4117 mozStorageStatementScoper scoper(statement); |
|
4118 |
|
4119 nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"), |
|
4120 visitId); |
|
4121 NS_ENSURE_SUCCESS(rv, rv); |
|
4122 |
|
4123 bool hasMore = false; |
|
4124 rv = statement->ExecuteStep(&hasMore); |
|
4125 NS_ENSURE_SUCCESS(rv, rv); |
|
4126 if (! hasMore) { |
|
4127 NS_NOTREACHED("Trying to get a result node for an invalid visit"); |
|
4128 return NS_ERROR_INVALID_ARG; |
|
4129 } |
|
4130 |
|
4131 nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); |
|
4132 NS_ENSURE_SUCCESS(rv, rv); |
|
4133 |
|
4134 return RowToResult(row, aOptions, aResult); |
|
4135 } |
|
4136 |
|
4137 nsresult |
|
4138 nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions, |
|
4139 nsNavHistoryResultNode** aResult) |
|
4140 { |
|
4141 nsAutoCString tagsFragment; |
|
4142 GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
|
4143 true, tagsFragment); |
|
4144 // Should match kGetInfoIndex_* |
|
4145 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
|
4146 "SELECT b.fk, h.url, COALESCE(b.title, h.title), " |
|
4147 "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " |
|
4148 "b.dateAdded, b.lastModified, b.parent, " |
|
4149 ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
4150 "FROM moz_bookmarks b " |
|
4151 "JOIN moz_places h ON b.fk = h.id " |
|
4152 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
4153 "WHERE b.id = :item_id ") |
|
4154 ); |
|
4155 NS_ENSURE_STATE(stmt); |
|
4156 mozStorageStatementScoper scoper(stmt); |
|
4157 |
|
4158 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), |
|
4159 aBookmarkId); |
|
4160 NS_ENSURE_SUCCESS(rv, rv); |
|
4161 |
|
4162 bool hasMore = false; |
|
4163 rv = stmt->ExecuteStep(&hasMore); |
|
4164 NS_ENSURE_SUCCESS(rv, rv); |
|
4165 if (!hasMore) { |
|
4166 NS_NOTREACHED("Trying to get a result node for an invalid bookmark identifier"); |
|
4167 return NS_ERROR_INVALID_ARG; |
|
4168 } |
|
4169 |
|
4170 nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); |
|
4171 NS_ENSURE_SUCCESS(rv, rv); |
|
4172 |
|
4173 return RowToResult(row, aOptions, aResult); |
|
4174 } |
|
4175 |
|
4176 nsresult |
|
4177 nsNavHistory::URIToResultNode(nsIURI* aURI, |
|
4178 nsNavHistoryQueryOptions* aOptions, |
|
4179 nsNavHistoryResultNode** aResult) |
|
4180 { |
|
4181 nsAutoCString tagsFragment; |
|
4182 GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), |
|
4183 true, tagsFragment); |
|
4184 // Should match kGetInfoIndex_* |
|
4185 nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( |
|
4186 "SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, " |
|
4187 "h.last_visit_date, f.url, null, null, null, null, " |
|
4188 ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " |
|
4189 "FROM moz_places h " |
|
4190 "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " |
|
4191 "WHERE h.url = :page_url ") |
|
4192 ); |
|
4193 NS_ENSURE_STATE(stmt); |
|
4194 mozStorageStatementScoper scoper(stmt); |
|
4195 |
|
4196 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); |
|
4197 NS_ENSURE_SUCCESS(rv, rv); |
|
4198 |
|
4199 bool hasMore = false; |
|
4200 rv = stmt->ExecuteStep(&hasMore); |
|
4201 NS_ENSURE_SUCCESS(rv, rv); |
|
4202 if (!hasMore) { |
|
4203 NS_NOTREACHED("Trying to get a result node for an invalid url"); |
|
4204 return NS_ERROR_INVALID_ARG; |
|
4205 } |
|
4206 |
|
4207 nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); |
|
4208 NS_ENSURE_SUCCESS(rv, rv); |
|
4209 |
|
4210 return RowToResult(row, aOptions, aResult); |
|
4211 } |
|
4212 |
|
4213 void |
|
4214 nsNavHistory::SendPageChangedNotification(nsIURI* aURI, |
|
4215 uint32_t aChangedAttribute, |
|
4216 const nsAString& aNewValue, |
|
4217 const nsACString& aGUID) |
|
4218 { |
|
4219 MOZ_ASSERT(!aGUID.IsEmpty()); |
|
4220 NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, |
|
4221 nsINavHistoryObserver, |
|
4222 OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID)); |
|
4223 } |
|
4224 |
|
4225 // nsNavHistory::TitleForDomain |
|
4226 // |
|
4227 // This computes the title for a given domain. Normally, this is just the |
|
4228 // domain name, but we specially handle empty cases to give you a nice |
|
4229 // localized string. |
|
4230 |
|
4231 void |
|
4232 nsNavHistory::TitleForDomain(const nsCString& domain, nsACString& aTitle) |
|
4233 { |
|
4234 if (! domain.IsEmpty()) { |
|
4235 aTitle = domain; |
|
4236 return; |
|
4237 } |
|
4238 |
|
4239 // use the localized one instead |
|
4240 GetStringFromName(MOZ_UTF16("localhost"), aTitle); |
|
4241 } |
|
4242 |
|
4243 void |
|
4244 nsNavHistory::GetAgeInDaysString(int32_t aInt, const char16_t *aName, |
|
4245 nsACString& aResult) |
|
4246 { |
|
4247 nsIStringBundle *bundle = GetBundle(); |
|
4248 if (bundle) { |
|
4249 nsAutoString intString; |
|
4250 intString.AppendInt(aInt); |
|
4251 const char16_t* strings[1] = { intString.get() }; |
|
4252 nsXPIDLString value; |
|
4253 nsresult rv = bundle->FormatStringFromName(aName, strings, |
|
4254 1, getter_Copies(value)); |
|
4255 if (NS_SUCCEEDED(rv)) { |
|
4256 CopyUTF16toUTF8(value, aResult); |
|
4257 return; |
|
4258 } |
|
4259 } |
|
4260 CopyUTF16toUTF8(nsDependentString(aName), aResult); |
|
4261 } |
|
4262 |
|
4263 void |
|
4264 nsNavHistory::GetStringFromName(const char16_t *aName, nsACString& aResult) |
|
4265 { |
|
4266 nsIStringBundle *bundle = GetBundle(); |
|
4267 if (bundle) { |
|
4268 nsXPIDLString value; |
|
4269 nsresult rv = bundle->GetStringFromName(aName, getter_Copies(value)); |
|
4270 if (NS_SUCCEEDED(rv)) { |
|
4271 CopyUTF16toUTF8(value, aResult); |
|
4272 return; |
|
4273 } |
|
4274 } |
|
4275 CopyUTF16toUTF8(nsDependentString(aName), aResult); |
|
4276 } |
|
4277 |
|
4278 void |
|
4279 nsNavHistory::GetMonthName(int32_t aIndex, nsACString& aResult) |
|
4280 { |
|
4281 nsIStringBundle *bundle = GetDateFormatBundle(); |
|
4282 if (bundle) { |
|
4283 nsCString name = nsPrintfCString("month.%d.name", aIndex); |
|
4284 nsXPIDLString value; |
|
4285 nsresult rv = bundle->GetStringFromName(NS_ConvertUTF8toUTF16(name).get(), |
|
4286 getter_Copies(value)); |
|
4287 if (NS_SUCCEEDED(rv)) { |
|
4288 CopyUTF16toUTF8(value, aResult); |
|
4289 return; |
|
4290 } |
|
4291 } |
|
4292 aResult = nsPrintfCString("[%d]", aIndex); |
|
4293 } |
|
4294 |
|
4295 void |
|
4296 nsNavHistory::GetMonthYear(int32_t aMonth, int32_t aYear, nsACString& aResult) |
|
4297 { |
|
4298 nsIStringBundle *bundle = GetBundle(); |
|
4299 if (bundle) { |
|
4300 nsAutoCString monthName; |
|
4301 GetMonthName(aMonth, monthName); |
|
4302 nsAutoString yearString; |
|
4303 yearString.AppendInt(aYear); |
|
4304 const char16_t* strings[2] = { |
|
4305 NS_ConvertUTF8toUTF16(monthName).get() |
|
4306 , yearString.get() |
|
4307 }; |
|
4308 nsXPIDLString value; |
|
4309 if (NS_SUCCEEDED(bundle->FormatStringFromName( |
|
4310 MOZ_UTF16("finduri-MonthYear"), strings, 2, |
|
4311 getter_Copies(value) |
|
4312 ))) { |
|
4313 CopyUTF16toUTF8(value, aResult); |
|
4314 return; |
|
4315 } |
|
4316 } |
|
4317 aResult.AppendLiteral("finduri-MonthYear"); |
|
4318 } |
|
4319 |
|
4320 |
|
4321 namespace { |
|
4322 |
|
4323 // GetSimpleBookmarksQueryFolder |
|
4324 // |
|
4325 // Determines if this set of queries is a simple bookmarks query for a |
|
4326 // folder with no other constraints. In these common cases, we can more |
|
4327 // efficiently compute the results. |
|
4328 // |
|
4329 // A simple bookmarks query will result in a hierarchical tree of |
|
4330 // bookmark items, folders and separators. |
|
4331 // |
|
4332 // Returns the folder ID if it is a simple folder query, 0 if not. |
|
4333 static int64_t |
|
4334 GetSimpleBookmarksQueryFolder(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
4335 nsNavHistoryQueryOptions* aOptions) |
|
4336 { |
|
4337 if (aQueries.Count() != 1) |
|
4338 return 0; |
|
4339 |
|
4340 nsNavHistoryQuery* query = aQueries[0]; |
|
4341 if (query->Folders().Length() != 1) |
|
4342 return 0; |
|
4343 |
|
4344 bool hasIt; |
|
4345 query->GetHasBeginTime(&hasIt); |
|
4346 if (hasIt) |
|
4347 return 0; |
|
4348 query->GetHasEndTime(&hasIt); |
|
4349 if (hasIt) |
|
4350 return 0; |
|
4351 query->GetHasDomain(&hasIt); |
|
4352 if (hasIt) |
|
4353 return 0; |
|
4354 query->GetHasUri(&hasIt); |
|
4355 if (hasIt) |
|
4356 return 0; |
|
4357 (void)query->GetHasSearchTerms(&hasIt); |
|
4358 if (hasIt) |
|
4359 return 0; |
|
4360 if (query->Tags().Length() > 0) |
|
4361 return 0; |
|
4362 if (aOptions->MaxResults() > 0) |
|
4363 return 0; |
|
4364 |
|
4365 // RESULTS_AS_TAG_CONTENTS is quite similar to a folder shortcut, but it must |
|
4366 // not be treated like that, since it needs all query options. |
|
4367 if(aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) |
|
4368 return 0; |
|
4369 |
|
4370 // Don't care about onlyBookmarked flag, since specifying a bookmark |
|
4371 // folder is inferring onlyBookmarked. |
|
4372 |
|
4373 return query->Folders()[0]; |
|
4374 } |
|
4375 |
|
4376 |
|
4377 // ParseSearchTermsFromQueries |
|
4378 // |
|
4379 // Construct a matrix of search terms from the given queries array. |
|
4380 // All of the query objects are ORed together. Within a query, all the terms |
|
4381 // are ANDed together. See nsINavHistoryService.idl. |
|
4382 // |
|
4383 // This just breaks the query up into words. We don't do anything fancy, |
|
4384 // not even quoting. We do, however, strip quotes, because people might |
|
4385 // try to input quotes expecting them to do something and get no results |
|
4386 // back. |
|
4387 |
|
4388 inline bool isQueryWhitespace(char16_t ch) |
|
4389 { |
|
4390 return ch == ' '; |
|
4391 } |
|
4392 |
|
4393 void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, |
|
4394 nsTArray<nsTArray<nsString>*>* aTerms) |
|
4395 { |
|
4396 int32_t lastBegin = -1; |
|
4397 for (int32_t i = 0; i < aQueries.Count(); i++) { |
|
4398 nsTArray<nsString> *queryTerms = new nsTArray<nsString>(); |
|
4399 bool hasSearchTerms; |
|
4400 if (NS_SUCCEEDED(aQueries[i]->GetHasSearchTerms(&hasSearchTerms)) && |
|
4401 hasSearchTerms) { |
|
4402 const nsString& searchTerms = aQueries[i]->SearchTerms(); |
|
4403 for (uint32_t j = 0; j < searchTerms.Length(); j++) { |
|
4404 if (isQueryWhitespace(searchTerms[j]) || |
|
4405 searchTerms[j] == '"') { |
|
4406 if (lastBegin >= 0) { |
|
4407 // found the end of a word |
|
4408 queryTerms->AppendElement(Substring(searchTerms, lastBegin, |
|
4409 j - lastBegin)); |
|
4410 lastBegin = -1; |
|
4411 } |
|
4412 } else { |
|
4413 if (lastBegin < 0) { |
|
4414 // found the beginning of a word |
|
4415 lastBegin = j; |
|
4416 } |
|
4417 } |
|
4418 } |
|
4419 // last word |
|
4420 if (lastBegin >= 0) |
|
4421 queryTerms->AppendElement(Substring(searchTerms, lastBegin)); |
|
4422 } |
|
4423 aTerms->AppendElement(queryTerms); |
|
4424 } |
|
4425 } |
|
4426 |
|
4427 } // anonymous namespace |
|
4428 |
|
4429 |
|
4430 nsresult |
|
4431 nsNavHistory::UpdateFrecency(int64_t aPlaceId) |
|
4432 { |
|
4433 nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement( |
|
4434 "UPDATE moz_places " |
|
4435 "SET frecency = NOTIFY_FRECENCY(" |
|
4436 "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date" |
|
4437 ") " |
|
4438 "WHERE id = :page_id" |
|
4439 ); |
|
4440 NS_ENSURE_STATE(updateFrecencyStmt); |
|
4441 nsresult rv = updateFrecencyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), |
|
4442 aPlaceId); |
|
4443 NS_ENSURE_SUCCESS(rv, rv); |
|
4444 nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement( |
|
4445 "UPDATE moz_places " |
|
4446 "SET hidden = 0 " |
|
4447 "WHERE id = :page_id AND frecency <> 0" |
|
4448 ); |
|
4449 NS_ENSURE_STATE(updateHiddenStmt); |
|
4450 rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), |
|
4451 aPlaceId); |
|
4452 NS_ENSURE_SUCCESS(rv, rv); |
|
4453 |
|
4454 mozIStorageBaseStatement *stmts[] = { |
|
4455 updateFrecencyStmt.get() |
|
4456 , updateHiddenStmt.get() |
|
4457 }; |
|
4458 |
|
4459 nsRefPtr<AsyncStatementCallbackNotifier> cb = |
|
4460 new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED); |
|
4461 nsCOMPtr<mozIStoragePendingStatement> ps; |
|
4462 rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, |
|
4463 getter_AddRefs(ps)); |
|
4464 NS_ENSURE_SUCCESS(rv, rv); |
|
4465 |
|
4466 return NS_OK; |
|
4467 } |
|
4468 |
|
4469 |
|
4470 namespace { |
|
4471 |
|
4472 class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier |
|
4473 { |
|
4474 public: |
|
4475 FixInvalidFrecenciesCallback() |
|
4476 : AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED) |
|
4477 { |
|
4478 } |
|
4479 |
|
4480 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
4481 { |
|
4482 nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason); |
|
4483 NS_ENSURE_SUCCESS(rv, rv); |
|
4484 if (aReason == REASON_FINISHED) { |
|
4485 nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); |
|
4486 NS_ENSURE_STATE(navHistory); |
|
4487 navHistory->NotifyManyFrecenciesChanged(); |
|
4488 } |
|
4489 return NS_OK; |
|
4490 } |
|
4491 }; |
|
4492 |
|
4493 } // anonymous namespace |
|
4494 |
|
4495 nsresult |
|
4496 nsNavHistory::FixInvalidFrecencies() |
|
4497 { |
|
4498 nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
|
4499 "UPDATE moz_places " |
|
4500 "SET frecency = CALCULATE_FRECENCY(id) " |
|
4501 "WHERE frecency < 0" |
|
4502 ); |
|
4503 NS_ENSURE_STATE(stmt); |
|
4504 |
|
4505 nsRefPtr<FixInvalidFrecenciesCallback> callback = |
|
4506 new FixInvalidFrecenciesCallback(); |
|
4507 nsCOMPtr<mozIStoragePendingStatement> ps; |
|
4508 (void)stmt->ExecuteAsync(callback, getter_AddRefs(ps)); |
|
4509 |
|
4510 return NS_OK; |
|
4511 } |
|
4512 |
|
4513 |
|
4514 #ifdef MOZ_XUL |
|
4515 |
|
4516 nsresult |
|
4517 nsNavHistory::AutoCompleteFeedback(int32_t aIndex, |
|
4518 nsIAutoCompleteController *aController) |
|
4519 { |
|
4520 nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( |
|
4521 "INSERT OR REPLACE INTO moz_inputhistory " |
|
4522 // use_count will asymptotically approach the max of 10. |
|
4523 "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 " |
|
4524 "FROM moz_places h " |
|
4525 "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text " |
|
4526 "WHERE url = :page_url " |
|
4527 ); |
|
4528 NS_ENSURE_STATE(stmt); |
|
4529 |
|
4530 nsAutoString input; |
|
4531 nsresult rv = aController->GetSearchString(input); |
|
4532 NS_ENSURE_SUCCESS(rv, rv); |
|
4533 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input); |
|
4534 NS_ENSURE_SUCCESS(rv, rv); |
|
4535 |
|
4536 nsAutoString url; |
|
4537 rv = aController->GetValueAt(aIndex, url); |
|
4538 NS_ENSURE_SUCCESS(rv, rv); |
|
4539 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), |
|
4540 NS_ConvertUTF16toUTF8(url)); |
|
4541 NS_ENSURE_SUCCESS(rv, rv); |
|
4542 |
|
4543 // We do the update asynchronously and we do not care about failures. |
|
4544 nsRefPtr<AsyncStatementCallbackNotifier> callback = |
|
4545 new AsyncStatementCallbackNotifier(TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED); |
|
4546 nsCOMPtr<mozIStoragePendingStatement> canceler; |
|
4547 rv = stmt->ExecuteAsync(callback, getter_AddRefs(canceler)); |
|
4548 NS_ENSURE_SUCCESS(rv, rv); |
|
4549 |
|
4550 return NS_OK; |
|
4551 } |
|
4552 |
|
4553 #endif |
|
4554 |
|
4555 |
|
4556 nsICollation * |
|
4557 nsNavHistory::GetCollation() |
|
4558 { |
|
4559 if (mCollation) |
|
4560 return mCollation; |
|
4561 |
|
4562 // locale |
|
4563 nsCOMPtr<nsILocale> locale; |
|
4564 nsCOMPtr<nsILocaleService> ls(do_GetService(NS_LOCALESERVICE_CONTRACTID)); |
|
4565 NS_ENSURE_TRUE(ls, nullptr); |
|
4566 nsresult rv = ls->GetApplicationLocale(getter_AddRefs(locale)); |
|
4567 NS_ENSURE_SUCCESS(rv, nullptr); |
|
4568 |
|
4569 // collation |
|
4570 nsCOMPtr<nsICollationFactory> cfact = |
|
4571 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); |
|
4572 NS_ENSURE_TRUE(cfact, nullptr); |
|
4573 rv = cfact->CreateCollation(locale, getter_AddRefs(mCollation)); |
|
4574 NS_ENSURE_SUCCESS(rv, nullptr); |
|
4575 |
|
4576 return mCollation; |
|
4577 } |
|
4578 |
|
4579 nsIStringBundle * |
|
4580 nsNavHistory::GetBundle() |
|
4581 { |
|
4582 if (!mBundle) { |
|
4583 nsCOMPtr<nsIStringBundleService> bundleService = |
|
4584 services::GetStringBundleService(); |
|
4585 NS_ENSURE_TRUE(bundleService, nullptr); |
|
4586 nsresult rv = bundleService->CreateBundle( |
|
4587 "chrome://places/locale/places.properties", |
|
4588 getter_AddRefs(mBundle)); |
|
4589 NS_ENSURE_SUCCESS(rv, nullptr); |
|
4590 } |
|
4591 return mBundle; |
|
4592 } |
|
4593 |
|
4594 nsIStringBundle * |
|
4595 nsNavHistory::GetDateFormatBundle() |
|
4596 { |
|
4597 if (!mDateFormatBundle) { |
|
4598 nsCOMPtr<nsIStringBundleService> bundleService = |
|
4599 services::GetStringBundleService(); |
|
4600 NS_ENSURE_TRUE(bundleService, nullptr); |
|
4601 nsresult rv = bundleService->CreateBundle( |
|
4602 "chrome://global/locale/dateFormat.properties", |
|
4603 getter_AddRefs(mDateFormatBundle)); |
|
4604 NS_ENSURE_SUCCESS(rv, nullptr); |
|
4605 } |
|
4606 return mDateFormatBundle; |
|
4607 } |