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 "nsPlacesTriggers.h"
20 #include "DateTimeFormat.h"
21 #include "History.h"
22 #include "Helpers.h"
23 
24 #include "nsTArray.h"
25 #include "nsCollationCID.h"
26 #include "nsNetUtil.h"
27 #include "nsPrintfCString.h"
28 #include "nsPromiseFlatString.h"
29 #include "nsString.h"
30 #include "nsUnicharUtils.h"
31 #include "prsystem.h"
32 #include "prtime.h"
33 #include "nsEscape.h"
34 #include "nsIEffectiveTLDService.h"
35 #include "nsIClassInfoImpl.h"
36 #include "nsIIDNService.h"
37 #include "nsQueryObject.h"
38 #include "nsThreadUtils.h"
39 #include "nsAppDirectoryServiceDefs.h"
40 #include "nsMathUtils.h"
41 #include "mozilla/storage.h"
42 #include "mozilla/Preferences.h"
43 #include <algorithm>
44 
45 using namespace mozilla;
46 using namespace mozilla::places;
47 
48 // The maximum number of things that we will store in the recent events list
49 // before calling ExpireNonrecentEvents. This number should be big enough so it
50 // is very difficult to get that many unconsumed events (for example, typed but
51 // never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
52 // checking each one for every page visit, which will be somewhat slower.
53 #define RECENT_EVENT_QUEUE_MAX_LENGTH 128
54 
55 // preference ID strings
56 #define PREF_HISTORY_ENABLED "places.history.enabled"
57 #define PREF_MATCH_DIACRITICS "places.search.matchDiacritics"
58 
59 #define PREF_FREC_NUM_VISITS "places.frecency.numVisits"
60 #define PREF_FREC_NUM_VISITS_DEF 10
61 #define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff"
62 #define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4
63 #define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff"
64 #define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14
65 #define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff"
66 #define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31
67 #define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff"
68 #define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90
69 #define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight"
70 #define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100
71 #define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight"
72 #define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70
73 #define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight"
74 #define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50
75 #define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight"
76 #define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30
77 #define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight"
78 #define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10
79 #define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus"
80 #define PREF_FREC_EMBED_VISIT_BONUS_DEF 0
81 #define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus"
82 #define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0
83 #define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus"
84 #define PREF_FREC_LINK_VISIT_BONUS_DEF 100
85 #define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus"
86 #define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000
87 #define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus"
88 #define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75
89 #define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus"
90 #define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0
91 #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS \
92   "places.frecency.permRedirectVisitBonus"
93 #define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0
94 #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS \
95   "places.frecency.tempRedirectVisitBonus"
96 #define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0
97 #define PREF_FREC_REDIR_SOURCE_VISIT_BONUS \
98   "places.frecency.redirectSourceVisitBonus"
99 #define PREF_FREC_REDIR_SOURCE_VISIT_BONUS_DEF 25
100 #define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus"
101 #define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0
102 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS \
103   "places.frecency.unvisitedBookmarkBonus"
104 #define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140
105 #define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus"
106 #define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200
107 #define PREF_FREC_RELOAD_VISIT_BONUS "places.frecency.reloadVisitBonus"
108 #define PREF_FREC_RELOAD_VISIT_BONUS_DEF 0
109 
110 // This is a 'hidden' pref for the purposes of unit tests.
111 #define PREF_FREC_DECAY_RATE "places.frecency.decayRate"
112 #define PREF_FREC_DECAY_RATE_DEF 0.975f
113 // An adaptive history entry is removed if unused for these many days.
114 #define ADAPTIVE_HISTORY_EXPIRE_DAYS 90
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 recent events cache.
135 #define RECENT_EVENTS_INITIAL_CACHE_LENGTH 64
136 
137 // Observed topics.
138 #define TOPIC_IDLE_DAILY "idle-daily"
139 #define TOPIC_PREF_CHANGED "nsPref:changed"
140 #define TOPIC_PROFILE_TEARDOWN "profile-change-teardown"
141 #define TOPIC_PROFILE_CHANGE "profile-before-change"
142 
143 static const char* kObservedPrefs[] = {PREF_HISTORY_ENABLED,
144                                        PREF_MATCH_DIACRITICS,
145                                        PREF_FREC_NUM_VISITS,
146                                        PREF_FREC_FIRST_BUCKET_CUTOFF,
147                                        PREF_FREC_SECOND_BUCKET_CUTOFF,
148                                        PREF_FREC_THIRD_BUCKET_CUTOFF,
149                                        PREF_FREC_FOURTH_BUCKET_CUTOFF,
150                                        PREF_FREC_FIRST_BUCKET_WEIGHT,
151                                        PREF_FREC_SECOND_BUCKET_WEIGHT,
152                                        PREF_FREC_THIRD_BUCKET_WEIGHT,
153                                        PREF_FREC_FOURTH_BUCKET_WEIGHT,
154                                        PREF_FREC_DEFAULT_BUCKET_WEIGHT,
155                                        PREF_FREC_EMBED_VISIT_BONUS,
156                                        PREF_FREC_FRAMED_LINK_VISIT_BONUS,
157                                        PREF_FREC_LINK_VISIT_BONUS,
158                                        PREF_FREC_TYPED_VISIT_BONUS,
159                                        PREF_FREC_BOOKMARK_VISIT_BONUS,
160                                        PREF_FREC_DOWNLOAD_VISIT_BONUS,
161                                        PREF_FREC_PERM_REDIRECT_VISIT_BONUS,
162                                        PREF_FREC_TEMP_REDIRECT_VISIT_BONUS,
163                                        PREF_FREC_REDIR_SOURCE_VISIT_BONUS,
164                                        PREF_FREC_DEFAULT_VISIT_BONUS,
165                                        PREF_FREC_UNVISITED_BOOKMARK_BONUS,
166                                        PREF_FREC_UNVISITED_TYPED_BONUS,
167                                        nullptr};
168 
169 NS_IMPL_ADDREF(nsNavHistory)
170 NS_IMPL_RELEASE(nsNavHistory)
171 
172 NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON,
173                   NS_NAVHISTORYSERVICE_CID)
174 NS_INTERFACE_MAP_BEGIN(nsNavHistory)
175   NS_INTERFACE_MAP_ENTRY(nsINavHistoryService)
176   NS_INTERFACE_MAP_ENTRY(nsIObserver)
177   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
178   NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
179   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
180   NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
181 NS_INTERFACE_MAP_END
182 
183 // We don't care about flattening everything
184 NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, nsINavHistoryService)
185 
186 namespace {
187 
188 static nsCString GetSimpleBookmarksQueryParent(
189     const RefPtr<nsNavHistoryQuery>& aQuery,
190     const RefPtr<nsNavHistoryQueryOptions>& aOptions);
191 static void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
192                                       nsTArray<nsString>* aTerms);
193 
GetTagsSqlFragment(int64_t aTagsFolder,const nsACString & aRelation,bool aHasSearchTerms,nsACString & _sqlFragment)194 void GetTagsSqlFragment(int64_t aTagsFolder, const nsACString& aRelation,
195                         bool aHasSearchTerms, nsACString& _sqlFragment) {
196   if (!aHasSearchTerms)
197     _sqlFragment.AssignLiteral("null");
198   else {
199     // This subquery DOES NOT order tags for performance reasons.
200     _sqlFragment.Assign(
201         NS_LITERAL_CSTRING("(SELECT GROUP_CONCAT(t_t.title, ',') "
202                            "FROM moz_bookmarks b_t "
203                            "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent  "
204                            "WHERE b_t.fk = ") +
205         aRelation +
206         NS_LITERAL_CSTRING(" "
207                            "AND t_t.parent = ") +
208         nsPrintfCString("%" PRId64, aTagsFolder) +
209         NS_LITERAL_CSTRING(" "
210                            ")"));
211   }
212 
213   _sqlFragment.AppendLiteral(" AS tags ");
214 }
215 
216 /**
217  * Recalculates invalid frecencies in chunks on the storage thread, optionally
218  * decays frecencies, and notifies history observers on the main thread.
219  */
220 class FixAndDecayFrecencyRunnable final : public Runnable {
221  public:
FixAndDecayFrecencyRunnable(Database * aDB,float aDecayRate)222   explicit FixAndDecayFrecencyRunnable(Database* aDB, float aDecayRate)
223       : Runnable("places::FixAndDecayFrecencyRunnable"),
224         mDB(aDB),
225         mDecayRate(aDecayRate),
226         mDecayReason(mozIStorageStatementCallback::REASON_FINISHED) {}
227 
228   NS_IMETHOD
Run()229   Run() override {
230     if (NS_IsMainThread()) {
231       nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
232       NS_ENSURE_STATE(navHistory);
233 
234       navHistory->DecayFrecencyCompleted(mDecayReason);
235       return NS_OK;
236     }
237 
238     MOZ_ASSERT(!NS_IsMainThread(),
239                "Frecencies should be recalculated on async thread");
240 
241     nsCOMPtr<mozIStorageStatement> updateStmt = mDB->GetStatement(
242         "UPDATE moz_places "
243         "SET frecency = CALCULATE_FRECENCY(id) "
244         "WHERE id IN ("
245         "SELECT id FROM moz_places "
246         "WHERE frecency < 0 "
247         "ORDER BY frecency ASC "
248         "LIMIT 400"
249         ")");
250     NS_ENSURE_STATE(updateStmt);
251     nsresult rv = updateStmt->Execute();
252     NS_ENSURE_SUCCESS(rv, rv);
253 
254     nsCOMPtr<mozIStorageStatement> selectStmt = mDB->GetStatement(
255         "SELECT id FROM moz_places WHERE frecency < 0 "
256         "LIMIT 1");
257     NS_ENSURE_STATE(selectStmt);
258     bool hasResult = false;
259     rv = selectStmt->ExecuteStep(&hasResult);
260     NS_ENSURE_SUCCESS(rv, rv);
261     if (hasResult) {
262       // There are more invalid frecencies to fix. Re-dispatch to the async
263       // storage thread for the next chunk.
264       return NS_DispatchToCurrentThread(this);
265     }
266 
267     mozStorageTransaction transaction(
268         mDB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
269 
270     if (NS_WARN_IF(NS_FAILED(DecayFrecencies()))) {
271       mDecayReason = mozIStorageStatementCallback::REASON_ERROR;
272     }
273 
274     // We've finished fixing and decaying frecencies. Trigger frecency updates
275     // for all affected origins.
276     nsCOMPtr<mozIStorageStatement> updateOriginFrecenciesStmt =
277         mDB->GetStatement("DELETE FROM moz_updateoriginsupdate_temp");
278     NS_ENSURE_STATE(updateOriginFrecenciesStmt);
279     rv = updateOriginFrecenciesStmt->Execute();
280     NS_ENSURE_SUCCESS(rv, rv);
281 
282     rv = transaction.Commit();
283     NS_ENSURE_SUCCESS(rv, rv);
284 
285     // Re-dispatch to the main thread to notify observers.
286     return NS_DispatchToMainThread(this);
287   }
288 
289  private:
DecayFrecencies()290   nsresult DecayFrecencies() {
291     TimeStamp start = TimeStamp::Now();
292 
293     // Globally decay places frecency rankings to estimate reduced frecency
294     // values of pages that haven't been visited for a while, i.e., they do
295     // not get an updated frecency.  A scaling factor of .975 results in .5 the
296     // original value after 28 days.
297     // When changing the scaling factor, ensure that the barrier in
298     // moz_places_afterupdate_frecency_trigger still ignores these changes.
299     nsCOMPtr<mozIStorageStatement> decayFrecency = mDB->GetStatement(
300         "UPDATE moz_places SET frecency = ROUND(frecency * :decay_rate) "
301         "WHERE frecency > 0");
302     NS_ENSURE_STATE(decayFrecency);
303     nsresult rv = decayFrecency->BindDoubleByName(
304         NS_LITERAL_CSTRING("decay_rate"), static_cast<double>(mDecayRate));
305     NS_ENSURE_SUCCESS(rv, rv);
306     rv = decayFrecency->Execute();
307     NS_ENSURE_SUCCESS(rv, rv);
308 
309     // Decay potentially unused adaptive entries (e.g. those that are at 1)
310     // to allow better chances for new entries that will start at 1.
311     nsCOMPtr<mozIStorageStatement> decayAdaptive = mDB->GetStatement(
312         "UPDATE moz_inputhistory SET use_count = use_count * :decay_rate");
313     NS_ENSURE_STATE(decayAdaptive);
314     rv = decayAdaptive->BindDoubleByName(NS_LITERAL_CSTRING("decay_rate"),
315                                          static_cast<double>(mDecayRate));
316     NS_ENSURE_SUCCESS(rv, rv);
317     rv = decayAdaptive->Execute();
318     NS_ENSURE_SUCCESS(rv, rv);
319 
320     // Delete any adaptive entries that won't help in ordering anymore.
321     nsCOMPtr<mozIStorageStatement> deleteAdaptive = mDB->GetStatement(
322         "DELETE FROM moz_inputhistory WHERE use_count < :use_count");
323     NS_ENSURE_STATE(deleteAdaptive);
324     rv = deleteAdaptive->BindDoubleByName(
325         NS_LITERAL_CSTRING("use_count"),
326         std::pow(static_cast<double>(mDecayRate),
327                  ADAPTIVE_HISTORY_EXPIRE_DAYS));
328     NS_ENSURE_SUCCESS(rv, rv);
329     rv = deleteAdaptive->Execute();
330     NS_ENSURE_SUCCESS(rv, rv);
331 
332     Telemetry::AccumulateTimeDelta(
333         Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS, start);
334 
335     return NS_OK;
336   }
337 
338   RefPtr<Database> mDB;
339   float mDecayRate;
340   uint16_t mDecayReason;
341 };
342 
343 }  // namespace
344 
345 // Queries rows indexes to bind or get values, if adding a new one, be sure to
346 // update nsNavBookmarks statements and its kGetChildrenIndex_* constants
347 const int32_t nsNavHistory::kGetInfoIndex_PageID = 0;
348 const int32_t nsNavHistory::kGetInfoIndex_URL = 1;
349 const int32_t nsNavHistory::kGetInfoIndex_Title = 2;
350 const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3;
351 const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4;
352 const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5;
353 const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6;
354 const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
355 const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
356 const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
357 const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
358 const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
359 const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
360 const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
361 const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
362 const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
363 const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
364 const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
365 // These columns are followed by corresponding constants in nsNavBookmarks.cpp,
366 // which must be kept in sync:
367 // nsNavBookmarks::kGetChildrenIndex_Guid = 18;
368 // nsNavBookmarks::kGetChildrenIndex_Position = 19;
369 // nsNavBookmarks::kGetChildrenIndex_Type = 20;
370 // nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
371 
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory,gHistoryService)372 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
373 
374 nsNavHistory::nsNavHistory()
375     : mCachedNow(0),
376       mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
377       mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
378       mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
379       mHistoryEnabled(true),
380       mMatchDiacritics(false),
381       mNumVisitsForFrecency(10),
382       mDecayFrecencyPendingCount(0),
383       mTagsFolder(-1),
384       mDaysOfHistory(-1),
385       mLastCachedStartOfDay(INT64_MAX),
386       mLastCachedEndOfDay(0),
387       mCanNotify(true)
388 #ifdef XP_WIN
389       ,
390       mCryptoProviderInitialized(false)
391 #endif
392 {
393   NS_ASSERTION(!gHistoryService,
394                "Attempting to create two instances of the service!");
395 #ifdef XP_WIN
396   BOOL cryptoAcquired =
397       CryptAcquireContext(&mCryptoProvider, 0, 0, PROV_RSA_FULL,
398                           CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
399   if (cryptoAcquired) {
400     mCryptoProviderInitialized = true;
401   }
402 #endif
403   gHistoryService = this;
404 }
405 
~nsNavHistory()406 nsNavHistory::~nsNavHistory() {
407   MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
408 
409   // remove the static reference to the service. Check to make sure its us
410   // in case somebody creates an extra instance of the service.
411   NS_ASSERTION(gHistoryService == this,
412                "Deleting a non-singleton instance of the service");
413 
414   if (gHistoryService == this) gHistoryService = nullptr;
415 
416 #ifdef XP_WIN
417   if (mCryptoProviderInitialized) {
418     Unused << CryptReleaseContext(mCryptoProvider, 0);
419   }
420 #endif
421 }
422 
Init()423 nsresult nsNavHistory::Init() {
424   LoadPrefs();
425 
426   mDB = Database::GetDatabase();
427   NS_ENSURE_STATE(mDB);
428 
429   /*****************************************************************************
430    *** IMPORTANT NOTICE!
431    ***
432    *** Nothing after these add observer calls should return anything but NS_OK.
433    *** If a failure code is returned, this nsNavHistory object will be held onto
434    *** by the observer service and the preference service.
435    ****************************************************************************/
436 
437   // Observe preferences changes.
438   Preferences::AddWeakObservers(this, kObservedPrefs);
439 
440   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
441   if (obsSvc) {
442     (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
443     (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true);
444   }
445 
446   // Don't add code that can fail here! Do it up above, before we add our
447   // observers.
448 
449   return NS_OK;
450 }
451 
452 NS_IMETHODIMP
GetDatabaseStatus(uint16_t * aDatabaseStatus)453 nsNavHistory::GetDatabaseStatus(uint16_t* aDatabaseStatus) {
454   NS_ENSURE_ARG_POINTER(aDatabaseStatus);
455   *aDatabaseStatus = mDB->GetDatabaseStatus();
456   return NS_OK;
457 }
458 
GetRecentFlags(nsIURI * aURI)459 uint32_t nsNavHistory::GetRecentFlags(nsIURI* aURI) {
460   uint32_t result = 0;
461   nsAutoCString spec;
462   nsresult rv = aURI->GetSpec(spec);
463   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
464 
465   if (NS_SUCCEEDED(rv)) {
466     if (CheckIsRecentEvent(&mRecentTyped, spec)) result |= RECENT_TYPED;
467     if (CheckIsRecentEvent(&mRecentLink, spec)) result |= RECENT_ACTIVATED;
468     if (CheckIsRecentEvent(&mRecentBookmark, spec)) result |= RECENT_BOOKMARKED;
469   }
470 
471   return result;
472 }
473 
GetIdForPage(nsIURI * aURI,int64_t * _pageId,nsCString & _GUID)474 nsresult nsNavHistory::GetIdForPage(nsIURI* aURI, int64_t* _pageId,
475                                     nsCString& _GUID) {
476   *_pageId = 0;
477 
478   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
479       "SELECT id, url, title, rev_host, visit_count, guid "
480       "FROM moz_places "
481       "WHERE url_hash = hash(:page_url) AND url = :page_url ");
482   NS_ENSURE_STATE(stmt);
483   mozStorageStatementScoper scoper(stmt);
484 
485   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
486   NS_ENSURE_SUCCESS(rv, rv);
487 
488   bool hasEntry = false;
489   rv = stmt->ExecuteStep(&hasEntry);
490   NS_ENSURE_SUCCESS(rv, rv);
491 
492   if (hasEntry) {
493     rv = stmt->GetInt64(0, _pageId);
494     NS_ENSURE_SUCCESS(rv, rv);
495     rv = stmt->GetUTF8String(5, _GUID);
496     NS_ENSURE_SUCCESS(rv, rv);
497   }
498 
499   return NS_OK;
500 }
501 
GetOrCreateIdForPage(nsIURI * aURI,int64_t * _pageId,nsCString & _GUID)502 nsresult nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, int64_t* _pageId,
503                                             nsCString& _GUID) {
504   nsresult rv = GetIdForPage(aURI, _pageId, _GUID);
505   NS_ENSURE_SUCCESS(rv, rv);
506 
507   if (*_pageId != 0) {
508     return NS_OK;
509   }
510 
511   {
512     // Create a new hidden, untyped and unvisited entry.
513     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
514         "INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, "
515         "guid) "
516         "VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, "
517         ":guid) ");
518     NS_ENSURE_STATE(stmt);
519     mozStorageStatementScoper scoper(stmt);
520 
521     rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
522     NS_ENSURE_SUCCESS(rv, rv);
523     // host (reversed with trailing period)
524     nsAutoString revHost;
525     rv = GetReversedHostname(aURI, revHost);
526     // Not all URI types have hostnames, so this is optional.
527     if (NS_SUCCEEDED(rv)) {
528       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
529     } else {
530       rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host"));
531     }
532     NS_ENSURE_SUCCESS(rv, rv);
533     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
534     NS_ENSURE_SUCCESS(rv, rv);
535     nsAutoCString spec;
536     rv = aURI->GetSpec(spec);
537     NS_ENSURE_SUCCESS(rv, rv);
538     rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
539                                IsQueryURI(spec) ? 0 : -1);
540     NS_ENSURE_SUCCESS(rv, rv);
541     rv = GenerateGUID(_GUID);
542     NS_ENSURE_SUCCESS(rv, rv);
543     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
544     NS_ENSURE_SUCCESS(rv, rv);
545 
546     rv = stmt->Execute();
547     NS_ENSURE_SUCCESS(rv, rv);
548 
549     *_pageId = sLastInsertedPlaceId;
550   }
551 
552   {
553     // Trigger the updates to the moz_origins tables
554     nsCOMPtr<mozIStorageStatement> stmt =
555         mDB->GetStatement("DELETE FROM moz_updateoriginsinsert_temp");
556     NS_ENSURE_STATE(stmt);
557     mozStorageStatementScoper scoper(stmt);
558   }
559 
560   return NS_OK;
561 }
562 
LoadPrefs()563 void nsNavHistory::LoadPrefs() {
564   // History preferences.
565   mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true);
566   mMatchDiacritics = Preferences::GetBool(PREF_MATCH_DIACRITICS, false);
567 
568   // Frecency preferences.
569 #define FRECENCY_PREF(_prop, _pref) \
570   _prop = Preferences::GetInt(_pref, _pref##_DEF)
571 
572   FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS);
573   FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF);
574   FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF);
575   FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF);
576   FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF);
577   FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS);
578   FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS);
579   FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS);
580   FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS);
581   FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS);
582   FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS);
583   FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS);
584   FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS);
585   FRECENCY_PREF(mRedirectSourceVisitBonus, PREF_FREC_REDIR_SOURCE_VISIT_BONUS);
586   FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS);
587   FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS);
588   FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS);
589   FRECENCY_PREF(mReloadVisitBonus, PREF_FREC_RELOAD_VISIT_BONUS);
590   FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT);
591   FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT);
592   FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT);
593   FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT);
594   FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT);
595 
596 #undef FRECENCY_PREF
597 }
598 
UpdateDaysOfHistory(PRTime visitTime)599 void nsNavHistory::UpdateDaysOfHistory(PRTime visitTime) {
600   if (mDaysOfHistory == 0) {
601     mDaysOfHistory = 1;
602   }
603 
604   if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
605     mDaysOfHistory = -1;
606   }
607 }
608 
NotifyTitleChange(nsIURI * aURI,const nsString & aTitle,const nsACString & aGUID)609 void nsNavHistory::NotifyTitleChange(nsIURI* aURI, const nsString& aTitle,
610                                      const nsACString& aGUID) {
611   MOZ_ASSERT(!aGUID.IsEmpty());
612   NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
613                    OnTitleChanged(aURI, aTitle, aGUID));
614 }
615 
NotifyFrecencyChanged(const nsACString & aSpec,int32_t aNewFrecency,const nsACString & aGUID,bool aHidden,PRTime aLastVisitDate)616 void nsNavHistory::NotifyFrecencyChanged(const nsACString& aSpec,
617                                          int32_t aNewFrecency,
618                                          const nsACString& aGUID, bool aHidden,
619                                          PRTime aLastVisitDate) {
620   MOZ_ASSERT(!aGUID.IsEmpty());
621 
622   nsCOMPtr<nsIURI> uri;
623   Unused << NS_NewURI(getter_AddRefs(uri), aSpec);
624   // We cannot assert since some automated tests are checking this path.
625   NS_WARNING_ASSERTION(uri,
626                        "Invalid URI in nsNavHistory::NotifyFrecencyChanged");
627   // Notify a frecency change only if we have a valid uri, otherwise
628   // the observer couldn't gather any useful data from the notification.
629   if (!uri) {
630     return;
631   }
632   NOTIFY_OBSERVERS(
633       mCanNotify, mObservers, nsINavHistoryObserver,
634       OnFrecencyChanged(uri, aNewFrecency, aGUID, aHidden, aLastVisitDate));
635 }
636 
NotifyManyFrecenciesChanged()637 void nsNavHistory::NotifyManyFrecenciesChanged() {
638   NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
639                    OnManyFrecenciesChanged());
640 }
641 
DispatchFrecencyChangedNotification(const nsACString & aSpec,int32_t aNewFrecency,const nsACString & aGUID,bool aHidden,PRTime aLastVisitDate) const642 void nsNavHistory::DispatchFrecencyChangedNotification(
643     const nsACString& aSpec, int32_t aNewFrecency, const nsACString& aGUID,
644     bool aHidden, PRTime aLastVisitDate) const {
645   Unused << NS_DispatchToMainThread(
646       NewRunnableMethod<nsCString, int32_t, nsCString, bool, PRTime>(
647           "nsNavHistory::NotifyFrecencyChanged",
648           const_cast<nsNavHistory*>(this), &nsNavHistory::NotifyFrecencyChanged,
649           aSpec, aNewFrecency, aGUID, aHidden, aLastVisitDate));
650 }
651 
652 NS_IMETHODIMP
RecalculateOriginFrecencyStats(nsIObserver * aCallback)653 nsNavHistory::RecalculateOriginFrecencyStats(nsIObserver* aCallback) {
654   RefPtr<nsNavHistory> self(this);
655   nsMainThreadPtrHandle<nsIObserver> callback(
656       !aCallback ? nullptr
657                  : new nsMainThreadPtrHolder<nsIObserver>(
658                        "nsNavHistory::RecalculateOriginFrecencyStats callback",
659                        aCallback));
660 
661   nsCOMPtr<nsIEventTarget> target(do_GetInterface(mDB->MainConn()));
662   NS_ENSURE_STATE(target);
663   nsresult rv = target->Dispatch(NS_NewRunnableFunction(
664       "nsNavHistory::RecalculateOriginFrecencyStats", [self, callback] {
665         Unused << self->RecalculateOriginFrecencyStatsInternal();
666         Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
667             "nsNavHistory::RecalculateOriginFrecencyStats callback",
668             [callback] {
669               if (callback) {
670                 Unused << callback->Observe(nullptr, "", nullptr);
671               }
672             }));
673       }));
674   NS_ENSURE_SUCCESS(rv, rv);
675 
676   return NS_OK;
677 }
678 
RecalculateOriginFrecencyStatsInternal()679 nsresult nsNavHistory::RecalculateOriginFrecencyStatsInternal() {
680   nsCOMPtr<mozIStorageConnection> conn(mDB->MainConn());
681   NS_ENSURE_STATE(conn);
682 
683   nsresult rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
684       "INSERT OR REPLACE INTO moz_meta(key, value) VALUES "
685       "( "
686       "'" MOZ_META_KEY_ORIGIN_FRECENCY_COUNT
687       "' , "
688       "(SELECT COUNT(*) FROM moz_origins WHERE frecency > 0) "
689       "), "
690       "( "
691       "'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM
692       "', "
693       "(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
694       "), "
695       "( "
696       "'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES
697       "' , "
698       "(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
699       ") "));
700   NS_ENSURE_SUCCESS(rv, rv);
701 
702   return NS_OK;
703 }
704 
705 Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
706 Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
707 
708 void  // static
StoreLastInsertedId(const nsACString & aTable,const int64_t aLastInsertedId)709 nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
710                                   const int64_t aLastInsertedId) {
711   if (aTable.EqualsLiteral("moz_places")) {
712     nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
713   } else if (aTable.EqualsLiteral("moz_historyvisits")) {
714     nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
715   } else {
716     MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
717   }
718 }
719 
GetDaysOfHistory()720 int32_t nsNavHistory::GetDaysOfHistory() {
721   MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
722 
723   if (mDaysOfHistory != -1) return mDaysOfHistory;
724 
725   // SQLite doesn't have a CEIL() function, so we must do that later.
726   // We should also take into account timers resolution, that may be as bad as
727   // 16ms on Windows, so in some cases the difference may be 0, if the
728   // check is done near the visit.  Thus remember to check for NULL separately.
729   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
730       "SELECT CAST(( "
731       "strftime('%s','now','localtime','utc') - "
732       "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) "
733       ") AS DOUBLE) "
734       "/86400, "
735       "strftime('%s','now','localtime','+1 day','start of day','utc') * "
736       "1000000");
737   NS_ENSURE_TRUE(stmt, 0);
738   mozStorageStatementScoper scoper(stmt);
739 
740   bool hasResult;
741   if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
742     // If we get NULL, then there are no visits, otherwise there must always be
743     // at least 1 day of history.
744     bool hasNoVisits;
745     (void)stmt->GetIsNull(0, &hasNoVisits);
746     mDaysOfHistory =
747         hasNoVisits
748             ? 0
749             : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0))));
750     mLastCachedStartOfDay =
751         NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
752     mLastCachedEndOfDay = stmt->AsInt64(1) - 1;  // Start of tomorrow - 1.
753   }
754 
755   return mDaysOfHistory;
756 }
757 
GetNow()758 PRTime nsNavHistory::GetNow() {
759   if (!mCachedNow) {
760     mCachedNow = PR_Now();
761     if (!mExpireNowTimer) mExpireNowTimer = NS_NewTimer();
762     if (mExpireNowTimer)
763       mExpireNowTimer->InitWithNamedFuncCallback(
764           expireNowTimerCallback, this, RENEW_CACHED_NOW_TIMEOUT,
765           nsITimer::TYPE_ONE_SHOT, "nsNavHistory::GetNow");
766   }
767   return mCachedNow;
768 }
769 
expireNowTimerCallback(nsITimer * aTimer,void * aClosure)770 void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) {
771   nsNavHistory* history = static_cast<nsNavHistory*>(aClosure);
772   if (history) {
773     history->mCachedNow = 0;
774     history->mExpireNowTimer = nullptr;
775   }
776 }
777 
778 /**
779  * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
780  * Pass in a pre-normalized now and a date, and we'll find the difference since
781  * midnight on each of the days.
782  */
NormalizeTimeRelativeToday(PRTime aTime)783 static PRTime NormalizeTimeRelativeToday(PRTime aTime) {
784   // round to midnight this morning
785   PRExplodedTime explodedTime;
786   PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
787 
788   // set to midnight (0:00)
789   explodedTime.tm_min = explodedTime.tm_hour = explodedTime.tm_sec =
790       explodedTime.tm_usec = 0;
791 
792   return PR_ImplodeTime(&explodedTime);
793 }
794 
795 // nsNavHistory::NormalizeTime
796 //
797 //    Converts a nsINavHistoryQuery reference+offset time into a PRTime
798 //    relative to the epoch.
799 //
800 //    It is important that this function NOT use the current time optimization.
801 //    It is called to update queries, and we really need to know what right
802 //    now is because those incoming values will also have current times that
803 //    we will have to compare against.
804 
805 PRTime  // static
NormalizeTime(uint32_t aRelative,PRTime aOffset)806 nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) {
807   PRTime ref;
808   switch (aRelative) {
809     case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
810       return aOffset;
811     case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
812       ref = NormalizeTimeRelativeToday(PR_Now());
813       break;
814     case nsINavHistoryQuery::TIME_RELATIVE_NOW:
815       ref = PR_Now();
816       break;
817     default:
818       MOZ_ASSERT_UNREACHABLE("Invalid relative time");
819       return 0;
820   }
821   return ref + aOffset;
822 }
823 
824 // nsNavHistory::DomainNameFromURI
825 //
826 //    This does the www.mozilla.org -> mozilla.org and
827 //    foo.theregister.co.uk -> theregister.co.uk conversion
DomainNameFromURI(nsIURI * aURI,nsACString & aDomainName)828 void nsNavHistory::DomainNameFromURI(nsIURI* aURI, nsACString& aDomainName) {
829   // lazily get the effective tld service
830   if (!mTLDService)
831     mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
832 
833   if (mTLDService) {
834     // get the base domain for a given hostname.
835     // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
836     nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName);
837     if (NS_SUCCEEDED(rv)) return;
838   }
839 
840   // just return the original hostname
841   // (it's also possible the host is an IP address)
842   aURI->GetAsciiHost(aDomainName);
843 }
844 
hasHistoryEntries()845 bool nsNavHistory::hasHistoryEntries() { return GetDaysOfHistory() > 0; }
846 
847 // Call this method before visiting a URL in order to help determine the
848 // transition type of the visit.
849 //
850 // @see MarkPageAsTyped
851 
852 NS_IMETHODIMP
MarkPageAsFollowedBookmark(nsIURI * aURI)853 nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) {
854   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
855   NS_ENSURE_ARG(aURI);
856 
857   // don't add when history is disabled
858   if (IsHistoryDisabled()) return NS_OK;
859 
860   nsAutoCString uriString;
861   nsresult rv = aURI->GetSpec(uriString);
862   NS_ENSURE_SUCCESS(rv, rv);
863 
864   mRecentBookmark.Put(uriString, GetNow());
865 
866   if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
867     ExpireNonrecentEvents(&mRecentBookmark);
868 
869   return NS_OK;
870 }
871 
872 // nsNavHistory::CanAddURI
873 //
874 //    Filter out unwanted URIs such as "chrome:", "mailbox:", etc.
875 //
876 //    The model is if we don't know differently then add which basically means
877 //    we are suppose to try all the things we know not to allow in and then if
878 //    we don't bail go on and allow it in.
879 
880 NS_IMETHODIMP
CanAddURI(nsIURI * aURI,bool * canAdd)881 nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) {
882   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
883   NS_ENSURE_ARG(aURI);
884   NS_ENSURE_ARG_POINTER(canAdd);
885 
886   // If history is disabled, don't add any entry.
887   if (IsHistoryDisabled()) {
888     *canAdd = false;
889     return NS_OK;
890   }
891 
892   return CanAddURIToHistory(aURI, canAdd);
893 }
894 
895 // nsNavHistory::CanAddURIToHistory
896 //
897 //    Helper for nsNavHistory::CanAddURI to be callable from a child process
898 
899 // static
CanAddURIToHistory(nsIURI * aURI,bool * aCanAdd)900 nsresult nsNavHistory::CanAddURIToHistory(nsIURI* aURI, bool* aCanAdd) {
901   // Default to false.
902   *aCanAdd = false;
903 
904   // If the url length is over a threshold, don't add it.
905   nsCString spec;
906   nsresult rv = aURI->GetSpec(spec);
907   NS_ENSURE_SUCCESS(rv, rv);
908   if (spec.Length() > MaxURILength()) {
909     return NS_OK;
910   }
911 
912   nsAutoCString scheme;
913   rv = aURI->GetScheme(scheme);
914   NS_ENSURE_SUCCESS(rv, rv);
915 
916   // first check the most common cases
917   if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) {
918     *aCanAdd = true;
919     return NS_OK;
920   }
921 
922   // now check for all bad things
923   *aCanAdd =
924       !scheme.EqualsLiteral("about") && !scheme.EqualsLiteral("blob") &&
925       !scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("data") &&
926       !scheme.EqualsLiteral("imap") && !scheme.EqualsLiteral("javascript") &&
927       !scheme.EqualsLiteral("mailbox") && !scheme.EqualsLiteral("moz-anno") &&
928       !scheme.EqualsLiteral("news") && !scheme.EqualsLiteral("page-icon") &&
929       !scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("view-source");
930 
931   return NS_OK;
932 }
933 
934 // nsNavHistory::MaxURILength
935 
936 // static
MaxURILength()937 uint32_t nsNavHistory::MaxURILength() {
938   // Duplicates Database::MaxUrlLength() for use in
939   // child processes without a database instance.
940   static uint32_t maxSpecLength = 0;
941   if (!maxSpecLength) {
942     maxSpecLength = Preferences::GetInt(PREF_HISTORY_MAXURLLEN,
943                                         PREF_HISTORY_MAXURLLEN_DEFAULT);
944     if (maxSpecLength < 255 || maxSpecLength > INT32_MAX) {
945       maxSpecLength = PREF_HISTORY_MAXURLLEN_DEFAULT;
946     }
947   }
948   return maxSpecLength;
949 }
950 
951 // nsNavHistory::GetNewQuery
952 
953 NS_IMETHODIMP
GetNewQuery(nsINavHistoryQuery ** _retval)954 nsNavHistory::GetNewQuery(nsINavHistoryQuery** _retval) {
955   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
956   NS_ENSURE_ARG_POINTER(_retval);
957 
958   RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
959   query.forget(_retval);
960   return NS_OK;
961 }
962 
963 // nsNavHistory::GetNewQueryOptions
964 
965 NS_IMETHODIMP
GetNewQueryOptions(nsINavHistoryQueryOptions ** _retval)966 nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions** _retval) {
967   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
968   NS_ENSURE_ARG_POINTER(_retval);
969 
970   RefPtr<nsNavHistoryQueryOptions> queryOptions =
971       new nsNavHistoryQueryOptions();
972   queryOptions.forget(_retval);
973   return NS_OK;
974 }
975 
976 NS_IMETHODIMP
ExecuteQuery(nsINavHistoryQuery * aQuery,nsINavHistoryQueryOptions * aOptions,nsINavHistoryResult ** _retval)977 nsNavHistory::ExecuteQuery(nsINavHistoryQuery* aQuery,
978                            nsINavHistoryQueryOptions* aOptions,
979                            nsINavHistoryResult** _retval) {
980   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
981   NS_ENSURE_ARG(aQuery);
982   NS_ENSURE_ARG(aOptions);
983   NS_ENSURE_ARG_POINTER(_retval);
984 
985   // Clone the input query and options, because the caller might change the
986   // objects, but we always want to reflect the original parameters.
987   nsCOMPtr<nsINavHistoryQuery> queryClone;
988   aQuery->Clone(getter_AddRefs(queryClone));
989   NS_ENSURE_STATE(queryClone);
990   RefPtr<nsNavHistoryQuery> query = do_QueryObject(queryClone);
991   NS_ENSURE_STATE(query);
992   nsCOMPtr<nsINavHistoryQueryOptions> optionsClone;
993   aOptions->Clone(getter_AddRefs(optionsClone));
994   NS_ENSURE_STATE(optionsClone);
995   RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(optionsClone);
996   NS_ENSURE_STATE(options);
997 
998   // Create the root node.
999   RefPtr<nsNavHistoryContainerResultNode> rootNode;
1000 
1001   nsCString folderGuid = GetSimpleBookmarksQueryParent(query, options);
1002   if (!folderGuid.IsEmpty()) {
1003     // In the simple case where we're just querying children of a single
1004     // bookmark folder, we can more efficiently generate results.
1005     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
1006     NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
1007     RefPtr<nsNavHistoryResultNode> tempRootNode;
1008     nsresult rv = bookmarks->ResultNodeForContainer(
1009         folderGuid, options, getter_AddRefs(tempRootNode));
1010     if (NS_SUCCEEDED(rv)) {
1011       rootNode = tempRootNode->GetAsContainer();
1012     } else {
1013       NS_WARNING("Generating a generic empty node for a broken query!");
1014       // This is a perf hack to generate an empty query that skips filtering.
1015       options->SetExcludeItems(true);
1016     }
1017   }
1018 
1019   if (!rootNode) {
1020     // Either this is not a folder shortcut, or is a broken one.  In both cases
1021     // just generate a query node.
1022     nsAutoCString queryUri;
1023     nsresult rv = QueryToQueryString(query, options, queryUri);
1024     NS_ENSURE_SUCCESS(rv, rv);
1025     rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), 0, queryUri,
1026                                                query, options);
1027   }
1028 
1029   // Create the result that will hold nodes.  Inject batching status into it.
1030   RefPtr<nsNavHistoryResult> result =
1031       new nsNavHistoryResult(rootNode, query, options);
1032   result.forget(_retval);
1033   return NS_OK;
1034 }
1035 
1036 // determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions
1037 // if this is the place query from the history menu.
1038 // from browser-menubar.inc, our history menu query is:
1039 // place:sort=4&maxResults=10
1040 // note, any maxResult > 0 will still be considered a history menu query
1041 // or if this is the place query from the old "Most Visited" item in some
1042 // profiles: folder: place:sort=8&maxResults=10 note, any maxResult > 0 will
1043 // still be considered a Most Visited menu query
IsOptimizableHistoryQuery(const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions,uint16_t aSortMode)1044 static bool IsOptimizableHistoryQuery(
1045     const RefPtr<nsNavHistoryQuery>& aQuery,
1046     const RefPtr<nsNavHistoryQueryOptions>& aOptions, uint16_t aSortMode) {
1047   if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1048     return false;
1049 
1050   if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
1051     return false;
1052 
1053   if (aOptions->SortingMode() != aSortMode) return false;
1054 
1055   if (aOptions->MaxResults() <= 0) return false;
1056 
1057   if (aOptions->ExcludeItems()) return false;
1058 
1059   if (aOptions->IncludeHidden()) return false;
1060 
1061   if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) return false;
1062 
1063   if (aQuery->BeginTime() || aQuery->BeginTimeReference()) return false;
1064 
1065   if (aQuery->EndTime() || aQuery->EndTimeReference()) return false;
1066 
1067   if (!aQuery->SearchTerms().IsEmpty()) return false;
1068 
1069   if (aQuery->OnlyBookmarked()) return false;
1070 
1071   if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) return false;
1072 
1073   if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
1074     return false;
1075 
1076   if (aQuery->Parents().Length() > 0) return false;
1077 
1078   if (aQuery->Tags().Length() > 0) return false;
1079 
1080   if (aQuery->Transitions().Length() > 0) return false;
1081 
1082   return true;
1083 }
1084 
NeedToFilterResultSet(const RefPtr<nsNavHistoryQuery> & aQuery,nsNavHistoryQueryOptions * aOptions)1085 static bool NeedToFilterResultSet(const RefPtr<nsNavHistoryQuery>& aQuery,
1086                                   nsNavHistoryQueryOptions* aOptions) {
1087   return aOptions->ExcludeQueries();
1088 }
1089 
1090 // ** Helper class for ConstructQueryString **/
1091 
1092 class PlacesSQLQueryBuilder {
1093  public:
1094   PlacesSQLQueryBuilder(const nsCString& aConditions,
1095                         const RefPtr<nsNavHistoryQuery>& aQuery,
1096                         const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1097                         bool aUseLimit, nsNavHistory::StringHash& aAddParams,
1098                         bool aHasSearchTerms);
1099 
1100   nsresult GetQueryString(nsCString& aQueryString);
1101 
1102  private:
1103   nsresult Select();
1104 
1105   nsresult SelectAsURI();
1106   nsresult SelectAsVisit();
1107   nsresult SelectAsDay();
1108   nsresult SelectAsSite();
1109   nsresult SelectAsTag();
1110   nsresult SelectAsRoots();
1111   nsresult SelectAsLeftPane();
1112 
1113   nsresult Where();
1114   nsresult GroupBy();
1115   nsresult OrderBy();
1116   nsresult Limit();
1117 
1118   void OrderByColumnIndexAsc(int32_t aIndex);
1119   void OrderByColumnIndexDesc(int32_t aIndex);
1120   // Use these if you want a case insensitive sorting.
1121   void OrderByTextColumnIndexAsc(int32_t aIndex);
1122   void OrderByTextColumnIndexDesc(int32_t aIndex);
1123 
1124   const nsCString& mConditions;
1125   bool mUseLimit;
1126   bool mHasSearchTerms;
1127 
1128   uint16_t mResultType;
1129   uint16_t mQueryType;
1130   bool mIncludeHidden;
1131   uint16_t mSortingMode;
1132   uint32_t mMaxResults;
1133 
1134   nsCString mQueryString;
1135   nsCString mGroupBy;
1136   bool mHasDateColumns;
1137   bool mSkipOrderBy;
1138 
1139   nsNavHistory::StringHash& mAddParams;
1140 };
1141 
PlacesSQLQueryBuilder(const nsCString & aConditions,const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions,bool aUseLimit,nsNavHistory::StringHash & aAddParams,bool aHasSearchTerms)1142 PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
1143     const nsCString& aConditions, const RefPtr<nsNavHistoryQuery>& aQuery,
1144     const RefPtr<nsNavHistoryQueryOptions>& aOptions, bool aUseLimit,
1145     nsNavHistory::StringHash& aAddParams, bool aHasSearchTerms)
1146     : mConditions(aConditions),
1147       mUseLimit(aUseLimit),
1148       mHasSearchTerms(aHasSearchTerms),
1149       mResultType(aOptions->ResultType()),
1150       mQueryType(aOptions->QueryType()),
1151       mIncludeHidden(aOptions->IncludeHidden()),
1152       mSortingMode(aOptions->SortingMode()),
1153       mMaxResults(aOptions->MaxResults()),
1154       mSkipOrderBy(false),
1155       mAddParams(aAddParams) {
1156   mHasDateColumns =
1157       (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
1158   // Force the default sorting mode for tag queries.
1159   if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
1160       aQuery->Tags().Length() > 0) {
1161     mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1162   }
1163 }
1164 
GetQueryString(nsCString & aQueryString)1165 nsresult PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) {
1166   nsresult rv = Select();
1167   NS_ENSURE_SUCCESS(rv, rv);
1168   rv = Where();
1169   NS_ENSURE_SUCCESS(rv, rv);
1170   rv = GroupBy();
1171   NS_ENSURE_SUCCESS(rv, rv);
1172   rv = OrderBy();
1173   NS_ENSURE_SUCCESS(rv, rv);
1174   rv = Limit();
1175   NS_ENSURE_SUCCESS(rv, rv);
1176 
1177   aQueryString = mQueryString;
1178   return NS_OK;
1179 }
1180 
Select()1181 nsresult PlacesSQLQueryBuilder::Select() {
1182   nsresult rv;
1183 
1184   switch (mResultType) {
1185     case nsINavHistoryQueryOptions::RESULTS_AS_URI:
1186       rv = SelectAsURI();
1187       NS_ENSURE_SUCCESS(rv, rv);
1188       break;
1189 
1190     case nsINavHistoryQueryOptions::RESULTS_AS_VISIT:
1191       rv = SelectAsVisit();
1192       NS_ENSURE_SUCCESS(rv, rv);
1193       break;
1194 
1195     case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY:
1196     case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY:
1197       rv = SelectAsDay();
1198       NS_ENSURE_SUCCESS(rv, rv);
1199       break;
1200 
1201     case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY:
1202       rv = SelectAsSite();
1203       NS_ENSURE_SUCCESS(rv, rv);
1204       break;
1205 
1206     case nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT:
1207       rv = SelectAsTag();
1208       NS_ENSURE_SUCCESS(rv, rv);
1209       break;
1210 
1211     case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
1212       rv = SelectAsRoots();
1213       NS_ENSURE_SUCCESS(rv, rv);
1214       break;
1215 
1216     case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
1217       rv = SelectAsLeftPane();
1218       NS_ENSURE_SUCCESS(rv, rv);
1219       break;
1220 
1221     default:
1222       MOZ_ASSERT_UNREACHABLE("Invalid result type");
1223   }
1224   return NS_OK;
1225 }
1226 
SelectAsURI()1227 nsresult PlacesSQLQueryBuilder::SelectAsURI() {
1228   nsNavHistory* history = nsNavHistory::GetHistoryService();
1229   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1230   nsAutoCString tagsSqlFragment;
1231 
1232   switch (mQueryType) {
1233     case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
1234       GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
1235                          mHasSearchTerms, tagsSqlFragment);
1236 
1237       mQueryString = NS_LITERAL_CSTRING(
1238                          "SELECT h.id, h.url, h.title AS page_title, "
1239                          "h.rev_host, h.visit_count, "
1240                          "h.last_visit_date, null, null, null, null, null, ") +
1241                      tagsSqlFragment +
1242                      NS_LITERAL_CSTRING(
1243                          ", h.frecency, h.hidden, h.guid, "
1244                          "null, null, null "
1245                          "FROM moz_places h "
1246                          // WHERE 1 is a no-op since additonal conditions will
1247                          // start with AND.
1248                          "WHERE 1 "
1249                          "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1250                          "{ADDITIONAL_CONDITIONS} ");
1251       break;
1252 
1253     case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
1254 
1255       GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("b.fk"),
1256                          mHasSearchTerms, tagsSqlFragment);
1257       mQueryString =
1258           NS_LITERAL_CSTRING(
1259               "SELECT b.fk, h.url, b.title AS page_title, "
1260               "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
1261               "b.dateAdded, b.lastModified, b.parent, ") +
1262           tagsSqlFragment +
1263           NS_LITERAL_CSTRING(
1264               ", h.frecency, h.hidden, h.guid,"
1265               "null, null, null, b.guid, b.position, b.type, b.fk "
1266               "FROM moz_bookmarks b "
1267               "JOIN moz_places h ON b.fk = h.id "
1268               "WHERE NOT EXISTS "
1269               "(SELECT id FROM moz_bookmarks "
1270               "WHERE id = b.parent AND parent = ") +
1271           nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
1272           NS_LITERAL_CSTRING(
1273               ") "
1274               "AND NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
1275               "hash('place', 'prefix_hi') "
1276               "{ADDITIONAL_CONDITIONS}");
1277       break;
1278 
1279     default:
1280       return NS_ERROR_NOT_IMPLEMENTED;
1281   }
1282   return NS_OK;
1283 }
1284 
SelectAsVisit()1285 nsresult PlacesSQLQueryBuilder::SelectAsVisit() {
1286   nsNavHistory* history = nsNavHistory::GetHistoryService();
1287   NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1288   nsAutoCString tagsSqlFragment;
1289   GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
1290                      mHasSearchTerms, tagsSqlFragment);
1291   mQueryString =
1292       NS_LITERAL_CSTRING(
1293           "SELECT h.id, h.url, h.title AS page_title, h.rev_host, "
1294           "h.visit_count, "
1295           "v.visit_date, null, null, null, null, null, ") +
1296       tagsSqlFragment +
1297       NS_LITERAL_CSTRING(
1298           ", h.frecency, h.hidden, h.guid, "
1299           "v.id, v.from_visit, v.visit_type "
1300           "FROM moz_places h "
1301           "JOIN moz_historyvisits v ON h.id = v.place_id "
1302           // WHERE 1 is a no-op since additonal conditions will start with AND.
1303           "WHERE 1 "
1304           "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1305           "{ADDITIONAL_CONDITIONS} ");
1306 
1307   return NS_OK;
1308 }
1309 
SelectAsDay()1310 nsresult PlacesSQLQueryBuilder::SelectAsDay() {
1311   mSkipOrderBy = true;
1312 
1313   // Sort child queries based on sorting mode if it's provided, otherwise
1314   // fallback to default sort by title ascending.
1315   uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1316   if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE &&
1317       mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY)
1318     sortingMode = mSortingMode;
1319 
1320   uint16_t resultType =
1321       mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY
1322           ? (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI
1323           : (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
1324 
1325   // beginTime will become the node's time property, we don't use endTime
1326   // because it could overlap, and we use time to sort containers and find
1327   // insert position in a result.
1328   mQueryString = nsPrintfCString(
1329       "SELECT null, "
1330       "'place:type=%d&sort=%d&beginTime='||beginTime||'&endTime='||endTime, "
1331       "dayTitle, null, null, beginTime, null, null, null, null, null, null, "
1332       "null, null, null "
1333       "FROM (",  // TOUTER BEGIN
1334       resultType, sortingMode);
1335 
1336   nsNavHistory* history = nsNavHistory::GetHistoryService();
1337   NS_ENSURE_STATE(history);
1338 
1339   int32_t daysOfHistory = history->GetDaysOfHistory();
1340   for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) {
1341     nsAutoCString dateName;
1342     // Timeframes are calculated as BeginTime <= container < EndTime.
1343     // Notice times can't be relative to now, since to recognize a query we
1344     // must ensure it won't change based on the time it is built.
1345     // So, to select till now, we really select till start of tomorrow, that is
1346     // a fixed timestamp.
1347     // These are used as limits for the inside containers.
1348     nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime;
1349     // These are used to query if the container should be visible.
1350     nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime;
1351     switch (i) {
1352       case 0:
1353         // Today
1354         history->GetStringFromName("finduri-AgeInDays-is-0", dateName);
1355         // From start of today
1356         sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1357             "(strftime('%s','now','localtime','start of day','utc')*1000000)");
1358         // To now (tomorrow)
1359         sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1360             "(strftime('%s','now','localtime','start of day','+1 "
1361             "day','utc')*1000000)");
1362         // Search for the same timeframe.
1363         sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1364         sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1365         break;
1366       case 1:
1367         // Yesterday
1368         history->GetStringFromName("finduri-AgeInDays-is-1", dateName);
1369         // From start of yesterday
1370         sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1371             "(strftime('%s','now','localtime','start of day','-1 "
1372             "day','utc')*1000000)");
1373         // To start of today
1374         sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1375             "(strftime('%s','now','localtime','start of day','utc')*1000000)");
1376         // Search for the same timeframe.
1377         sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1378         sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1379         break;
1380       case 2:
1381         // Last 7 days
1382         history->GetAgeInDaysString(7, "finduri-AgeInDays-last-is", dateName);
1383         // From start of 7 days ago
1384         sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1385             "(strftime('%s','now','localtime','start of day','-7 "
1386             "days','utc')*1000000)");
1387         // To now (tomorrow)
1388         sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1389             "(strftime('%s','now','localtime','start of day','+1 "
1390             "day','utc')*1000000)");
1391         // This is an overlapped container, but we show it only if there are
1392         // visits older than yesterday.
1393         sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1394         sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1395             "(strftime('%s','now','localtime','start of day','-1 "
1396             "day','utc')*1000000)");
1397         break;
1398       case 3:
1399         // This month
1400         history->GetStringFromName("finduri-AgeInMonths-is-0", dateName);
1401         // From start of this month
1402         sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1403             "(strftime('%s','now','localtime','start of "
1404             "month','utc')*1000000)");
1405         // To now (tomorrow)
1406         sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1407             "(strftime('%s','now','localtime','start of day','+1 "
1408             "day','utc')*1000000)");
1409         // This is an overlapped container, but we show it only if there are
1410         // visits older than 7 days ago.
1411         sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1412         sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1413             "(strftime('%s','now','localtime','start of day','-7 "
1414             "days','utc')*1000000)");
1415         break;
1416       default:
1417         if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) {
1418           // Older than 6 months
1419           history->GetAgeInDaysString(6, "finduri-AgeInMonths-isgreater",
1420                                       dateName);
1421           // From start of epoch
1422           sqlFragmentContainerBeginTime =
1423               NS_LITERAL_CSTRING("(datetime(0, 'unixepoch')*1000000)");
1424           // To start of 6 months ago ( 5 months + this month).
1425           sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1426               "(strftime('%s','now','localtime','start of month','-5 "
1427               "months','utc')*1000000)");
1428           // Search for the same timeframe.
1429           sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1430           sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1431           break;
1432         }
1433         int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM;
1434         // Previous months' titles are month's name if inside this year,
1435         // month's name and year for previous years.
1436         PRExplodedTime tm;
1437         PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm);
1438         uint16_t currentYear = tm.tm_year;
1439         // Set day before month, setting month without day could cause issues.
1440         // For example setting month to February when today is 30, since
1441         // February has not 30 days, will return March instead.
1442         // Also, we use day 2 instead of day 1, so that the GMT month is always
1443         // the same as the local month. (Bug 603002)
1444         tm.tm_mday = 2;
1445         tm.tm_month -= MonthIndex;
1446         // Notice we use GMTParameters because we just want to get the first
1447         // day of each month.  Using LocalTimeParameters would instead force us
1448         // to apply a DST correction that we don't really need here.
1449         PR_NormalizeTime(&tm, PR_GMTParameters);
1450         // If the container is for a past year, add the year to its title,
1451         // otherwise just show the month name.
1452         if (tm.tm_year < currentYear) {
1453           nsNavHistory::GetMonthYear(tm, dateName);
1454         } else {
1455           nsNavHistory::GetMonthName(tm, dateName);
1456         }
1457 
1458         // From start of MonthIndex + 1 months ago
1459         sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1460             "(strftime('%s','now','localtime','start of month','-");
1461         sqlFragmentContainerBeginTime.AppendInt(MonthIndex);
1462         sqlFragmentContainerBeginTime.AppendLiteral(" months','utc')*1000000)");
1463         // To start of MonthIndex months ago
1464         sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1465             "(strftime('%s','now','localtime','start of month','-");
1466         sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1);
1467         sqlFragmentContainerEndTime.AppendLiteral(" months','utc')*1000000)");
1468         // Search for the same timeframe.
1469         sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1470         sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1471         break;
1472     }
1473 
1474     nsPrintfCString dateParam("dayTitle%d", i);
1475     mAddParams.Put(dateParam, dateName);
1476 
1477     nsPrintfCString dayRange(
1478         "SELECT :%s AS dayTitle, "
1479         "%s AS beginTime, "
1480         "%s AS endTime "
1481         "WHERE EXISTS ( "
1482         "SELECT id FROM moz_historyvisits "
1483         "WHERE visit_date >= %s "
1484         "AND visit_date < %s "
1485         "AND visit_type NOT IN (0,%d,%d) "
1486         "{QUERY_OPTIONS_VISITS} "
1487         "LIMIT 1 "
1488         ") ",
1489         dateParam.get(), sqlFragmentContainerBeginTime.get(),
1490         sqlFragmentContainerEndTime.get(), sqlFragmentSearchBeginTime.get(),
1491         sqlFragmentSearchEndTime.get(), nsINavHistoryService::TRANSITION_EMBED,
1492         nsINavHistoryService::TRANSITION_FRAMED_LINK);
1493 
1494     mQueryString.Append(dayRange);
1495 
1496     if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
1497       mQueryString.AppendLiteral(" UNION ALL ");
1498   }
1499 
1500   mQueryString.AppendLiteral(") ");  // TOUTER END
1501 
1502   return NS_OK;
1503 }
1504 
SelectAsSite()1505 nsresult PlacesSQLQueryBuilder::SelectAsSite() {
1506   nsAutoCString localFiles;
1507 
1508   nsNavHistory* history = nsNavHistory::GetHistoryService();
1509   NS_ENSURE_STATE(history);
1510 
1511   history->GetStringFromName("localhost", localFiles);
1512   mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles);
1513 
1514   // If there are additional conditions the query has to join on visits too.
1515   nsAutoCString visitsJoin;
1516   nsAutoCString additionalConditions;
1517   nsAutoCString timeConstraints;
1518   if (!mConditions.IsEmpty()) {
1519     visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id ");
1520     additionalConditions.AssignLiteral(
1521         "{QUERY_OPTIONS_VISITS} "
1522         "{QUERY_OPTIONS_PLACES} "
1523         "{ADDITIONAL_CONDITIONS} ");
1524     timeConstraints.AssignLiteral(
1525         "||'&beginTime='||:begin_time||"
1526         "'&endTime='||:end_time");
1527   }
1528 
1529   mQueryString = nsPrintfCString(
1530       "SELECT null, 'place:type=%d&sort=%d&domain=&domainIsHost=true'%s, "
1531       ":localhost, :localhost, null, null, null, null, null, null, null, "
1532       "null, null, null "
1533       "WHERE EXISTS ( "
1534       "SELECT h.id FROM moz_places h "
1535       "%s "
1536       "WHERE h.hidden = 0 "
1537       "AND h.visit_count > 0 "
1538       "AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
1539       "hash('file', 'prefix_hi') "
1540       "%s "
1541       "LIMIT 1 "
1542       ") "
1543       "UNION ALL "
1544       "SELECT null, "
1545       "'place:type=%d&sort=%d&domain='||host||'&domainIsHost=true'%s, "
1546       "host, host, null, null, null, null, null, null, null, "
1547       "null, null, null "
1548       "FROM ( "
1549       "SELECT get_unreversed_host(h.rev_host) AS host "
1550       "FROM moz_places h "
1551       "%s "
1552       "WHERE h.hidden = 0 "
1553       "AND h.rev_host <> '.' "
1554       "AND h.visit_count > 0 "
1555       "%s "
1556       "GROUP BY h.rev_host "
1557       "ORDER BY host ASC "
1558       ") ",
1559       nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
1560       timeConstraints.get(), visitsJoin.get(), additionalConditions.get(),
1561       nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
1562       timeConstraints.get(), visitsJoin.get(), additionalConditions.get());
1563 
1564   return NS_OK;
1565 }
1566 
SelectAsTag()1567 nsresult PlacesSQLQueryBuilder::SelectAsTag() {
1568   nsNavHistory* history = nsNavHistory::GetHistoryService();
1569   NS_ENSURE_STATE(history);
1570 
1571   // This allows sorting by date fields what is not possible with
1572   // other history queries.
1573   mHasDateColumns = true;
1574 
1575   // TODO (Bug 1449939): This is likely wrong, since the tag name should
1576   // probably be urlencoded, and we have no util for that in SQL, yet.
1577   // We could encode the tag when the user sets it though.
1578   mQueryString = nsPrintfCString(
1579       "SELECT null, 'place:tag=' || title, "
1580       "title, null, null, null, null, null, dateAdded, "
1581       "lastModified, null, null, null, null, null, null "
1582       "FROM moz_bookmarks "
1583       "WHERE parent = %" PRId64,
1584       history->GetTagsFolder());
1585 
1586   return NS_OK;
1587 }
1588 
SelectAsRoots()1589 nsresult PlacesSQLQueryBuilder::SelectAsRoots() {
1590   nsNavHistory* history = nsNavHistory::GetHistoryService();
1591   NS_ENSURE_STATE(history);
1592 
1593   nsAutoCString toolbarTitle;
1594   nsAutoCString menuTitle;
1595   nsAutoCString unfiledTitle;
1596 
1597   history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
1598   mAddParams.Put(NS_LITERAL_CSTRING("BookmarksToolbarFolderTitle"),
1599                  toolbarTitle);
1600   history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
1601   mAddParams.Put(NS_LITERAL_CSTRING("BookmarksMenuFolderTitle"), menuTitle);
1602   history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
1603   mAddParams.Put(NS_LITERAL_CSTRING("OtherBookmarksFolderTitle"), unfiledTitle);
1604 
1605   nsAutoCString mobileString;
1606 
1607   if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
1608     nsAutoCString mobileTitle;
1609     history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
1610     mAddParams.Put(NS_LITERAL_CSTRING("MobileBookmarksFolderTitle"),
1611                    mobileTitle);
1612 
1613     mobileString = NS_LITERAL_CSTRING(
1614         ","
1615         "(null, 'place:parent=" MOBILE_ROOT_GUID
1616         "', :MobileBookmarksFolderTitle, null, null, null, "
1617         "null, null, 0, 0, null, null, null, null, "
1618         "'" MOBILE_BOOKMARKS_VIRTUAL_GUID "', null) ");
1619   }
1620 
1621   mQueryString =
1622       NS_LITERAL_CSTRING(
1623           "SELECT * FROM ("
1624           "VALUES(null, 'place:parent=" TOOLBAR_ROOT_GUID
1625           "', :BookmarksToolbarFolderTitle, null, null, null, "
1626           "null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
1627           "(null, 'place:parent=" MENU_ROOT_GUID
1628           "', :BookmarksMenuFolderTitle, null, null, null, "
1629           "null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
1630           "(null, 'place:parent=" UNFILED_ROOT_GUID
1631           "', :OtherBookmarksFolderTitle, null, null, null, "
1632           "null, null, 0, 0, null, null, null, null, 'unfiled____v', null) ") +
1633       mobileString + NS_LITERAL_CSTRING(")");
1634 
1635   return NS_OK;
1636 }
1637 
SelectAsLeftPane()1638 nsresult PlacesSQLQueryBuilder::SelectAsLeftPane() {
1639   nsNavHistory* history = nsNavHistory::GetHistoryService();
1640   NS_ENSURE_STATE(history);
1641 
1642   nsAutoCString historyTitle;
1643   nsAutoCString downloadsTitle;
1644   nsAutoCString tagsTitle;
1645   nsAutoCString allBookmarksTitle;
1646 
1647   history->GetStringFromName("OrganizerQueryHistory", historyTitle);
1648   mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryHistory"), historyTitle);
1649   history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
1650   mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryDownloads"), downloadsTitle);
1651   history->GetStringFromName("TagsFolderTitle", tagsTitle);
1652   mAddParams.Put(NS_LITERAL_CSTRING("TagsFolderTitle"), tagsTitle);
1653   history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
1654   mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryAllBookmarks"),
1655                  allBookmarksTitle);
1656 
1657   mQueryString = nsPrintfCString(
1658       "SELECT * FROM ("
1659       "VALUES"
1660       "(null, 'place:type=%d&sort=%d', :OrganizerQueryHistory, null, null, "
1661       "null, "
1662       "null, null, 0, 0, null, null, null, null, 'history____v', null), "
1663       "(null, 'place:transition=%d&sort=%d', :OrganizerQueryDownloads, null, "
1664       "null, null, "
1665       "null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
1666       "(null, 'place:type=%d&sort=%d', :TagsFolderTitle, null, null, null, "
1667       "null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
1668       "(null, 'place:type=%d', :OrganizerQueryAllBookmarks, null, null, null, "
1669       "null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
1670       ")",
1671       nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
1672       nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1673       nsINavHistoryService::TRANSITION_DOWNLOAD,
1674       nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1675       nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT,
1676       nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
1677       nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY);
1678   return NS_OK;
1679 }
1680 
Where()1681 nsresult PlacesSQLQueryBuilder::Where() {
1682   // Set query options
1683   nsAutoCString additionalVisitsConditions;
1684   nsAutoCString additionalPlacesConditions;
1685 
1686   if (!mIncludeHidden) {
1687     additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 ");
1688   }
1689 
1690   if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
1691     // last_visit_date is updated for any kind of visit, so it's a good
1692     // indicator whether the page has visits.
1693     additionalPlacesConditions +=
1694         NS_LITERAL_CSTRING("AND last_visit_date NOTNULL ");
1695   }
1696 
1697   if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI &&
1698       !additionalVisitsConditions.IsEmpty()) {
1699     // URI results don't join on visits.
1700     nsAutoCString tmp = additionalVisitsConditions;
1701     additionalVisitsConditions =
1702         "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id ";
1703     additionalVisitsConditions.Append(tmp);
1704     additionalVisitsConditions.AppendLiteral("LIMIT 1)");
1705   }
1706 
1707   mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
1708                                 additionalVisitsConditions.get());
1709   mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
1710                                 additionalPlacesConditions.get());
1711 
1712   // If we used WHERE already, we inject the conditions
1713   // in place of {ADDITIONAL_CONDITIONS}
1714   if (mQueryString.Find("{ADDITIONAL_CONDITIONS}") != kNotFound) {
1715     nsAutoCString innerCondition;
1716     // If we have condition AND it
1717     if (!mConditions.IsEmpty()) {
1718       innerCondition = " AND (";
1719       innerCondition += mConditions;
1720       innerCondition += ")";
1721     }
1722     mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}",
1723                                   innerCondition.get());
1724 
1725   } else if (!mConditions.IsEmpty()) {
1726     mQueryString += "WHERE ";
1727     mQueryString += mConditions;
1728   }
1729   return NS_OK;
1730 }
1731 
GroupBy()1732 nsresult PlacesSQLQueryBuilder::GroupBy() {
1733   mQueryString += mGroupBy;
1734   return NS_OK;
1735 }
1736 
OrderBy()1737 nsresult PlacesSQLQueryBuilder::OrderBy() {
1738   if (mSkipOrderBy) return NS_OK;
1739 
1740   // Sort clause: we will sort later, but if it comes out of the DB sorted,
1741   // our later sort will be basically free. The DB can sort these for free
1742   // most of the time anyway, because it has indices over these items.
1743   switch (mSortingMode) {
1744     case nsINavHistoryQueryOptions::SORT_BY_NONE:
1745       // Ensure sorting does not change based on tables status.
1746       if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
1747         if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
1748           mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
1749         else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1750           mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
1751       }
1752       break;
1753     case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
1754     case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
1755       // If the user wants few results, we limit them by date, necessitating
1756       // a sort by date here (see the IDL definition for maxResults).
1757       // Otherwise we will do actual sorting by title, but since we could need
1758       // to special sort for some locale we will repeat a second sorting at the
1759       // end in nsNavHistoryResult, that should be faster since the list will be
1760       // almost ordered.
1761       if (mMaxResults > 0)
1762         OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1763       else if (mSortingMode ==
1764                nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING)
1765         OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title);
1766       else
1767         OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title);
1768       break;
1769     case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
1770       OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate);
1771       break;
1772     case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
1773       OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1774       break;
1775     case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
1776       OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL);
1777       break;
1778     case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
1779       OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL);
1780       break;
1781     case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
1782       OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount);
1783       break;
1784     case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
1785       OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount);
1786       break;
1787     case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
1788       if (mHasDateColumns)
1789         OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1790       break;
1791     case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
1792       if (mHasDateColumns)
1793         OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1794       break;
1795     case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
1796       if (mHasDateColumns)
1797         OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1798       break;
1799     case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
1800       if (mHasDateColumns)
1801         OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1802       break;
1803     case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
1804     case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
1805       break;  // Sort later in nsNavHistoryQueryResultNode::FillChildren()
1806     case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
1807       OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency);
1808       break;
1809     case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
1810       OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency);
1811       break;
1812     default:
1813       MOZ_ASSERT_UNREACHABLE("Invalid sorting mode");
1814   }
1815   return NS_OK;
1816 }
1817 
OrderByColumnIndexAsc(int32_t aIndex)1818 void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex) {
1819   mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex + 1);
1820 }
1821 
OrderByColumnIndexDesc(int32_t aIndex)1822 void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex) {
1823   mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex + 1);
1824 }
1825 
OrderByTextColumnIndexAsc(int32_t aIndex)1826 void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex) {
1827   mQueryString +=
1828       nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC", aIndex + 1);
1829 }
1830 
OrderByTextColumnIndexDesc(int32_t aIndex)1831 void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex) {
1832   mQueryString +=
1833       nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC", aIndex + 1);
1834 }
1835 
Limit()1836 nsresult PlacesSQLQueryBuilder::Limit() {
1837   if (mUseLimit && mMaxResults > 0) {
1838     mQueryString += NS_LITERAL_CSTRING(" LIMIT ");
1839     mQueryString.AppendInt(mMaxResults);
1840     mQueryString.Append(' ');
1841   }
1842   return NS_OK;
1843 }
1844 
ConstructQueryString(const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions,nsCString & queryString,bool & aParamsPresent,nsNavHistory::StringHash & aAddParams)1845 nsresult nsNavHistory::ConstructQueryString(
1846     const RefPtr<nsNavHistoryQuery>& aQuery,
1847     const RefPtr<nsNavHistoryQueryOptions>& aOptions, nsCString& queryString,
1848     bool& aParamsPresent, nsNavHistory::StringHash& aAddParams) {
1849   // For information about visit_type see nsINavHistoryService.idl.
1850   // visitType == 0 is undefined (see bug #375777 for details).
1851   // Some sites, especially Javascript-heavy ones, load things in frames to
1852   // display them, resulting in a lot of these entries. This is the reason
1853   // why such visits are filtered out.
1854   nsresult rv;
1855   aParamsPresent = false;
1856 
1857   int32_t sortingMode = aOptions->SortingMode();
1858   NS_ASSERTION(
1859       sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE &&
1860           sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING,
1861       "Invalid sortingMode found while building query!");
1862 
1863   bool hasSearchTerms = !aQuery->SearchTerms().IsEmpty();
1864 
1865   nsAutoCString tagsSqlFragment;
1866   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
1867                      hasSearchTerms, tagsSqlFragment);
1868 
1869   if (IsOptimizableHistoryQuery(
1870           aQuery, aOptions,
1871           nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) ||
1872       IsOptimizableHistoryQuery(
1873           aQuery, aOptions,
1874           nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
1875     // Generate an optimized query for the history menu and the old most visited
1876     // bookmark that was inserted into profiles.
1877     queryString =
1878         NS_LITERAL_CSTRING(
1879             "SELECT h.id, h.url, h.title AS page_title, h.rev_host, "
1880             "h.visit_count, h.last_visit_date, "
1881             "null, null, null, null, null, ") +
1882         tagsSqlFragment +
1883         NS_LITERAL_CSTRING(
1884             ", h.frecency, h.hidden, h.guid, "
1885             "null, null, null "
1886             "FROM moz_places h "
1887             "WHERE h.hidden = 0 "
1888             "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = "
1889             "h.id "
1890             "AND visit_type NOT IN ") +
1891         nsPrintfCString("(0,%d,%d) ", nsINavHistoryService::TRANSITION_EMBED,
1892                         nsINavHistoryService::TRANSITION_FRAMED_LINK) +
1893         NS_LITERAL_CSTRING(
1894             "LIMIT 1) "
1895             "{QUERY_OPTIONS} ");
1896 
1897     queryString.AppendLiteral("ORDER BY ");
1898     if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
1899       queryString.AppendLiteral("last_visit_date DESC ");
1900     else
1901       queryString.AppendLiteral("visit_count DESC ");
1902 
1903     queryString.AppendLiteral("LIMIT ");
1904     queryString.AppendInt(aOptions->MaxResults());
1905 
1906     nsAutoCString additionalQueryOptions;
1907 
1908     queryString.ReplaceSubstring("{QUERY_OPTIONS}",
1909                                  additionalQueryOptions.get());
1910     return NS_OK;
1911   }
1912 
1913   // If the query is a tag query, the type is bookmarks.
1914   if (!aQuery->Tags().IsEmpty()) {
1915     aOptions->SetQueryType(nsNavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
1916   }
1917 
1918   nsAutoCString conditions;
1919   nsCString queryClause;
1920   rv = QueryToSelectClause(aQuery, aOptions, &queryClause);
1921   NS_ENSURE_SUCCESS(rv, rv);
1922   if (!queryClause.IsEmpty()) {
1923     // TODO: This should be set on a case basis, not blindly.
1924     aParamsPresent = true;
1925     conditions += queryClause;
1926   }
1927 
1928   // Determine whether we can push maxResults constraints into the query
1929   // as LIMIT, or if we need to do result count clamping later
1930   // using FilterResultSet()
1931   bool useLimitClause = !NeedToFilterResultSet(aQuery, aOptions);
1932 
1933   PlacesSQLQueryBuilder queryStringBuilder(
1934       conditions, aQuery, aOptions, useLimitClause, aAddParams, hasSearchTerms);
1935   rv = queryStringBuilder.GetQueryString(queryString);
1936   NS_ENSURE_SUCCESS(rv, rv);
1937 
1938   return NS_OK;
1939 }
1940 
1941 // nsNavHistory::GetQueryResults
1942 //
1943 //    Call this to get the results from a complex query. This is used by
1944 //    nsNavHistoryQueryResultNode to populate its children. For simple bookmark
1945 //    queries, use nsNavBookmarks::QueryFolderChildren.
1946 //
1947 //    THIS DOES NOT DO SORTING. You will need to sort the container yourself
1948 //    when you get the results. This is because sorting depends on tree
1949 //    statistics that will be built from the perspective of the tree. See
1950 //    nsNavHistoryQueryResultNode::FillChildren
1951 //
1952 //    FIXME: This only does keyword searching for the first query, and does
1953 //    it ANDed with the all the rest of the queries.
1954 
GetQueryResults(nsNavHistoryQueryResultNode * aResultNode,const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions,nsCOMArray<nsNavHistoryResultNode> * aResults)1955 nsresult nsNavHistory::GetQueryResults(
1956     nsNavHistoryQueryResultNode* aResultNode,
1957     const RefPtr<nsNavHistoryQuery>& aQuery,
1958     const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1959     nsCOMArray<nsNavHistoryResultNode>* aResults) {
1960   NS_ENSURE_ARG_POINTER(aQuery);
1961   NS_ENSURE_ARG_POINTER(aOptions);
1962   NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty");
1963 
1964   nsCString queryString;
1965   bool paramsPresent = false;
1966   nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
1967   nsresult rv = ConstructQueryString(aQuery, aOptions, queryString,
1968                                      paramsPresent, addParams);
1969   NS_ENSURE_SUCCESS(rv, rv);
1970 
1971   // create statement
1972   nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString);
1973 #ifdef DEBUG
1974   if (!statement) {
1975     nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
1976     if (conn) {
1977       nsAutoCString lastErrorString;
1978       (void)conn->GetLastErrorString(lastErrorString);
1979       int32_t lastError = 0;
1980       (void)conn->GetLastError(&lastError);
1981       printf(
1982           "Places failed to create a statement from this query:\n%s\nStorage "
1983           "error (%d): %s\n",
1984           queryString.get(), lastError, lastErrorString.get());
1985     }
1986   }
1987 #endif
1988   NS_ENSURE_STATE(statement);
1989   mozStorageStatementScoper scoper(statement);
1990 
1991   if (paramsPresent) {
1992     rv = BindQueryClauseParameters(statement, aQuery, aOptions);
1993     NS_ENSURE_SUCCESS(rv, rv);
1994   }
1995 
1996   for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
1997     nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
1998     if (NS_FAILED(rv)) {
1999       break;
2000     }
2001   }
2002 
2003   // Optimize the case where there is no need for any post-query filtering.
2004   if (NeedToFilterResultSet(aQuery, aOptions)) {
2005     // Generate the top-level results.
2006     nsCOMArray<nsNavHistoryResultNode> toplevel;
2007     rv = ResultsAsList(statement, aOptions, &toplevel);
2008     NS_ENSURE_SUCCESS(rv, rv);
2009 
2010     FilterResultSet(aResultNode, toplevel, aResults, aQuery, aOptions);
2011   } else {
2012     rv = ResultsAsList(statement, aOptions, aResults);
2013     NS_ENSURE_SUCCESS(rv, rv);
2014   }
2015 
2016   return NS_OK;
2017 }
2018 
2019 NS_IMETHODIMP
AddObserver(nsINavHistoryObserver * aObserver,bool aOwnsWeak)2020 nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak) {
2021   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2022   NS_ENSURE_ARG(aObserver);
2023 
2024   if (NS_WARN_IF(!mCanNotify)) return NS_ERROR_UNEXPECTED;
2025 
2026   return mObservers.AppendWeakElementUnlessExists(aObserver, aOwnsWeak);
2027 }
2028 
2029 NS_IMETHODIMP
RemoveObserver(nsINavHistoryObserver * aObserver)2030 nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver) {
2031   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2032   NS_ENSURE_ARG(aObserver);
2033 
2034   return mObservers.RemoveWeakElement(aObserver);
2035 }
2036 
2037 NS_IMETHODIMP
GetObservers(nsTArray<RefPtr<nsINavHistoryObserver>> & aObservers)2038 nsNavHistory::GetObservers(
2039     nsTArray<RefPtr<nsINavHistoryObserver>>& aObservers) {
2040   aObservers.Clear();
2041 
2042   // Clear any cached value, cause it's very likely the consumer has made
2043   // changes to history and is now trying to notify them.
2044   mDaysOfHistory = -1;
2045 
2046   if (!mCanNotify) return NS_OK;
2047 
2048   // Then add the other observers.
2049   for (uint32_t i = 0; i < mObservers.Length(); ++i) {
2050     nsCOMPtr<nsINavHistoryObserver> observer =
2051         mObservers.ElementAt(i).GetValue();
2052     // Skip nullified weak observers.
2053     if (observer) {
2054       aObservers.AppendElement(observer.forget());
2055     }
2056   }
2057 
2058   return NS_OK;
2059 }
2060 
2061 NS_IMETHODIMP
GetHistoryDisabled(bool * _retval)2062 nsNavHistory::GetHistoryDisabled(bool* _retval) {
2063   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2064   NS_ENSURE_ARG_POINTER(_retval);
2065 
2066   *_retval = IsHistoryDisabled();
2067   return NS_OK;
2068 }
2069 
2070 // Call this method before visiting a URL in order to help determine the
2071 // transition type of the visit.
2072 //
2073 // @see MarkPageAsFollowedBookmark
2074 
2075 NS_IMETHODIMP
MarkPageAsTyped(nsIURI * aURI)2076 nsNavHistory::MarkPageAsTyped(nsIURI* aURI) {
2077   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2078   NS_ENSURE_ARG(aURI);
2079 
2080   // don't add when history is disabled
2081   if (IsHistoryDisabled()) return NS_OK;
2082 
2083   nsAutoCString uriString;
2084   nsresult rv = aURI->GetSpec(uriString);
2085   NS_ENSURE_SUCCESS(rv, rv);
2086 
2087   mRecentTyped.Put(uriString, GetNow());
2088 
2089   if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
2090     ExpireNonrecentEvents(&mRecentTyped);
2091 
2092   return NS_OK;
2093 }
2094 
2095 // Call this method before visiting a URL in order to help determine the
2096 // transition type of the visit.
2097 //
2098 // @see MarkPageAsTyped
2099 
2100 NS_IMETHODIMP
MarkPageAsFollowedLink(nsIURI * aURI)2101 nsNavHistory::MarkPageAsFollowedLink(nsIURI* aURI) {
2102   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2103   NS_ENSURE_ARG(aURI);
2104 
2105   // don't add when history is disabled
2106   if (IsHistoryDisabled()) return NS_OK;
2107 
2108   nsAutoCString uriString;
2109   nsresult rv = aURI->GetSpec(uriString);
2110   NS_ENSURE_SUCCESS(rv, rv);
2111 
2112   mRecentLink.Put(uriString, GetNow());
2113 
2114   if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
2115     ExpireNonrecentEvents(&mRecentLink);
2116 
2117   return NS_OK;
2118 }
2119 
2120 ////////////////////////////////////////////////////////////////////////////////
2121 //// mozIStorageVacuumParticipant
2122 
2123 NS_IMETHODIMP
GetDatabaseConnection(mozIStorageConnection ** _DBConnection)2124 nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection) {
2125   return GetDBConnection(_DBConnection);
2126 }
2127 
2128 NS_IMETHODIMP
GetExpectedDatabasePageSize(int32_t * _expectedPageSize)2129 nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize) {
2130   NS_ENSURE_STATE(mDB);
2131   NS_ENSURE_STATE(mDB->MainConn());
2132   return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize);
2133 }
2134 
2135 NS_IMETHODIMP
OnBeginVacuum(bool * _vacuumGranted)2136 nsNavHistory::OnBeginVacuum(bool* _vacuumGranted) {
2137   // TODO: Check if we have to deny the vacuum in some heavy-load case.
2138   // We could maybe want to do that during batches?
2139   *_vacuumGranted = true;
2140   return NS_OK;
2141 }
2142 
2143 NS_IMETHODIMP
OnEndVacuum(bool aSucceeded)2144 nsNavHistory::OnEndVacuum(bool aSucceeded) {
2145   NS_WARNING_ASSERTION(aSucceeded, "Places.sqlite vacuum failed.");
2146   return NS_OK;
2147 }
2148 
2149 NS_IMETHODIMP
GetDBConnection(mozIStorageConnection ** _DBConnection)2150 nsNavHistory::GetDBConnection(mozIStorageConnection** _DBConnection) {
2151   NS_ENSURE_ARG_POINTER(_DBConnection);
2152   nsCOMPtr<mozIStorageConnection> connection = mDB->MainConn();
2153   connection.forget(_DBConnection);
2154 
2155   return NS_OK;
2156 }
2157 
2158 NS_IMETHODIMP
GetShutdownClient(nsIAsyncShutdownClient ** _shutdownClient)2159 nsNavHistory::GetShutdownClient(nsIAsyncShutdownClient** _shutdownClient) {
2160   NS_ENSURE_ARG_POINTER(_shutdownClient);
2161   nsCOMPtr<nsIAsyncShutdownClient> client = mDB->GetClientsShutdown();
2162   if (!client) {
2163     return NS_ERROR_UNEXPECTED;
2164   }
2165   client.forget(_shutdownClient);
2166   return NS_OK;
2167 }
2168 
2169 NS_IMETHODIMP
GetConnectionShutdownClient(nsIAsyncShutdownClient ** _shutdownClient)2170 nsNavHistory::GetConnectionShutdownClient(
2171     nsIAsyncShutdownClient** _shutdownClient) {
2172   NS_ENSURE_ARG_POINTER(_shutdownClient);
2173   nsCOMPtr<nsIAsyncShutdownClient> client = mDB->GetConnectionShutdown();
2174   if (!client) {
2175     return NS_ERROR_UNEXPECTED;
2176   }
2177   client.forget(_shutdownClient);
2178   return NS_OK;
2179 }
2180 
2181 NS_IMETHODIMP
AsyncExecuteLegacyQuery(nsINavHistoryQuery * aQuery,nsINavHistoryQueryOptions * aOptions,mozIStorageStatementCallback * aCallback,mozIStoragePendingStatement ** _stmt)2182 nsNavHistory::AsyncExecuteLegacyQuery(nsINavHistoryQuery* aQuery,
2183                                       nsINavHistoryQueryOptions* aOptions,
2184                                       mozIStorageStatementCallback* aCallback,
2185                                       mozIStoragePendingStatement** _stmt) {
2186   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2187   NS_ENSURE_ARG(aQuery);
2188   NS_ENSURE_ARG(aOptions);
2189   NS_ENSURE_ARG(aCallback);
2190   NS_ENSURE_ARG_POINTER(_stmt);
2191 
2192   RefPtr<nsNavHistoryQuery> query = do_QueryObject(aQuery);
2193   NS_ENSURE_STATE(query);
2194   RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(aOptions);
2195   NS_ENSURE_ARG(options);
2196 
2197   nsCString queryString;
2198   bool paramsPresent = false;
2199   nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
2200   nsresult rv = ConstructQueryString(query, options, queryString, paramsPresent,
2201                                      addParams);
2202   NS_ENSURE_SUCCESS(rv, rv);
2203 
2204   nsCOMPtr<mozIStorageAsyncStatement> statement =
2205       mDB->GetAsyncStatement(queryString);
2206   NS_ENSURE_STATE(statement);
2207 
2208 #ifdef DEBUG
2209   if (NS_FAILED(rv)) {
2210     nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
2211     if (conn) {
2212       nsAutoCString lastErrorString;
2213       (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
2214       int32_t lastError = 0;
2215       (void)mDB->MainConn()->GetLastError(&lastError);
2216       printf(
2217           "Places failed to create a statement from this query:\n%s\nStorage "
2218           "error (%d): %s\n",
2219           queryString.get(), lastError, lastErrorString.get());
2220     }
2221   }
2222 #endif
2223   NS_ENSURE_SUCCESS(rv, rv);
2224 
2225   if (paramsPresent) {
2226     rv = BindQueryClauseParameters(statement, query, options);
2227     NS_ENSURE_SUCCESS(rv, rv);
2228   }
2229 
2230   for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
2231     nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
2232     if (NS_FAILED(rv)) {
2233       break;
2234     }
2235   }
2236 
2237   rv = statement->ExecuteAsync(aCallback, _stmt);
2238   NS_ENSURE_SUCCESS(rv, rv);
2239 
2240   return NS_OK;
2241 }
2242 
2243 ////////////////////////////////////////////////////////////////////////////////
2244 //// nsIObserver
2245 
2246 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)2247 nsNavHistory::Observe(nsISupports* aSubject, const char* aTopic,
2248                       const char16_t* aData) {
2249   NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
2250   if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 ||
2251       strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0 ||
2252       strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
2253     // These notifications are used by tests to simulate a Places shutdown.
2254     // They should just be forwarded to the Database handle.
2255     mDB->Observe(aSubject, aTopic, aData);
2256   }
2257 
2258   else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
2259     // Don't even try to notify observers from this point on, the category
2260     // cache would init services that could try to use our APIs.
2261     mCanNotify = false;
2262     mObservers.Clear();
2263   }
2264 
2265   else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) {
2266     LoadPrefs();
2267   }
2268 
2269   else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) {
2270     (void)DecayFrecency();
2271   }
2272 
2273   return NS_OK;
2274 }
2275 
2276 NS_IMETHODIMP
DecayFrecency()2277 nsNavHistory::DecayFrecency() {
2278   float decayRate =
2279       Preferences::GetFloat(PREF_FREC_DECAY_RATE, PREF_FREC_DECAY_RATE_DEF);
2280   if (decayRate > 1.0f) {
2281     MOZ_ASSERT(false, "The frecency decay rate should not be greater than 1.0");
2282     decayRate = PREF_FREC_DECAY_RATE_DEF;
2283   }
2284 
2285   RefPtr<FixAndDecayFrecencyRunnable> runnable =
2286       new FixAndDecayFrecencyRunnable(mDB, decayRate);
2287   nsCOMPtr<nsIEventTarget> target = do_GetInterface(mDB->MainConn());
2288   NS_ENSURE_STATE(target);
2289 
2290   mDecayFrecencyPendingCount++;
2291   return target->Dispatch(runnable, NS_DISPATCH_NORMAL);
2292 }
2293 
DecayFrecencyCompleted(uint16_t reason)2294 void nsNavHistory::DecayFrecencyCompleted(uint16_t reason) {
2295   MOZ_ASSERT(mDecayFrecencyPendingCount > 0);
2296   mDecayFrecencyPendingCount--;
2297   if (mozIStorageStatementCallback::REASON_FINISHED == reason) {
2298     NotifyManyFrecenciesChanged();
2299   }
2300 }
2301 
IsFrecencyDecaying() const2302 bool nsNavHistory::IsFrecencyDecaying() const {
2303   return mDecayFrecencyPendingCount > 0;
2304 }
2305 
2306 // Query stuff *****************************************************************
2307 
2308 // Helper class for QueryToSelectClause
2309 //
2310 // This class helps to build part of the WHERE clause.
2311 
2312 class ConditionBuilder {
2313  public:
Condition(const char * aStr)2314   ConditionBuilder& Condition(const char* aStr) {
2315     if (!mClause.IsEmpty()) mClause.AppendLiteral(" AND ");
2316     Str(aStr);
2317     return *this;
2318   }
2319 
Str(const char * aStr)2320   ConditionBuilder& Str(const char* aStr) {
2321     mClause.Append(' ');
2322     mClause.Append(aStr);
2323     mClause.Append(' ');
2324     return *this;
2325   }
2326 
Param(const char * aParam)2327   ConditionBuilder& Param(const char* aParam) {
2328     mClause.Append(' ');
2329     mClause.Append(aParam);
2330     mClause.Append(' ');
2331     return *this;
2332   }
2333 
GetClauseString(nsCString & aResult)2334   void GetClauseString(nsCString& aResult) { aResult = mClause; }
2335 
2336  private:
2337   nsCString mClause;
2338 };
2339 
2340 // nsNavHistory::QueryToSelectClause
2341 //
2342 //    THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters
2343 //
2344 //    I don't check return values from the query object getters because there's
2345 //    no way for those to fail.
2346 
QueryToSelectClause(const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions,nsCString * aClause)2347 nsresult nsNavHistory::QueryToSelectClause(
2348     const RefPtr<nsNavHistoryQuery>& aQuery,
2349     const RefPtr<nsNavHistoryQueryOptions>& aOptions, nsCString* aClause) {
2350   bool hasIt;
2351   // We don't use the value from options here - we post filter if that
2352   // is set.
2353   bool excludeQueries = false;
2354 
2355   ConditionBuilder clause;
2356 
2357   if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) ||
2358       (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) {
2359     clause.Condition(
2360         "EXISTS (SELECT 1 FROM moz_historyvisits "
2361         "WHERE place_id = h.id");
2362     // begin time
2363     if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
2364       clause.Condition("visit_date >=").Param(":begin_time");
2365     // end time
2366     if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
2367       clause.Condition("visit_date <=").Param(":end_time");
2368     clause.Str(" LIMIT 1)");
2369   }
2370 
2371   // search terms
2372   int32_t searchBehavior = mozIPlacesAutoComplete::BEHAVIOR_HISTORY |
2373                            mozIPlacesAutoComplete::BEHAVIOR_BOOKMARK;
2374   if (!aQuery->SearchTerms().IsEmpty()) {
2375     // Re-use the autocomplete_match function.  Setting the behavior to match
2376     // history or typed history or bookmarks or open pages will match almost
2377     // everything.
2378     clause.Condition("AUTOCOMPLETE_MATCH(")
2379         .Param(":search_string")
2380         .Str(", h.url, page_title, tags, ")
2381         .Str(nsPrintfCString("1, 1, 1, 1, %d, %d)",
2382                              mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED,
2383                              searchBehavior)
2384                  .get());
2385     // Serching by terms implicitly exclude queries.
2386     excludeQueries = true;
2387   }
2388 
2389   // min and max visit count
2390   if (aQuery->MinVisits() >= 0)
2391     clause.Condition("h.visit_count >=").Param(":min_visits");
2392 
2393   if (aQuery->MaxVisits() >= 0)
2394     clause.Condition("h.visit_count <=").Param(":max_visits");
2395 
2396   // only bookmarked, has no affect on bookmarks-only queries
2397   if (aOptions->QueryType() !=
2398           nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
2399       aQuery->OnlyBookmarked())
2400     clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ")
2401         .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get())
2402         .Str("AND b.fk = h.id)");
2403 
2404   // domain
2405   if (!aQuery->Domain().IsVoid()) {
2406     bool domainIsHost = false;
2407     aQuery->GetDomainIsHost(&domainIsHost);
2408     if (domainIsHost)
2409       clause.Condition("h.rev_host =").Param(":domain_lower");
2410     else
2411       // see domain setting in BindQueryClauseParameters for why we do this
2412       clause.Condition("h.rev_host >=")
2413           .Param(":domain_lower")
2414           .Condition("h.rev_host <")
2415           .Param(":domain_upper");
2416   }
2417 
2418   // URI
2419   if (aQuery->Uri()) {
2420     clause.Condition("h.url_hash = hash(")
2421         .Param(":uri")
2422         .Str(")")
2423         .Condition("h.url =")
2424         .Param(":uri");
2425   }
2426 
2427   // annotation
2428   if (!aQuery->Annotation().IsEmpty()) {
2429     clause.Condition("");
2430     if (aQuery->AnnotationIsNot()) clause.Str("NOT");
2431     clause
2432         .Str(
2433             "EXISTS "
2434             "(SELECT h.id "
2435             "FROM moz_annos anno "
2436             "JOIN moz_anno_attributes annoname "
2437             "ON anno.anno_attribute_id = annoname.id "
2438             "WHERE anno.place_id = h.id "
2439             "AND annoname.name = ")
2440         .Param(":anno")
2441         .Str(")");
2442     // annotation-based queries don't get the common conditions, so you get
2443     // all URLs with that annotation
2444   }
2445 
2446   // tags
2447   const nsTArray<nsString>& tags = aQuery->Tags();
2448   if (tags.Length() > 0) {
2449     clause.Condition("h.id");
2450     if (aQuery->TagsAreNot()) clause.Str("NOT");
2451     clause
2452         .Str(
2453             "IN "
2454             "(SELECT bms.fk "
2455             "FROM moz_bookmarks bms "
2456             "JOIN moz_bookmarks tags ON bms.parent = tags.id "
2457             "WHERE tags.parent =")
2458         .Param(":tags_folder")
2459         .Str("AND lower(tags.title) IN (");
2460     for (uint32_t i = 0; i < tags.Length(); ++i) {
2461       nsPrintfCString param(":tag%d_", i);
2462       clause.Param(param.get());
2463       if (i < tags.Length() - 1) clause.Str(",");
2464     }
2465     clause.Str(")");
2466     if (!aQuery->TagsAreNot()) {
2467       clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count");
2468     }
2469     clause.Str(")");
2470   }
2471 
2472   // transitions
2473   const nsTArray<uint32_t>& transitions = aQuery->Transitions();
2474   for (uint32_t i = 0; i < transitions.Length(); ++i) {
2475     nsPrintfCString param(":transition%d_", i);
2476     clause
2477         .Condition(
2478             "h.id IN (SELECT place_id FROM moz_historyvisits "
2479             "WHERE visit_type = ")
2480         .Param(param.get())
2481         .Str(")");
2482   }
2483 
2484   // parents
2485   const nsTArray<nsCString>& parents = aQuery->Parents();
2486   if (parents.Length() > 0) {
2487     aOptions->SetQueryType(nsNavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
2488     clause.Condition(
2489         "b.parent IN( "
2490         "WITH RECURSIVE parents(id) AS ( "
2491         "SELECT id FROM moz_bookmarks WHERE GUID IN (");
2492 
2493     for (uint32_t i = 0; i < parents.Length(); ++i) {
2494       nsPrintfCString param(":parentguid%d_", i);
2495       clause.Param(param.get());
2496       if (i < parents.Length() - 1) {
2497         clause.Str(",");
2498       }
2499     }
2500     clause.Str(
2501         ") "
2502         "UNION ALL "
2503         "SELECT b2.id "
2504         "FROM moz_bookmarks b2 "
2505         "JOIN parents p ON b2.parent = p.id "
2506         "WHERE b2.type = 2 "
2507         ") "
2508         "SELECT id FROM parents "
2509         ")");
2510   }
2511 
2512   if (excludeQueries) {
2513     // Serching by terms implicitly exclude queries and folder shortcuts.
2514     clause.Condition(
2515         "NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
2516         "hash('place', 'prefix_hi')");
2517   }
2518 
2519   clause.GetClauseString(*aClause);
2520   return NS_OK;
2521 }
2522 
2523 // nsNavHistory::BindQueryClauseParameters
2524 //
2525 //    THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause
2526 
BindQueryClauseParameters(mozIStorageBaseStatement * statement,const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions)2527 nsresult nsNavHistory::BindQueryClauseParameters(
2528     mozIStorageBaseStatement* statement,
2529     const RefPtr<nsNavHistoryQuery>& aQuery,
2530     const RefPtr<nsNavHistoryQueryOptions>& aOptions) {
2531   nsresult rv;
2532 
2533   bool hasIt;
2534   // begin time
2535   if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) {
2536     PRTime time =
2537         NormalizeTime(aQuery->BeginTimeReference(), aQuery->BeginTime());
2538     rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("begin_time"), time);
2539     NS_ENSURE_SUCCESS(rv, rv);
2540   }
2541 
2542   // end time
2543   if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) {
2544     PRTime time = NormalizeTime(aQuery->EndTimeReference(), aQuery->EndTime());
2545     rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("end_time"), time);
2546     NS_ENSURE_SUCCESS(rv, rv);
2547   }
2548 
2549   // search terms
2550   if (!aQuery->SearchTerms().IsEmpty()) {
2551     rv = statement->BindStringByName(NS_LITERAL_CSTRING("search_string"),
2552                                      aQuery->SearchTerms());
2553     NS_ENSURE_SUCCESS(rv, rv);
2554   }
2555 
2556   // min and max visit count
2557   int32_t visits = aQuery->MinVisits();
2558   if (visits >= 0) {
2559     rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("min_visits"), visits);
2560     NS_ENSURE_SUCCESS(rv, rv);
2561   }
2562 
2563   visits = aQuery->MaxVisits();
2564   if (visits >= 0) {
2565     rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("max_visits"), visits);
2566     NS_ENSURE_SUCCESS(rv, rv);
2567   }
2568 
2569   // domain (see GetReversedHostname for more info on reversed host names)
2570   if (!aQuery->Domain().IsVoid()) {
2571     nsString revDomain;
2572     GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain);
2573 
2574     if (aQuery->DomainIsHost()) {
2575       rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_lower"),
2576                                        revDomain);
2577       NS_ENSURE_SUCCESS(rv, rv);
2578     } else {
2579       // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/"
2580       // which will get everything starting with "gro.allizom." while using the
2581       // index (using SUBSTRING() causes indexes to be discarded).
2582       NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.',
2583                    "Invalid rev. host");
2584       rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_lower"),
2585                                        revDomain);
2586       NS_ENSURE_SUCCESS(rv, rv);
2587       revDomain.Truncate(revDomain.Length() - 1);
2588       revDomain.Append(char16_t('/'));
2589       rv = statement->BindStringByName(NS_LITERAL_CSTRING("domain_upper"),
2590                                        revDomain);
2591       NS_ENSURE_SUCCESS(rv, rv);
2592     }
2593   }
2594 
2595   // URI
2596   if (aQuery->Uri()) {
2597     rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("uri"), aQuery->Uri());
2598     NS_ENSURE_SUCCESS(rv, rv);
2599   }
2600 
2601   // annotation
2602   if (!aQuery->Annotation().IsEmpty()) {
2603     rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno"),
2604                                          aQuery->Annotation());
2605     NS_ENSURE_SUCCESS(rv, rv);
2606   }
2607 
2608   // tags
2609   const nsTArray<nsString>& tags = aQuery->Tags();
2610   if (tags.Length() > 0) {
2611     for (uint32_t i = 0; i < tags.Length(); ++i) {
2612       nsPrintfCString paramName("tag%d_", i);
2613       nsString utf16Tag = tags[i];
2614       ToLowerCase(utf16Tag);
2615       NS_ConvertUTF16toUTF8 tag(utf16Tag);
2616       rv = statement->BindUTF8StringByName(paramName, tag);
2617       NS_ENSURE_SUCCESS(rv, rv);
2618     }
2619     int64_t tagsFolder = GetTagsFolder();
2620     rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
2621                                     tagsFolder);
2622     NS_ENSURE_SUCCESS(rv, rv);
2623     if (!aQuery->TagsAreNot()) {
2624       rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("tag_count"),
2625                                       tags.Length());
2626       NS_ENSURE_SUCCESS(rv, rv);
2627     }
2628   }
2629 
2630   // transitions
2631   const nsTArray<uint32_t>& transitions = aQuery->Transitions();
2632   for (uint32_t i = 0; i < transitions.Length(); ++i) {
2633     nsPrintfCString paramName("transition%d_", i);
2634     rv = statement->BindInt64ByName(paramName, transitions[i]);
2635     NS_ENSURE_SUCCESS(rv, rv);
2636   }
2637 
2638   // parents
2639   const nsTArray<nsCString>& parents = aQuery->Parents();
2640   for (uint32_t i = 0; i < parents.Length(); ++i) {
2641     nsPrintfCString paramName("parentguid%d_", i);
2642     rv = statement->BindUTF8StringByName(paramName, parents[i]);
2643     NS_ENSURE_SUCCESS(rv, rv);
2644   }
2645 
2646   return NS_OK;
2647 }
2648 
2649 // nsNavHistory::ResultsAsList
2650 //
2651 
ResultsAsList(mozIStorageStatement * statement,nsNavHistoryQueryOptions * aOptions,nsCOMArray<nsNavHistoryResultNode> * aResults)2652 nsresult nsNavHistory::ResultsAsList(
2653     mozIStorageStatement* statement, nsNavHistoryQueryOptions* aOptions,
2654     nsCOMArray<nsNavHistoryResultNode>* aResults) {
2655   nsresult rv;
2656   nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
2657   NS_ENSURE_SUCCESS(rv, rv);
2658 
2659   bool hasMore = false;
2660   while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
2661     RefPtr<nsNavHistoryResultNode> result;
2662     rv = RowToResult(row, aOptions, getter_AddRefs(result));
2663     NS_ENSURE_SUCCESS(rv, rv);
2664     aResults->AppendElement(result.forget());
2665   }
2666   return NS_OK;
2667 }
2668 
2669 const int64_t UNDEFINED_URN_VALUE = -1;
2670 
2671 // Create a urn (like
2672 // urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29)
2673 // to be used to persist the open state of this container
CreatePlacesPersistURN(nsNavHistoryQueryResultNode * aResultNode,int64_t aValue,const nsCString & aTitle,nsCString & aURN)2674 nsresult CreatePlacesPersistURN(nsNavHistoryQueryResultNode* aResultNode,
2675                                 int64_t aValue, const nsCString& aTitle,
2676                                 nsCString& aURN) {
2677   nsAutoCString uri;
2678   nsresult rv = aResultNode->GetUri(uri);
2679   NS_ENSURE_SUCCESS(rv, rv);
2680 
2681   aURN.AssignLiteral("urn:places-persist:");
2682   aURN.Append(uri);
2683 
2684   aURN.Append(',');
2685   if (aValue != UNDEFINED_URN_VALUE) aURN.AppendInt(aValue);
2686 
2687   aURN.Append(',');
2688   if (!aTitle.IsEmpty()) {
2689     nsAutoCString escapedTitle;
2690     bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas);
2691     NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
2692     aURN.Append(escapedTitle);
2693   }
2694 
2695   return NS_OK;
2696 }
2697 
GetTagsFolder()2698 int64_t nsNavHistory::GetTagsFolder() {
2699   // cache our tags folder
2700   // note, we can't do this in nsNavHistory::Init(),
2701   // as getting the bookmarks service would initialize it.
2702   if (mTagsFolder == -1) {
2703     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
2704     NS_ENSURE_TRUE(bookmarks, -1);
2705 
2706     nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder);
2707     NS_ENSURE_SUCCESS(rv, -1);
2708   }
2709   return mTagsFolder;
2710 }
2711 
2712 // nsNavHistory::FilterResultSet
2713 //
2714 // This does some post-query-execution filtering:
2715 //   - searching on title, url and tags
2716 //   - limit count
2717 //
2718 // Note:  changes to filtering in FilterResultSet()
2719 // may require changes to NeedToFilterResultSet()
2720 
2721 // static
FilterResultSet(nsNavHistoryQueryResultNode * aQueryNode,const nsCOMArray<nsNavHistoryResultNode> & aSet,nsCOMArray<nsNavHistoryResultNode> * aFiltered,const RefPtr<nsNavHistoryQuery> & aQuery,nsNavHistoryQueryOptions * aOptions)2722 nsresult nsNavHistory::FilterResultSet(
2723     nsNavHistoryQueryResultNode* aQueryNode,
2724     const nsCOMArray<nsNavHistoryResultNode>& aSet,
2725     nsCOMArray<nsNavHistoryResultNode>* aFiltered,
2726     const RefPtr<nsNavHistoryQuery>& aQuery,
2727     nsNavHistoryQueryOptions* aOptions) {
2728   // parse the search terms
2729   nsTArray<nsString> terms;
2730   ParseSearchTermsFromQuery(aQuery, &terms);
2731 
2732   bool excludeQueries = aOptions->ExcludeQueries();
2733   for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) {
2734     if (excludeQueries && aSet[nodeIndex]->IsQuery()) {
2735       continue;
2736     }
2737 
2738     if (aSet[nodeIndex]->mItemId != -1 && aQueryNode &&
2739         aQueryNode->mItemId == aSet[nodeIndex]->mItemId) {
2740       continue;
2741     }
2742 
2743     // If there are search terms, we are already getting only uri nodes,
2744     // thus we don't need to filter node types. Though, we must check for
2745     // matching terms.
2746     if (terms.Length()) {
2747       // Filter based on search terms.
2748       // Convert title and url for the current node to UTF16 strings.
2749       NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle);
2750       // Unescape the URL for search terms matching.
2751       nsAutoCString cNodeURL(aSet[nodeIndex]->mURI);
2752       NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL));
2753 
2754       // Determine if every search term matches anywhere in the title, url or
2755       // tag.
2756       bool matchAllTerms = true;
2757       for (int32_t termIndex = terms.Length() - 1;
2758            termIndex >= 0 && matchAllTerms; termIndex--) {
2759         nsString& term = terms.ElementAt(termIndex);
2760         // True if any of them match; false makes us quit the loop
2761         matchAllTerms =
2762             CaseInsensitiveFindInReadable(term, nodeTitle) ||
2763             CaseInsensitiveFindInReadable(term, nodeURL) ||
2764             CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags);
2765       }
2766       // Skip the node if we don't match all terms in the title, url or tag
2767       if (!matchAllTerms) {
2768         continue;
2769       }
2770     }
2771 
2772     aFiltered->AppendObject(aSet[nodeIndex]);
2773 
2774     // Stop once we have reached max results.
2775     if (aOptions->MaxResults() > 0 &&
2776         (uint32_t)aFiltered->Count() >= aOptions->MaxResults())
2777       break;
2778   }
2779 
2780   return NS_OK;
2781 }
2782 
2783 NS_IMETHODIMP
MakeGuid(nsACString & aGuid)2784 nsNavHistory::MakeGuid(nsACString& aGuid) {
2785   if (NS_FAILED(GenerateGUID(aGuid))) {
2786     MOZ_ASSERT(false, "Shouldn't fail to create a guid!");
2787     aGuid.SetIsVoid(true);
2788   }
2789   return NS_OK;
2790 }
2791 
2792 NS_IMETHODIMP
HashURL(const nsACString & aSpec,const nsACString & aMode,uint64_t * _hash)2793 nsNavHistory::HashURL(const nsACString& aSpec, const nsACString& aMode,
2794                       uint64_t* _hash) {
2795   return places::HashURL(aSpec, aMode, _hash);
2796 }
2797 
2798 // nsNavHistory::CheckIsRecentEvent
2799 //
2800 //    Sees if this URL happened "recently."
2801 //
2802 //    It is always removed from our recent list no matter what. It only counts
2803 //    as "recent" if the event happened more recently than our event
2804 //    threshold ago.
2805 
CheckIsRecentEvent(RecentEventHash * hashTable,const nsACString & url)2806 bool nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable,
2807                                       const nsACString& url) {
2808   PRTime eventTime;
2809   if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) {
2810     hashTable->Remove(url);
2811     if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD) return true;
2812     return false;
2813   }
2814   return false;
2815 }
2816 
2817 // nsNavHistory::ExpireNonrecentEvents
2818 //
2819 //    This goes through our
2820 
ExpireNonrecentEvents(RecentEventHash * hashTable)2821 void nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable) {
2822   int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD;
2823   for (auto iter = hashTable->Iter(); !iter.Done(); iter.Next()) {
2824     if (iter.Data() < threshold) {
2825       iter.Remove();
2826     }
2827   }
2828 }
2829 
2830 // nsNavHistory::RowToResult
2831 //
2832 //    Here, we just have a generic row. It could be a query, URL, visit,
2833 //    or full visit.
2834 
RowToResult(mozIStorageValueArray * aRow,nsNavHistoryQueryOptions * aOptions,nsNavHistoryResultNode ** aResult)2835 nsresult nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
2836                                    nsNavHistoryQueryOptions* aOptions,
2837                                    nsNavHistoryResultNode** aResult) {
2838   NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult");
2839 
2840   // URL
2841   nsAutoCString url;
2842   nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url);
2843   NS_ENSURE_SUCCESS(rv, rv);
2844   // In case of data corruption URL may be null, but our UI code prefers an
2845   // empty string.
2846   if (url.IsVoid()) {
2847     MOZ_ASSERT(false, "Found a NULL url in moz_places");
2848     url.SetIsVoid(false);
2849   }
2850 
2851   // title
2852   nsAutoCString title;
2853   bool isNull;
2854   rv = aRow->GetIsNull(kGetInfoIndex_Title, &isNull);
2855   NS_ENSURE_SUCCESS(rv, rv);
2856   if (!isNull) {
2857     rv = aRow->GetUTF8String(kGetInfoIndex_Title, title);
2858     NS_ENSURE_SUCCESS(rv, rv);
2859   }
2860 
2861   uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount);
2862   PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate);
2863 
2864   // itemId
2865   int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId);
2866   int64_t parentId = -1;
2867   if (itemId == 0) {
2868     // This is not a bookmark.  For non-bookmarks we use a -1 itemId value.
2869     // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0.
2870     itemId = -1;
2871   } else {
2872     // This is a bookmark, so it has a parent.
2873     int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId);
2874     if (itemParentId > 0) {
2875       // The Places root has parent == 0, but that item id does not really
2876       // exist. We want to set the parent only if it's a real one.
2877       parentId = itemParentId;
2878     }
2879   }
2880 
2881   if (IsQueryURI(url)) {
2882     // Special case "place:" URIs: turn them into containers.
2883     if (itemId != -1) {
2884       // We should never expose the history title for query nodes if the
2885       // bookmark-item's title is set to null (the history title may be the
2886       // query string without the place: prefix). Thus we call getItemTitle
2887       // explicitly. Doing this in the SQL query would be less performant since
2888       // it should be done for all results rather than only for queries.
2889       nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
2890       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
2891 
2892       rv = bookmarks->GetItemTitle(itemId, title);
2893       NS_ENSURE_SUCCESS(rv, rv);
2894     }
2895 
2896     nsAutoCString guid;
2897     if (itemId != -1) {
2898       rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
2899       NS_ENSURE_SUCCESS(rv, rv);
2900     }
2901 
2902     if (aOptions->ResultType() ==
2903             nsNavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY ||
2904         aOptions->ResultType() ==
2905             nsNavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY) {
2906       rv = aRow->GetUTF8String(kGetInfoIndex_Guid, guid);
2907       NS_ENSURE_SUCCESS(rv, rv);
2908     }
2909 
2910     RefPtr<nsNavHistoryResultNode> resultNode;
2911     rv = QueryRowToResult(itemId, guid, url, title, accessCount, time,
2912                           getter_AddRefs(resultNode));
2913     NS_ENSURE_SUCCESS(rv, rv);
2914 
2915     if (itemId != -1 || aOptions->ResultType() ==
2916                             nsNavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT) {
2917       // RESULTS_AS_TAGS_ROOT has date columns
2918       resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
2919       resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
2920       if (resultNode->IsFolder()) {
2921         // If it's a simple folder node (i.e. a shortcut to another folder),
2922         // apply our options for it. However, if the parent type was tag query,
2923         // we do not apply them, because it would not yield any results.
2924         resultNode->GetAsContainer()->mOptions = aOptions;
2925       }
2926     }
2927 
2928     resultNode.forget(aResult);
2929     return rv;
2930   } else if (aOptions->ResultType() ==
2931              nsNavHistoryQueryOptions::RESULTS_AS_URI) {
2932     RefPtr<nsNavHistoryResultNode> resultNode =
2933         new nsNavHistoryResultNode(url, title, accessCount, time);
2934 
2935     if (itemId != -1) {
2936       resultNode->mItemId = itemId;
2937       resultNode->mFolderId = parentId;
2938       resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
2939       resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
2940 
2941       rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
2942                                resultNode->mBookmarkGuid);
2943       NS_ENSURE_SUCCESS(rv, rv);
2944     }
2945 
2946     resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency);
2947     resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden);
2948 
2949     nsAutoString tags;
2950     rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
2951     NS_ENSURE_SUCCESS(rv, rv);
2952     if (!tags.IsVoid()) {
2953       resultNode->mTags.Assign(tags);
2954     }
2955 
2956     rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
2957     NS_ENSURE_SUCCESS(rv, rv);
2958 
2959     resultNode.forget(aResult);
2960     return NS_OK;
2961   }
2962 
2963   if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
2964     RefPtr<nsNavHistoryResultNode> resultNode =
2965         new nsNavHistoryResultNode(url, title, accessCount, time);
2966 
2967     nsAutoString tags;
2968     rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
2969     if (!tags.IsVoid()) resultNode->mTags.Assign(tags);
2970 
2971     rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
2972     NS_ENSURE_SUCCESS(rv, rv);
2973 
2974     rv = aRow->GetInt64(kGetInfoIndex_VisitId, &resultNode->mVisitId);
2975     NS_ENSURE_SUCCESS(rv, rv);
2976 
2977     int64_t fromVisitId;
2978     rv = aRow->GetInt64(kGetInfoIndex_FromVisitId, &fromVisitId);
2979     NS_ENSURE_SUCCESS(rv, rv);
2980 
2981     if (fromVisitId > 0) {
2982       resultNode->mFromVisitId = fromVisitId;
2983     }
2984 
2985     resultNode->mTransitionType = aRow->AsInt32(kGetInfoIndex_VisitType);
2986 
2987     resultNode.forget(aResult);
2988     return NS_OK;
2989   }
2990 
2991   return NS_ERROR_FAILURE;
2992 }
2993 
2994 // nsNavHistory::QueryRowToResult
2995 //
2996 //    Called by RowToResult when the URI is a place: URI to generate the proper
2997 //    folder or query node.
2998 
QueryRowToResult(int64_t itemId,const nsACString & aBookmarkGuid,const nsACString & aURI,const nsACString & aTitle,uint32_t aAccessCount,PRTime aTime,nsNavHistoryResultNode ** aNode)2999 nsresult nsNavHistory::QueryRowToResult(int64_t itemId,
3000                                         const nsACString& aBookmarkGuid,
3001                                         const nsACString& aURI,
3002                                         const nsACString& aTitle,
3003                                         uint32_t aAccessCount, PRTime aTime,
3004                                         nsNavHistoryResultNode** aNode) {
3005   // Only assert if the itemId is set. In some cases (e.g. virtual queries), we
3006   // have a guid, but not an itemId.
3007   if (itemId != -1) {
3008     MOZ_ASSERT(!aBookmarkGuid.IsEmpty());
3009   }
3010 
3011   nsCOMPtr<nsINavHistoryQuery> query;
3012   nsCOMPtr<nsINavHistoryQueryOptions> options;
3013   nsresult rv =
3014       QueryStringToQuery(aURI, getter_AddRefs(query), getter_AddRefs(options));
3015   RefPtr<nsNavHistoryResultNode> resultNode;
3016   RefPtr<nsNavHistoryQuery> queryObj = do_QueryObject(query);
3017   NS_ENSURE_STATE(queryObj);
3018   RefPtr<nsNavHistoryQueryOptions> optionsObj = do_QueryObject(options);
3019   NS_ENSURE_STATE(optionsObj);
3020   // If this failed the query does not parse correctly, let the error pass and
3021   // handle it later.
3022   if (NS_SUCCEEDED(rv)) {
3023     // Check if this is a folder shortcut, so we can take a faster path.
3024     nsCString targetFolderGuid =
3025         GetSimpleBookmarksQueryParent(queryObj, optionsObj);
3026     if (!targetFolderGuid.IsEmpty()) {
3027       nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
3028       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
3029 
3030       rv = bookmarks->ResultNodeForContainer(targetFolderGuid, optionsObj,
3031                                              getter_AddRefs(resultNode));
3032       // If this failed the shortcut is pointing to nowhere, let the error pass
3033       // and handle it later.
3034       if (NS_SUCCEEDED(rv)) {
3035         // At this point the node is set up like a regular folder node. Here
3036         // we make the necessary change to make it a folder shortcut.
3037         resultNode->mItemId = itemId;
3038         resultNode->mBookmarkGuid = aBookmarkGuid;
3039         resultNode->GetAsFolder()->mTargetFolderGuid = targetFolderGuid;
3040 
3041         // Use the query item title, unless it's empty (in that case use the
3042         // concrete folder title).
3043         if (!aTitle.IsEmpty()) {
3044           resultNode->mTitle = aTitle;
3045         }
3046       }
3047     } else {
3048       // This is a regular query.
3049       resultNode = new nsNavHistoryQueryResultNode(aTitle, aTime, aURI,
3050                                                    queryObj, optionsObj);
3051       resultNode->mItemId = itemId;
3052       resultNode->mBookmarkGuid = aBookmarkGuid;
3053     }
3054   }
3055 
3056   if (NS_FAILED(rv)) {
3057     NS_WARNING("Generating a generic empty node for a broken query!");
3058     // This is a broken query, that either did not parse or points to not
3059     // existing data.  We don't want to return failure since that will kill the
3060     // whole result.  Instead make a generic empty query node.
3061     resultNode =
3062         new nsNavHistoryQueryResultNode(aTitle, 0, aURI, queryObj, optionsObj);
3063     resultNode->mItemId = itemId;
3064     resultNode->mBookmarkGuid = aBookmarkGuid;
3065     // This is a perf hack to generate an empty query that skips filtering.
3066     resultNode->GetAsQuery()->Options()->SetExcludeItems(true);
3067   }
3068 
3069   resultNode.forget(aNode);
3070   return NS_OK;
3071 }
3072 
3073 // nsNavHistory::VisitIdToResultNode
3074 //
3075 //    Used by the query results to create new nodes on the fly when
3076 //    notifications come in. This just creates a node for the given visit ID.
3077 
VisitIdToResultNode(int64_t visitId,nsNavHistoryQueryOptions * aOptions,nsNavHistoryResultNode ** aResult)3078 nsresult nsNavHistory::VisitIdToResultNode(int64_t visitId,
3079                                            nsNavHistoryQueryOptions* aOptions,
3080                                            nsNavHistoryResultNode** aResult) {
3081   nsAutoCString tagsFragment;
3082   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), true,
3083                      tagsFragment);
3084 
3085   nsCOMPtr<mozIStorageStatement> statement;
3086   switch (aOptions->ResultType()) {
3087     case nsNavHistoryQueryOptions::RESULTS_AS_VISIT:
3088       // visit query - want exact visit time
3089       // Should match kGetInfoIndex_* (see GetQueryResults)
3090       statement = mDB->GetStatement(
3091           NS_LITERAL_CSTRING(
3092               "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
3093               "v.visit_date, null, null, null, null, null, ") +
3094           tagsFragment +
3095           NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3096                              "v.id, v.from_visit, v.visit_type "
3097                              "FROM moz_places h "
3098                              "JOIN moz_historyvisits v ON h.id = v.place_id "
3099                              "WHERE v.id = :visit_id "));
3100       break;
3101 
3102     case nsNavHistoryQueryOptions::RESULTS_AS_URI:
3103       // URL results - want last visit time
3104       // Should match kGetInfoIndex_* (see GetQueryResults)
3105       statement = mDB->GetStatement(
3106           NS_LITERAL_CSTRING(
3107               "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
3108               "h.last_visit_date, null, null, null, null, null, ") +
3109           tagsFragment +
3110           NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3111                              "null, null, null "
3112                              "FROM moz_places h "
3113                              "JOIN moz_historyvisits v ON h.id = v.place_id "
3114                              "WHERE v.id = :visit_id "));
3115       break;
3116 
3117     default:
3118       // Query base types like RESULTS_AS_*_QUERY handle additions
3119       // by registering their own observers when they are expanded.
3120       return NS_OK;
3121   }
3122   NS_ENSURE_STATE(statement);
3123   mozStorageStatementScoper scoper(statement);
3124 
3125   nsresult rv =
3126       statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"), visitId);
3127   NS_ENSURE_SUCCESS(rv, rv);
3128 
3129   bool hasMore = false;
3130   rv = statement->ExecuteStep(&hasMore);
3131   NS_ENSURE_SUCCESS(rv, rv);
3132   if (!hasMore) {
3133     MOZ_ASSERT_UNREACHABLE("Trying to get a result node for an invalid visit");
3134     return NS_ERROR_INVALID_ARG;
3135   }
3136 
3137   nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
3138   NS_ENSURE_SUCCESS(rv, rv);
3139 
3140   return RowToResult(row, aOptions, aResult);
3141 }
3142 
BookmarkIdToResultNode(int64_t aBookmarkId,nsNavHistoryQueryOptions * aOptions,nsNavHistoryResultNode ** aResult)3143 nsresult nsNavHistory::BookmarkIdToResultNode(
3144     int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions,
3145     nsNavHistoryResultNode** aResult) {
3146   nsAutoCString tagsFragment;
3147   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), true,
3148                      tagsFragment);
3149   // Should match kGetInfoIndex_*
3150   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
3151       NS_LITERAL_CSTRING(
3152           "SELECT b.fk, h.url, b.title, "
3153           "h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
3154           "b.dateAdded, b.lastModified, b.parent, ") +
3155       tagsFragment +
3156       NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
3157                          "null, null, null, b.guid, b.position, b.type, b.fk "
3158                          "FROM moz_bookmarks b "
3159                          "JOIN moz_places h ON b.fk = h.id "
3160                          "WHERE b.id = :item_id "));
3161   NS_ENSURE_STATE(stmt);
3162   mozStorageStatementScoper scoper(stmt);
3163 
3164   nsresult rv =
3165       stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmarkId);
3166   NS_ENSURE_SUCCESS(rv, rv);
3167 
3168   bool hasMore = false;
3169   rv = stmt->ExecuteStep(&hasMore);
3170   NS_ENSURE_SUCCESS(rv, rv);
3171   if (!hasMore) {
3172     MOZ_ASSERT_UNREACHABLE(
3173         "Trying to get a result node for an invalid "
3174         "bookmark identifier");
3175     return NS_ERROR_INVALID_ARG;
3176   }
3177 
3178   nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
3179   NS_ENSURE_SUCCESS(rv, rv);
3180 
3181   return RowToResult(row, aOptions, aResult);
3182 }
3183 
URIToResultNode(nsIURI * aURI,nsNavHistoryQueryOptions * aOptions,nsNavHistoryResultNode ** aResult)3184 nsresult nsNavHistory::URIToResultNode(nsIURI* aURI,
3185                                        nsNavHistoryQueryOptions* aOptions,
3186                                        nsNavHistoryResultNode** aResult) {
3187   nsAutoCString tagsFragment;
3188   GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), true,
3189                      tagsFragment);
3190   // Should match kGetInfoIndex_*
3191   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
3192       NS_LITERAL_CSTRING("SELECT h.id, :page_url, COALESCE(b.title, h.title), "
3193                          "h.rev_host, h.visit_count, h.last_visit_date, null, "
3194                          "b.id, b.dateAdded, b.lastModified, b.parent, ") +
3195       tagsFragment +
3196       NS_LITERAL_CSTRING(
3197           ", h.frecency, h.hidden, h.guid, "
3198           "null, null, null, b.guid, b.position, b.type, b.fk "
3199           "FROM moz_places h "
3200           "LEFT JOIN moz_bookmarks b ON b.fk = h.id "
3201           "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url "));
3202   NS_ENSURE_STATE(stmt);
3203   mozStorageStatementScoper scoper(stmt);
3204 
3205   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
3206   NS_ENSURE_SUCCESS(rv, rv);
3207 
3208   bool hasMore = false;
3209   rv = stmt->ExecuteStep(&hasMore);
3210   NS_ENSURE_SUCCESS(rv, rv);
3211   if (!hasMore) {
3212     MOZ_ASSERT_UNREACHABLE("Trying to get a result node for an invalid url");
3213     return NS_ERROR_INVALID_ARG;
3214   }
3215 
3216   nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
3217   NS_ENSURE_SUCCESS(rv, rv);
3218 
3219   return RowToResult(row, aOptions, aResult);
3220 }
3221 
SendPageChangedNotification(nsIURI * aURI,uint32_t aChangedAttribute,const nsAString & aNewValue,const nsACString & aGUID)3222 void nsNavHistory::SendPageChangedNotification(nsIURI* aURI,
3223                                                uint32_t aChangedAttribute,
3224                                                const nsAString& aNewValue,
3225                                                const nsACString& aGUID) {
3226   MOZ_ASSERT(!aGUID.IsEmpty());
3227   NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
3228                    OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID));
3229 }
3230 
GetAgeInDaysString(int32_t aInt,const char * aName,nsACString & aResult)3231 void nsNavHistory::GetAgeInDaysString(int32_t aInt, const char* aName,
3232                                       nsACString& aResult) {
3233   nsIStringBundle* bundle = GetBundle();
3234   if (bundle) {
3235     AutoTArray<nsString, 1> strings;
3236     strings.AppendElement()->AppendInt(aInt);
3237     nsAutoString value;
3238     nsresult rv = bundle->FormatStringFromName(aName, strings, value);
3239     if (NS_SUCCEEDED(rv)) {
3240       CopyUTF16toUTF8(value, aResult);
3241       return;
3242     }
3243   }
3244   aResult.Assign(aName);
3245 }
3246 
GetStringFromName(const char * aName,nsACString & aResult)3247 void nsNavHistory::GetStringFromName(const char* aName, nsACString& aResult) {
3248   nsIStringBundle* bundle = GetBundle();
3249   if (bundle) {
3250     nsAutoString value;
3251     nsresult rv = bundle->GetStringFromName(aName, value);
3252     if (NS_SUCCEEDED(rv)) {
3253       CopyUTF16toUTF8(value, aResult);
3254       return;
3255     }
3256   }
3257   aResult.Assign(aName);
3258 }
3259 
3260 // static
GetMonthName(const PRExplodedTime & aTime,nsACString & aResult)3261 void nsNavHistory::GetMonthName(const PRExplodedTime& aTime,
3262                                 nsACString& aResult) {
3263   nsAutoString month;
3264   nsresult rv = DateTimeFormat::FormatPRExplodedTime(
3265       kDateFormatMonthLong, kTimeFormatNone, &aTime, month);
3266   if (NS_FAILED(rv)) {
3267     aResult = nsPrintfCString("[%d]", aTime.tm_month + 1);
3268     return;
3269   }
3270   CopyUTF16toUTF8(month, aResult);
3271 }
3272 
3273 // static
GetMonthYear(const PRExplodedTime & aTime,nsACString & aResult)3274 void nsNavHistory::GetMonthYear(const PRExplodedTime& aTime,
3275                                 nsACString& aResult) {
3276   nsAutoString monthYear;
3277   nsresult rv = DateTimeFormat::FormatPRExplodedTime(
3278       kDateFormatYearMonthLong, kTimeFormatNone, &aTime, monthYear);
3279   if (NS_FAILED(rv)) {
3280     aResult = nsPrintfCString("[%d-%d]", aTime.tm_month + 1, aTime.tm_year);
3281     return;
3282   }
3283   CopyUTF16toUTF8(monthYear, aResult);
3284 }
3285 
3286 namespace {
3287 
3288 // GetSimpleBookmarksQueryParent
3289 //
3290 //    Determines if this is a simple bookmarks query for a
3291 //    folder with no other constraints. In these common cases, we can more
3292 //    efficiently compute the results.
3293 //
3294 //    A simple bookmarks query will result in a hierarchical tree of
3295 //    bookmark items, folders and separators.
3296 //
3297 //    Returns the folder ID if it is a simple folder query, 0 if not.
GetSimpleBookmarksQueryParent(const RefPtr<nsNavHistoryQuery> & aQuery,const RefPtr<nsNavHistoryQueryOptions> & aOptions)3298 static nsCString GetSimpleBookmarksQueryParent(
3299     const RefPtr<nsNavHistoryQuery>& aQuery,
3300     const RefPtr<nsNavHistoryQueryOptions>& aOptions) {
3301   if (aQuery->Parents().Length() != 1) return EmptyCString();
3302 
3303   bool hasIt;
3304   if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
3305     return EmptyCString();
3306   if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
3307     return EmptyCString();
3308   if (!aQuery->Domain().IsVoid()) return EmptyCString();
3309   if (aQuery->Uri()) return EmptyCString();
3310   if (!aQuery->SearchTerms().IsEmpty()) return EmptyCString();
3311   if (aQuery->Tags().Length() > 0) return EmptyCString();
3312   if (aOptions->MaxResults() > 0) return EmptyCString();
3313 
3314   // Don't care about onlyBookmarked flag, since specifying a bookmark
3315   // folder is inferring onlyBookmarked.
3316 
3317   return aQuery->Parents()[0];
3318 }
3319 
3320 // ParseSearchTermsFromQuery
3321 //
3322 //    Construct an array of search terms from the given query.
3323 //    Within a query, all the terms are ANDed together.
3324 //
3325 //    This just breaks the query up into words. We don't do anything fancy,
3326 //    not even quoting. We do, however, strip quotes, because people might
3327 //    try to input quotes expecting them to do something and get no results
3328 //    back.
3329 
isQueryWhitespace(char16_t ch)3330 inline bool isQueryWhitespace(char16_t ch) { return ch == ' '; }
3331 
ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery> & aQuery,nsTArray<nsString> * aTerms)3332 void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
3333                                nsTArray<nsString>* aTerms) {
3334   int32_t lastBegin = -1;
3335   if (!aQuery->SearchTerms().IsEmpty()) {
3336     const nsString& searchTerms = aQuery->SearchTerms();
3337     for (uint32_t j = 0; j < searchTerms.Length(); j++) {
3338       if (isQueryWhitespace(searchTerms[j]) || searchTerms[j] == '"') {
3339         if (lastBegin >= 0) {
3340           // found the end of a word
3341           aTerms->AppendElement(
3342               Substring(searchTerms, lastBegin, j - lastBegin));
3343           lastBegin = -1;
3344         }
3345       } else {
3346         if (lastBegin < 0) {
3347           // found the beginning of a word
3348           lastBegin = j;
3349         }
3350       }
3351     }
3352     // last word
3353     if (lastBegin >= 0)
3354       aTerms->AppendElement(Substring(searchTerms, lastBegin));
3355   }
3356 }
3357 
3358 }  // namespace
3359 
UpdateFrecency(int64_t aPlaceId)3360 nsresult nsNavHistory::UpdateFrecency(int64_t aPlaceId) {
3361   nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt =
3362       mDB->GetAsyncStatement(
3363           "UPDATE moz_places "
3364           "SET frecency = NOTIFY_FRECENCY("
3365           "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date"
3366           ") "
3367           "WHERE id = :page_id");
3368   NS_ENSURE_STATE(updateFrecencyStmt);
3369   nsresult rv = updateFrecencyStmt->BindInt64ByName(
3370       NS_LITERAL_CSTRING("page_id"), aPlaceId);
3371   NS_ENSURE_SUCCESS(rv, rv);
3372   nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement(
3373       "UPDATE moz_places "
3374       "SET hidden = 0 "
3375       "WHERE id = :page_id AND frecency <> 0");
3376   NS_ENSURE_STATE(updateHiddenStmt);
3377   rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
3378                                          aPlaceId);
3379   NS_ENSURE_SUCCESS(rv, rv);
3380 
3381   nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
3382   if (!conn) {
3383     return NS_ERROR_UNEXPECTED;
3384   }
3385 
3386   nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
3387       ToRefPtr(std::move(updateFrecencyStmt)),
3388       ToRefPtr(std::move(updateHiddenStmt)),
3389   };
3390   nsCOMPtr<mozIStoragePendingStatement> ps;
3391   rv = conn->ExecuteAsync(stmts, nullptr, getter_AddRefs(ps));
3392   NS_ENSURE_SUCCESS(rv, rv);
3393 
3394   // Trigger frecency updates for all affected origins.
3395   nsCOMPtr<mozIStorageAsyncStatement> updateOriginFrecenciesStmt =
3396       mDB->GetAsyncStatement("DELETE FROM moz_updateoriginsupdate_temp");
3397   NS_ENSURE_STATE(updateOriginFrecenciesStmt);
3398   rv = updateOriginFrecenciesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
3399   NS_ENSURE_SUCCESS(rv, rv);
3400 
3401   return NS_OK;
3402 }
3403 
GetCollation()3404 nsICollation* nsNavHistory::GetCollation() {
3405   if (mCollation) return mCollation;
3406 
3407   // collation
3408   nsCOMPtr<nsICollationFactory> cfact =
3409       do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
3410   NS_ENSURE_TRUE(cfact, nullptr);
3411   nsresult rv = cfact->CreateCollation(getter_AddRefs(mCollation));
3412   NS_ENSURE_SUCCESS(rv, nullptr);
3413 
3414   return mCollation;
3415 }
3416 
GetBundle()3417 nsIStringBundle* nsNavHistory::GetBundle() {
3418   if (!mBundle) {
3419     nsCOMPtr<nsIStringBundleService> bundleService =
3420         services::GetStringBundleService();
3421     NS_ENSURE_TRUE(bundleService, nullptr);
3422     nsresult rv = bundleService->CreateBundle(
3423         "chrome://places/locale/places.properties", getter_AddRefs(mBundle));
3424     NS_ENSURE_SUCCESS(rv, nullptr);
3425   }
3426   return mBundle;
3427 }
3428