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