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