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