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