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