Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include <stdio.h>
8
9
#include "mozilla/DebugOnly.h"
10
#include "mozilla/IntegerPrintfMacros.h"
11
12
#include "nsNavHistory.h"
13
14
#include "mozIPlacesAutoComplete.h"
15
#include "nsNavBookmarks.h"
16
#include "nsAnnotationService.h"
17
#include "nsFaviconService.h"
18
#include "nsPlacesMacros.h"
19
#include "nsPlacesTriggers.h"
20
#include "DateTimeFormat.h"
21
#include "History.h"
22
#include "Helpers.h"
23
24
#include "nsTArray.h"
25
#include "nsCollationCID.h"
26
#include "nsNetUtil.h"
27
#include "nsPrintfCString.h"
28
#include "nsPromiseFlatString.h"
29
#include "nsString.h"
30
#include "nsUnicharUtils.h"
31
#include "prsystem.h"
32
#include "prtime.h"
33
#include "nsEscape.h"
34
#include "nsIEffectiveTLDService.h"
35
#include "nsIClassInfoImpl.h"
36
#include "nsIIDNService.h"
37
#include "nsQueryObject.h"
38
#include "nsThreadUtils.h"
39
#include "nsAppDirectoryServiceDefs.h"
40
#include "nsMathUtils.h"
41
#include "mozilla/storage.h"
42
#include "mozilla/Preferences.h"
43
#include <algorithm>
44
45
using namespace mozilla;
46
using namespace mozilla::places;
47
48
// The maximum number of things that we will store in the recent events list
49
// before calling ExpireNonrecentEvents. This number should be big enough so it
50
// is very difficult to get that many unconsumed events (for example, typed but
51
// never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
52
// checking each one for every page visit, which will be somewhat slower.
53
#define RECENT_EVENT_QUEUE_MAX_LENGTH 128
54
55
// preference ID strings
56
#define PREF_HISTORY_ENABLED "places.history.enabled"
57
58
#define PREF_FREC_NUM_VISITS "places.frecency.numVisits"
59
#define PREF_FREC_NUM_VISITS_DEF 10
60
#define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff"
61
#define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4
62
#define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff"
63
#define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14
64
#define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff"
65
#define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31
66
#define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff"
67
#define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90
68
#define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight"
69
#define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100
70
#define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight"
71
#define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70
72
#define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight"
73
#define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50
74
#define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight"
75
#define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30
76
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight"
77
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10
78
#define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus"
79
#define PREF_FREC_EMBED_VISIT_BONUS_DEF 0
80
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus"
81
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0
82
#define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus"
83
#define PREF_FREC_LINK_VISIT_BONUS_DEF 100
84
#define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus"
85
#define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000
86
#define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus"
87
#define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75
88
#define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus"
89
#define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0
90
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS \
91
"places.frecency.permRedirectVisitBonus"
92
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0
93
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS \
94
"places.frecency.tempRedirectVisitBonus"
95
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0
96
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS \
97
"places.frecency.redirectSourceVisitBonus"
98
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS_DEF 25
99
#define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus"
100
#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0
101
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS \
102
"places.frecency.unvisitedBookmarkBonus"
103
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140
104
#define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus"
105
#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200
106
#define PREF_FREC_RELOAD_VISIT_BONUS "places.frecency.reloadVisitBonus"
107
#define PREF_FREC_RELOAD_VISIT_BONUS_DEF 0
108
109
// This is a 'hidden' pref for the purposes of unit tests.
110
#define PREF_FREC_DECAY_RATE "places.frecency.decayRate"
111
#define PREF_FREC_DECAY_RATE_DEF 0.975f
112
// An adaptive history entry is removed if unused for these many days.
113
#define ADAPTIVE_HISTORY_EXPIRE_DAYS 90
114
115
// In order to avoid calling PR_now() too often we use a cached "now" value
116
// for repeating stuff. These are milliseconds between "now" cache refreshes.
117
#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
118
119
// character-set annotation
120
#define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet")
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_FREC_NUM_VISITS,
144
PREF_FREC_FIRST_BUCKET_CUTOFF,
145
PREF_FREC_SECOND_BUCKET_CUTOFF,
146
PREF_FREC_THIRD_BUCKET_CUTOFF,
147
PREF_FREC_FOURTH_BUCKET_CUTOFF,
148
PREF_FREC_FIRST_BUCKET_WEIGHT,
149
PREF_FREC_SECOND_BUCKET_WEIGHT,
150
PREF_FREC_THIRD_BUCKET_WEIGHT,
151
PREF_FREC_FOURTH_BUCKET_WEIGHT,
152
PREF_FREC_DEFAULT_BUCKET_WEIGHT,
153
PREF_FREC_EMBED_VISIT_BONUS,
154
PREF_FREC_FRAMED_LINK_VISIT_BONUS,
155
PREF_FREC_LINK_VISIT_BONUS,
156
PREF_FREC_TYPED_VISIT_BONUS,
157
PREF_FREC_BOOKMARK_VISIT_BONUS,
158
PREF_FREC_DOWNLOAD_VISIT_BONUS,
159
PREF_FREC_PERM_REDIRECT_VISIT_BONUS,
160
PREF_FREC_TEMP_REDIRECT_VISIT_BONUS,
161
PREF_FREC_REDIR_SOURCE_VISIT_BONUS,
162
PREF_FREC_DEFAULT_VISIT_BONUS,
163
PREF_FREC_UNVISITED_BOOKMARK_BONUS,
164
PREF_FREC_UNVISITED_TYPED_BONUS,
165
nullptr};
166
167
NS_IMPL_ADDREF(nsNavHistory)
168
NS_IMPL_RELEASE(nsNavHistory)
169
170
NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON,
171
NS_NAVHISTORYSERVICE_CID)
172
NS_INTERFACE_MAP_BEGIN(nsNavHistory)
173
NS_INTERFACE_MAP_ENTRY(nsINavHistoryService)
174
NS_INTERFACE_MAP_ENTRY(nsIObserver)
175
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
176
NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
177
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
178
NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
179
NS_INTERFACE_MAP_END
180
181
// We don't care about flattening everything
182
NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, nsINavHistoryService)
183
184
namespace {
185
186
static nsCString GetSimpleBookmarksQueryParent(
187
const RefPtr<nsNavHistoryQuery>& aQuery,
188
const RefPtr<nsNavHistoryQueryOptions>& aOptions);
189
static void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
190
nsTArray<nsString>* aTerms);
191
192
void GetTagsSqlFragment(int64_t aTagsFolder, const nsACString& aRelation,
193
bool aHasSearchTerms, nsACString& _sqlFragment) {
194
if (!aHasSearchTerms)
195
_sqlFragment.AssignLiteral("null");
196
else {
197
// This subquery DOES NOT order tags for performance reasons.
198
_sqlFragment.Assign(
199
NS_LITERAL_CSTRING("(SELECT GROUP_CONCAT(t_t.title, ',') "
200
"FROM moz_bookmarks b_t "
201
"JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent "
202
"WHERE b_t.fk = ") +
203
aRelation +
204
NS_LITERAL_CSTRING(" "
205
"AND t_t.parent = ") +
206
nsPrintfCString("%" PRId64, aTagsFolder) +
207
NS_LITERAL_CSTRING(" "
208
")"));
209
}
210
211
_sqlFragment.AppendLiteral(" AS tags ");
212
}
213
214
/**
215
* Recalculates invalid frecencies in chunks on the storage thread, optionally
216
* decays frecencies, and notifies history observers on the main thread.
217
*/
218
class FixAndDecayFrecencyRunnable final : public Runnable {
219
public:
220
explicit FixAndDecayFrecencyRunnable(Database* aDB, float aDecayRate)
221
: Runnable("places::FixAndDecayFrecencyRunnable"),
222
mDB(aDB),
223
mDecayRate(aDecayRate),
224
mDecayReason(mozIStorageStatementCallback::REASON_FINISHED) {}
225
226
NS_IMETHOD
227
Run() override {
228
if (NS_IsMainThread()) {
229
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
230
NS_ENSURE_STATE(navHistory);
231
232
navHistory->DecayFrecencyCompleted(mDecayReason);
233
return NS_OK;
234
}
235
236
MOZ_ASSERT(!NS_IsMainThread(),
237
"Frecencies should be recalculated on async thread");
238
239
nsCOMPtr<mozIStorageStatement> updateStmt = mDB->GetStatement(
240
"UPDATE moz_places "
241
"SET frecency = CALCULATE_FRECENCY(id) "
242
"WHERE id IN ("
243
"SELECT id FROM moz_places "
244
"WHERE frecency < 0 "
245
"ORDER BY frecency ASC "
246
"LIMIT 400"
247
")");
248
NS_ENSURE_STATE(updateStmt);
249
nsresult rv = updateStmt->Execute();
250
NS_ENSURE_SUCCESS(rv, rv);
251
252
nsCOMPtr<mozIStorageStatement> selectStmt = mDB->GetStatement(
253
"SELECT id FROM moz_places WHERE frecency < 0 "
254
"LIMIT 1");
255
NS_ENSURE_STATE(selectStmt);
256
bool hasResult = false;
257
rv = selectStmt->ExecuteStep(&hasResult);
258
NS_ENSURE_SUCCESS(rv, rv);
259
if (hasResult) {
260
// There are more invalid frecencies to fix. Re-dispatch to the async
261
// storage thread for the next chunk.
262
return NS_DispatchToCurrentThread(this);
263
}
264
265
mozStorageTransaction transaction(
266
mDB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
267
268
if (NS_WARN_IF(NS_FAILED(DecayFrecencies()))) {
269
mDecayReason = mozIStorageStatementCallback::REASON_ERROR;
270
}
271
272
// We've finished fixing and decaying frecencies. Trigger frecency updates
273
// for all affected origins.
274
nsCOMPtr<mozIStorageStatement> updateOriginFrecenciesStmt =
275
mDB->GetStatement("DELETE FROM moz_updateoriginsupdate_temp");
276
NS_ENSURE_STATE(updateOriginFrecenciesStmt);
277
rv = updateOriginFrecenciesStmt->Execute();
278
NS_ENSURE_SUCCESS(rv, rv);
279
280
rv = transaction.Commit();
281
NS_ENSURE_SUCCESS(rv, rv);
282
283
// Re-dispatch to the main thread to notify observers.
284
return NS_DispatchToMainThread(this);
285
}
286
287
private:
288
nsresult DecayFrecencies() {
289
TimeStamp start = TimeStamp::Now();
290
291
// Globally decay places frecency rankings to estimate reduced frecency
292
// values of pages that haven't been visited for a while, i.e., they do
293
// not get an updated frecency. A scaling factor of .975 results in .5 the
294
// original value after 28 days.
295
// When changing the scaling factor, ensure that the barrier in
296
// moz_places_afterupdate_frecency_trigger still ignores these changes.
297
nsCOMPtr<mozIStorageStatement> decayFrecency = mDB->GetStatement(
298
"UPDATE moz_places SET frecency = ROUND(frecency * :decay_rate) "
299
"WHERE frecency > 0");
300
NS_ENSURE_STATE(decayFrecency);
301
nsresult rv = decayFrecency->BindDoubleByName(
302
NS_LITERAL_CSTRING("decay_rate"), static_cast<double>(mDecayRate));
303
NS_ENSURE_SUCCESS(rv, rv);
304
rv = decayFrecency->Execute();
305
NS_ENSURE_SUCCESS(rv, rv);
306
307
// Decay potentially unused adaptive entries (e.g. those that are at 1)
308
// to allow better chances for new entries that will start at 1.
309
nsCOMPtr<mozIStorageStatement> decayAdaptive = mDB->GetStatement(
310
"UPDATE moz_inputhistory SET use_count = use_count * :decay_rate");
311
NS_ENSURE_STATE(decayAdaptive);
312
rv = decayAdaptive->BindDoubleByName(NS_LITERAL_CSTRING("decay_rate"),
313
static_cast<double>(mDecayRate));
314
NS_ENSURE_SUCCESS(rv, rv);
315
rv = decayAdaptive->Execute();
316
NS_ENSURE_SUCCESS(rv, rv);
317
318
// Delete any adaptive entries that won't help in ordering anymore.
319
nsCOMPtr<mozIStorageStatement> deleteAdaptive = mDB->GetStatement(
320
"DELETE FROM moz_inputhistory WHERE use_count < :use_count");
321
NS_ENSURE_STATE(deleteAdaptive);
322
rv = deleteAdaptive->BindDoubleByName(
323
NS_LITERAL_CSTRING("use_count"),
324
std::pow(static_cast<double>(mDecayRate),
325
ADAPTIVE_HISTORY_EXPIRE_DAYS));
326
NS_ENSURE_SUCCESS(rv, rv);
327
rv = deleteAdaptive->Execute();
328
NS_ENSURE_SUCCESS(rv, rv);
329
330
Telemetry::AccumulateTimeDelta(
331
Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS, start);
332
333
return NS_OK;
334
}
335
336
RefPtr<Database> mDB;
337
float mDecayRate;
338
uint16_t mDecayReason;
339
};
340
341
} // namespace
342
343
// Queries rows indexes to bind or get values, if adding a new one, be sure to
344
// update nsNavBookmarks statements and its kGetChildrenIndex_* constants
345
const int32_t nsNavHistory::kGetInfoIndex_PageID = 0;
346
const int32_t nsNavHistory::kGetInfoIndex_URL = 1;
347
const int32_t nsNavHistory::kGetInfoIndex_Title = 2;
348
const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3;
349
const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4;
350
const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5;
351
const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6;
352
const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
353
const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
354
const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
355
const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
356
const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
357
const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
358
const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
359
const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
360
const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
361
const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
362
const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
363
// These columns are followed by corresponding constants in nsNavBookmarks.cpp,
364
// which must be kept in sync:
365
// nsNavBookmarks::kGetChildrenIndex_Guid = 18;
366
// nsNavBookmarks::kGetChildrenIndex_Position = 19;
367
// nsNavBookmarks::kGetChildrenIndex_Type = 20;
368
// nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
369
370
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
371
372
nsNavHistory::nsNavHistory()
373
: mCachedNow(0),
374
mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
375
mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
376
mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
377
mHistoryEnabled(true),
378
mNumVisitsForFrecency(10),
379
mDecayFrecencyPendingCount(0),
380
mTagsFolder(-1),
381
mDaysOfHistory(-1),
382
mLastCachedStartOfDay(INT64_MAX),
383
mLastCachedEndOfDay(0),
384
mCanNotify(true)
385
#ifdef XP_WIN
386
,
387
mCryptoProviderInitialized(false)
388
#endif
389
{
390
NS_ASSERTION(!gHistoryService,
391
"Attempting to create two instances of the service!");
392
#ifdef XP_WIN
393
BOOL cryptoAcquired =
394
CryptAcquireContext(&mCryptoProvider, 0, 0, PROV_RSA_FULL,
395
CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
396
if (cryptoAcquired) {
397
mCryptoProviderInitialized = true;
398
}
399
#endif
400
gHistoryService = this;
401
}
402
403
nsNavHistory::~nsNavHistory() {
404
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
405
406
// remove the static reference to the service. Check to make sure its us
407
// in case somebody creates an extra instance of the service.
408
NS_ASSERTION(gHistoryService == this,
409
"Deleting a non-singleton instance of the service");
410
411
if (gHistoryService == this) gHistoryService = nullptr;
412
413
#ifdef XP_WIN
414
if (mCryptoProviderInitialized) {
415
Unused << CryptReleaseContext(mCryptoProvider, 0);
416
}
417
#endif
418
}
419
420
nsresult nsNavHistory::Init() {
421
LoadPrefs();
422
423
mDB = Database::GetDatabase();
424
NS_ENSURE_STATE(mDB);
425
426
/*****************************************************************************
427
*** IMPORTANT NOTICE!
428
***
429
*** Nothing after these add observer calls should return anything but NS_OK.
430
*** If a failure code is returned, this nsNavHistory object will be held onto
431
*** by the observer service and the preference service.
432
****************************************************************************/
433
434
// Observe preferences changes.
435
Preferences::AddWeakObservers(this, kObservedPrefs);
436
437
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
438
if (obsSvc) {
439
(void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
440
(void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true);
441
}
442
443
// Don't add code that can fail here! Do it up above, before we add our
444
// observers.
445
446
return NS_OK;
447
}
448
449
NS_IMETHODIMP
450
nsNavHistory::GetDatabaseStatus(uint16_t* aDatabaseStatus) {
451
NS_ENSURE_ARG_POINTER(aDatabaseStatus);
452
*aDatabaseStatus = mDB->GetDatabaseStatus();
453
return NS_OK;
454
}
455
456
uint32_t nsNavHistory::GetRecentFlags(nsIURI* aURI) {
457
uint32_t result = 0;
458
nsAutoCString spec;
459
nsresult rv = aURI->GetSpec(spec);
460
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
461
462
if (NS_SUCCEEDED(rv)) {
463
if (CheckIsRecentEvent(&mRecentTyped, spec)) result |= RECENT_TYPED;
464
if (CheckIsRecentEvent(&mRecentLink, spec)) result |= RECENT_ACTIVATED;
465
if (CheckIsRecentEvent(&mRecentBookmark, spec)) result |= RECENT_BOOKMARKED;
466
}
467
468
return result;
469
}
470
471
nsresult nsNavHistory::GetIdForPage(nsIURI* aURI, int64_t* _pageId,
472
nsCString& _GUID) {
473
*_pageId = 0;
474
475
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
476
"SELECT id, url, title, rev_host, visit_count, guid "
477
"FROM moz_places "
478
"WHERE url_hash = hash(:page_url) AND url = :page_url ");
479
NS_ENSURE_STATE(stmt);
480
mozStorageStatementScoper scoper(stmt);
481
482
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
483
NS_ENSURE_SUCCESS(rv, rv);
484
485
bool hasEntry = false;
486
rv = stmt->ExecuteStep(&hasEntry);
487
NS_ENSURE_SUCCESS(rv, rv);
488
489
if (hasEntry) {
490
rv = stmt->GetInt64(0, _pageId);
491
NS_ENSURE_SUCCESS(rv, rv);
492
rv = stmt->GetUTF8String(5, _GUID);
493
NS_ENSURE_SUCCESS(rv, rv);
494
}
495
496
return NS_OK;
497
}
498
499
nsresult nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, int64_t* _pageId,
500
nsCString& _GUID) {
501
nsresult rv = GetIdForPage(aURI, _pageId, _GUID);
502
NS_ENSURE_SUCCESS(rv, rv);
503
504
if (*_pageId != 0) {
505
return NS_OK;
506
}
507
508
{
509
// Create a new hidden, untyped and unvisited entry.
510
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
511
"INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, "
512
"guid) "
513
"VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, "
514
":guid) ");
515
NS_ENSURE_STATE(stmt);
516
mozStorageStatementScoper scoper(stmt);
517
518
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
519
NS_ENSURE_SUCCESS(rv, rv);
520
// host (reversed with trailing period)
521
nsAutoString revHost;
522
rv = GetReversedHostname(aURI, revHost);
523
// Not all URI types have hostnames, so this is optional.
524
if (NS_SUCCEEDED(rv)) {
525
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
526
} else {
527
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host"));
528
}
529
NS_ENSURE_SUCCESS(rv, rv);
530
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
531
NS_ENSURE_SUCCESS(rv, rv);
532
nsAutoCString spec;
533
rv = aURI->GetSpec(spec);
534
NS_ENSURE_SUCCESS(rv, rv);
535
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
536
IsQueryURI(spec) ? 0 : -1);
537
NS_ENSURE_SUCCESS(rv, rv);
538
rv = GenerateGUID(_GUID);
539
NS_ENSURE_SUCCESS(rv, rv);
540
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
541
NS_ENSURE_SUCCESS(rv, rv);
542
543
rv = stmt->Execute();
544
NS_ENSURE_SUCCESS(rv, rv);
545
546
*_pageId = sLastInsertedPlaceId;
547
}
548
549
{
550
// Trigger the updates to the moz_origins tables
551
nsCOMPtr<mozIStorageStatement> stmt =
552
mDB->GetStatement("DELETE FROM moz_updateoriginsinsert_temp");
553
NS_ENSURE_STATE(stmt);
554
mozStorageStatementScoper scoper(stmt);
555
}
556
557
return NS_OK;
558
}
559
560
void nsNavHistory::LoadPrefs() {
561
// History preferences.
562
mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true);
563
564
// Frecency preferences.
565
#define FRECENCY_PREF(_prop, _pref) \
566
_prop = Preferences::GetInt(_pref, _pref##_DEF)
567
568
FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS);
569
FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF);
570
FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF);
571
FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF);
572
FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF);
573
FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS);
574
FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS);
575
FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS);
576
FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS);
577
FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS);
578
FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS);
579
FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS);
580
FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS);
581
FRECENCY_PREF(mRedirectSourceVisitBonus, PREF_FREC_REDIR_SOURCE_VISIT_BONUS);
582
FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS);
583
FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS);
584
FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS);
585
FRECENCY_PREF(mReloadVisitBonus, PREF_FREC_RELOAD_VISIT_BONUS);
586
FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT);
587
FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT);
588
FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT);
589
FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT);
590
FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT);
591
592
#undef FRECENCY_PREF
593
}
594
595
void nsNavHistory::UpdateDaysOfHistory(PRTime visitTime) {
596
if (mDaysOfHistory == 0) {
597
mDaysOfHistory = 1;
598
}
599
600
if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
601
mDaysOfHistory = -1;
602
}
603
}
604
605
void nsNavHistory::NotifyTitleChange(nsIURI* aURI, const nsString& aTitle,
606
const nsACString& aGUID) {
607
MOZ_ASSERT(!aGUID.IsEmpty());
608
NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
609
OnTitleChanged(aURI, aTitle, aGUID));
610
}
611
612
void nsNavHistory::NotifyFrecencyChanged(const nsACString& aSpec,
613
int32_t aNewFrecency,
614
const nsACString& aGUID, bool aHidden,
615
PRTime aLastVisitDate) {
616
MOZ_ASSERT(!aGUID.IsEmpty());
617
618
nsCOMPtr<nsIURI> uri;
619
Unused << NS_NewURI(getter_AddRefs(uri), aSpec);
620
// We cannot assert since some automated tests are checking this path.
621
NS_WARNING_ASSERTION(uri,
622
"Invalid URI in nsNavHistory::NotifyFrecencyChanged");
623
// Notify a frecency change only if we have a valid uri, otherwise
624
// the observer couldn't gather any useful data from the notification.
625
if (!uri) {
626
return;
627
}
628
NOTIFY_OBSERVERS(
629
mCanNotify, mObservers, nsINavHistoryObserver,
630
OnFrecencyChanged(uri, aNewFrecency, aGUID, aHidden, aLastVisitDate));
631
}
632
633
void nsNavHistory::NotifyManyFrecenciesChanged() {
634
NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
635
OnManyFrecenciesChanged());
636
}
637
638
void nsNavHistory::DispatchFrecencyChangedNotification(
639
const nsACString& aSpec, int32_t aNewFrecency, const nsACString& aGUID,
640
bool aHidden, PRTime aLastVisitDate) const {
641
Unused << NS_DispatchToMainThread(
642
NewRunnableMethod<nsCString, int32_t, nsCString, bool, PRTime>(
643
"nsNavHistory::NotifyFrecencyChanged",
644
const_cast<nsNavHistory*>(this), &nsNavHistory::NotifyFrecencyChanged,
645
aSpec, aNewFrecency, aGUID, aHidden, aLastVisitDate));
646
}
647
648
NS_IMETHODIMP
649
nsNavHistory::RecalculateOriginFrecencyStats(nsIObserver* aCallback) {
650
RefPtr<nsNavHistory> self(this);
651
nsMainThreadPtrHandle<nsIObserver> callback(
652
!aCallback ? nullptr
653
: new nsMainThreadPtrHolder<nsIObserver>(
654
"nsNavHistory::RecalculateOriginFrecencyStats callback",
655
aCallback));
656
657
nsCOMPtr<nsIEventTarget> target(do_GetInterface(mDB->MainConn()));
658
NS_ENSURE_STATE(target);
659
nsresult rv = target->Dispatch(NS_NewRunnableFunction(
660
"nsNavHistory::RecalculateOriginFrecencyStats", [self, callback] {
661
Unused << self->RecalculateOriginFrecencyStatsInternal();
662
Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
663
"nsNavHistory::RecalculateOriginFrecencyStats callback",
664
[callback] {
665
if (callback) {
666
Unused << callback->Observe(nullptr, "", nullptr);
667
}
668
}));
669
}));
670
NS_ENSURE_SUCCESS(rv, rv);
671
672
return NS_OK;
673
}
674
675
nsresult nsNavHistory::RecalculateOriginFrecencyStatsInternal() {
676
nsCOMPtr<mozIStorageConnection> conn(mDB->MainConn());
677
NS_ENSURE_STATE(conn);
678
679
nsresult rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
680
"INSERT OR REPLACE INTO moz_meta(key, value) VALUES "
681
"( "
682
"'" MOZ_META_KEY_ORIGIN_FRECENCY_COUNT
683
"' , "
684
"(SELECT COUNT(*) FROM moz_origins WHERE frecency > 0) "
685
"), "
686
"( "
687
"'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM
688
"', "
689
"(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
690
"), "
691
"( "
692
"'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES
693
"' , "
694
"(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
695
") "));
696
NS_ENSURE_SUCCESS(rv, rv);
697
698
return NS_OK;
699
}
700
701
Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
702
Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
703
704
void // static
705
nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
706
const int64_t aLastInsertedId) {
707
if (aTable.EqualsLiteral("moz_places")) {
708
nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
709
} else if (aTable.EqualsLiteral("moz_historyvisits")) {
710
nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
711
} else {
712
MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
713
}
714
}
715
716
int32_t nsNavHistory::GetDaysOfHistory() {
717
MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
718
719
if (mDaysOfHistory != -1) return mDaysOfHistory;
720
721
// SQLite doesn't have a CEIL() function, so we must do that later.
722
// We should also take into account timers resolution, that may be as bad as
723
// 16ms on Windows, so in some cases the difference may be 0, if the
724
// check is done near the visit. Thus remember to check for NULL separately.
725
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
726
"SELECT CAST(( "
727
"strftime('%s','now','localtime','utc') - "
728
"(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) "
729
") AS DOUBLE) "
730
"/86400, "
731
"strftime('%s','now','localtime','+1 day','start of day','utc') * "
732
"1000000");
733
NS_ENSURE_TRUE(stmt, 0);
734
mozStorageStatementScoper scoper(stmt);
735
736
bool hasResult;
737
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
738
// If we get NULL, then there are no visits, otherwise there must always be
739
// at least 1 day of history.
740
bool hasNoVisits;
741
(void)stmt->GetIsNull(0, &hasNoVisits);
742
mDaysOfHistory =
743
hasNoVisits
744
? 0
745
: std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0))));
746
mLastCachedStartOfDay =
747
NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
748
mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1.
749
}
750
751
return mDaysOfHistory;
752
}
753
754
PRTime nsNavHistory::GetNow() {
755
if (!mCachedNow) {
756
mCachedNow = PR_Now();
757
if (!mExpireNowTimer) mExpireNowTimer = NS_NewTimer();
758
if (mExpireNowTimer)
759
mExpireNowTimer->InitWithNamedFuncCallback(
760
expireNowTimerCallback, this, RENEW_CACHED_NOW_TIMEOUT,
761
nsITimer::TYPE_ONE_SHOT, "nsNavHistory::GetNow");
762
}
763
return mCachedNow;
764
}
765
766
void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) {
767
nsNavHistory* history = static_cast<nsNavHistory*>(aClosure);
768
if (history) {
769
history->mCachedNow = 0;
770
history->mExpireNowTimer = nullptr;
771
}
772
}
773
774
/**
775
* Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
776
* Pass in a pre-normalized now and a date, and we'll find the difference since
777
* midnight on each of the days.
778
*/
779
static PRTime NormalizeTimeRelativeToday(PRTime aTime) {
780
// round to midnight this morning
781
PRExplodedTime explodedTime;
782
PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
783
784
// set to midnight (0:00)
785
explodedTime.tm_min = explodedTime.tm_hour = explodedTime.tm_sec =
786
explodedTime.tm_usec = 0;
787
788
return PR_ImplodeTime(&explodedTime);
789
}
790
791
// nsNavHistory::NormalizeTime
792
//
793
// Converts a nsINavHistoryQuery reference+offset time into a PRTime
794
// relative to the epoch.
795
//
796
// It is important that this function NOT use the current time optimization.
797
// It is called to update queries, and we really need to know what right
798
// now is because those incoming values will also have current times that
799
// we will have to compare against.
800
801
PRTime // static
802
nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) {
803
PRTime ref;
804
switch (aRelative) {
805
case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
806
return aOffset;
807
case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
808
ref = NormalizeTimeRelativeToday(PR_Now());
809
break;
810
case nsINavHistoryQuery::TIME_RELATIVE_NOW:
811
ref = PR_Now();
812
break;
813
default:
814
MOZ_ASSERT_UNREACHABLE("Invalid relative time");
815
return 0;
816
}
817
return ref + aOffset;
818
}
819
820
// nsNavHistory::DomainNameFromURI
821
//
822
// This does the www.mozilla.org -> mozilla.org and
823
// foo.theregister.co.uk -> theregister.co.uk conversion
824
void nsNavHistory::DomainNameFromURI(nsIURI* aURI, nsACString& aDomainName) {
825
// lazily get the effective tld service
826
if (!mTLDService)
827
mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
828
829
if (mTLDService) {
830
// get the base domain for a given hostname.
831
// e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
832
nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName);
833
if (NS_SUCCEEDED(rv)) return;
834
}
835
836
// just return the original hostname
837
// (it's also possible the host is an IP address)
838
aURI->GetAsciiHost(aDomainName);
839
}
840
841
bool nsNavHistory::hasHistoryEntries() { return GetDaysOfHistory() > 0; }
842
843
// Call this method before visiting a URL in order to help determine the
844
// transition type of the visit.
845
//
846
// @see MarkPageAsTyped
847
848
NS_IMETHODIMP
849
nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) {
850
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
851
NS_ENSURE_ARG(aURI);
852
853
// don't add when history is disabled
854
if (IsHistoryDisabled()) return NS_OK;
855
856
nsAutoCString uriString;
857
nsresult rv = aURI->GetSpec(uriString);
858
NS_ENSURE_SUCCESS(rv, rv);
859
860
mRecentBookmark.Put(uriString, GetNow());
861
862
if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
863
ExpireNonrecentEvents(&mRecentBookmark);
864
865
return NS_OK;
866
}
867
868
// nsNavHistory::CanAddURI
869
//
870
// Filter out unwanted URIs such as "chrome:", "mailbox:", etc.
871
//
872
// The model is if we don't know differently then add which basically means
873
// we are suppose to try all the things we know not to allow in and then if
874
// we don't bail go on and allow it in.
875
876
NS_IMETHODIMP
877
nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) {
878
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
879
NS_ENSURE_ARG(aURI);
880
NS_ENSURE_ARG_POINTER(canAdd);
881
882
// Default to false.
883
*canAdd = false;
884
885
// If history is disabled, don't add any entry.
886
if (IsHistoryDisabled()) {
887
return NS_OK;
888
}
889
890
// If the url length is over a threshold, don't add it.
891
nsCString spec;
892
nsresult rv = aURI->GetSpec(spec);
893
NS_ENSURE_SUCCESS(rv, rv);
894
if (!mDB || spec.Length() > mDB->MaxUrlLength()) {
895
return NS_OK;
896
}
897
898
nsAutoCString scheme;
899
rv = aURI->GetScheme(scheme);
900
NS_ENSURE_SUCCESS(rv, rv);
901
902
// first check the most common cases (HTTP, HTTPS) to allow in to avoid most
903
// of the work
904
if (scheme.EqualsLiteral("http")) {
905
*canAdd = true;
906
return NS_OK;
907
}
908
if (scheme.EqualsLiteral("https")) {
909
*canAdd = true;
910
return NS_OK;
911
}
912
913
// now check for all bad things
914
if (scheme.EqualsLiteral("about") || scheme.EqualsLiteral("blob") ||
915
scheme.EqualsLiteral("chrome") || scheme.EqualsLiteral("data") ||
916
scheme.EqualsLiteral("imap") || scheme.EqualsLiteral("javascript") ||
917
scheme.EqualsLiteral("mailbox") || scheme.EqualsLiteral("moz-anno") ||
918
scheme.EqualsLiteral("news") || scheme.EqualsLiteral("page-icon") ||
919
scheme.EqualsLiteral("resource") || scheme.EqualsLiteral("view-source")) {
920
return NS_OK;
921
}
922
*canAdd = true;
923
return NS_OK;
924
}
925
926
// nsNavHistory::GetNewQuery
927
928
NS_IMETHODIMP
929
nsNavHistory::GetNewQuery(nsINavHistoryQuery** _retval) {
930
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
931
NS_ENSURE_ARG_POINTER(_retval);
932
933
RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
934
query.forget(_retval);
935
return NS_OK;
936
}
937
938
// nsNavHistory::GetNewQueryOptions
939
940
NS_IMETHODIMP
941
nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions** _retval) {
942
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
943
NS_ENSURE_ARG_POINTER(_retval);
944
945
RefPtr<nsNavHistoryQueryOptions> queryOptions =
946
new nsNavHistoryQueryOptions();
947
queryOptions.forget(_retval);
948
return NS_OK;
949
}
950
951
NS_IMETHODIMP
952
nsNavHistory::ExecuteQuery(nsINavHistoryQuery* aQuery,
953
nsINavHistoryQueryOptions* aOptions,
954
nsINavHistoryResult** _retval) {
955
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
956
NS_ENSURE_ARG(aQuery);
957
NS_ENSURE_ARG(aOptions);
958
NS_ENSURE_ARG_POINTER(_retval);
959
960
// Clone the input query and options, because the caller might change the
961
// objects, but we always want to reflect the original parameters.
962
nsCOMPtr<nsINavHistoryQuery> queryClone;
963
aQuery->Clone(getter_AddRefs(queryClone));
964
NS_ENSURE_STATE(queryClone);
965
RefPtr<nsNavHistoryQuery> query = do_QueryObject(queryClone);
966
NS_ENSURE_STATE(query);
967
nsCOMPtr<nsINavHistoryQueryOptions> optionsClone;
968
aOptions->Clone(getter_AddRefs(optionsClone));
969
NS_ENSURE_STATE(optionsClone);
970
RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(optionsClone);
971
NS_ENSURE_STATE(options);
972
973
// Create the root node.
974
RefPtr<nsNavHistoryContainerResultNode> rootNode;
975
976
nsCString folderGuid = GetSimpleBookmarksQueryParent(query, options);
977
if (!folderGuid.IsEmpty()) {
978
// In the simple case where we're just querying children of a single
979
// bookmark folder, we can more efficiently generate results.
980
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
981
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
982
RefPtr<nsNavHistoryResultNode> tempRootNode;
983
nsresult rv = bookmarks->ResultNodeForContainer(
984
folderGuid, options, getter_AddRefs(tempRootNode));
985
if (NS_SUCCEEDED(rv)) {
986
rootNode = tempRootNode->GetAsContainer();
987
} else {
988
NS_WARNING("Generating a generic empty node for a broken query!");
989
// This is a perf hack to generate an empty query that skips filtering.
990
options->SetExcludeItems(true);
991
}
992
}
993
994
if (!rootNode) {
995
// Either this is not a folder shortcut, or is a broken one. In both cases
996
// just generate a query node.
997
nsAutoCString queryUri;
998
nsresult rv = QueryToQueryString(query, options, queryUri);
999
NS_ENSURE_SUCCESS(rv, rv);
1000
rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), 0, queryUri,
1001
query, options);
1002
}
1003
1004
// Create the result that will hold nodes. Inject batching status into it.
1005
RefPtr<nsNavHistoryResult> result =
1006
new nsNavHistoryResult(rootNode, query, options);
1007
result.forget(_retval);
1008
return NS_OK;
1009
}
1010
1011
// determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions
1012
// if this is the place query from the history menu.
1013
// from browser-menubar.inc, our history menu query is:
1014
// place:sort=4&maxResults=10
1015
// note, any maxResult > 0 will still be considered a history menu query
1016
// or if this is the place query from the old "Most Visited" item in some
1017
// profiles: folder: place:sort=8&maxResults=10 note, any maxResult > 0 will
1018
// still be considered a Most Visited menu query
1019
static bool IsOptimizableHistoryQuery(
1020
const RefPtr<nsNavHistoryQuery>& aQuery,
1021
const RefPtr<nsNavHistoryQueryOptions>& aOptions, uint16_t aSortMode) {
1022
if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1023
return false;
1024
1025
if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
1026
return false;
1027
1028
if (aOptions->SortingMode() != aSortMode) return false;
1029
1030
if (aOptions->MaxResults() <= 0) return false;
1031
1032
if (aOptions->ExcludeItems()) return false;
1033
1034
if (aOptions->IncludeHidden()) return false;
1035
1036
if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) return false;
1037
1038
if (aQuery->BeginTime() || aQuery->BeginTimeReference()) return false;
1039
1040
if (aQuery->EndTime() || aQuery->EndTimeReference()) return false;
1041
1042
if (!aQuery->SearchTerms().IsEmpty()) return false;
1043
1044
if (aQuery->OnlyBookmarked()) return false;
1045
1046
if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) return false;
1047
1048
if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
1049
return false;
1050
1051
if (aQuery->Parents().Length() > 0) return false;
1052
1053
if (aQuery->Tags().Length() > 0) return false;
1054
1055
if (aQuery->Transitions().Length() > 0) return false;
1056
1057
return true;
1058
}
1059
1060
static bool NeedToFilterResultSet(const RefPtr<nsNavHistoryQuery>& aQuery,
1061
nsNavHistoryQueryOptions* aOptions) {
1062
return aOptions->ExcludeQueries();
1063
}
1064
1065
// ** Helper class for ConstructQueryString **/
1066
1067
class PlacesSQLQueryBuilder {
1068
public:
1069
PlacesSQLQueryBuilder(const nsCString& aConditions,
1070
const RefPtr<nsNavHistoryQuery>& aQuery,
1071
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
1072
bool aUseLimit, nsNavHistory::StringHash& aAddParams,
1073
bool aHasSearchTerms);
1074
1075
nsresult GetQueryString(nsCString& aQueryString);
1076
1077
private:
1078
nsresult Select();
1079
1080
nsresult SelectAsURI();
1081
nsresult SelectAsVisit();
1082
nsresult SelectAsDay();
1083
nsresult SelectAsSite();
1084
nsresult SelectAsTag();
1085
nsresult SelectAsRoots();
1086
nsresult SelectAsLeftPane();
1087
1088
nsresult Where();
1089
nsresult GroupBy();
1090
nsresult OrderBy();
1091
nsresult Limit();
1092
1093
void OrderByColumnIndexAsc(int32_t aIndex);
1094
void OrderByColumnIndexDesc(int32_t aIndex);
1095
// Use these if you want a case insensitive sorting.
1096
void OrderByTextColumnIndexAsc(int32_t aIndex);
1097
void OrderByTextColumnIndexDesc(int32_t aIndex);
1098
1099
const nsCString& mConditions;
1100
bool mUseLimit;
1101
bool mHasSearchTerms;
1102
1103
uint16_t mResultType;
1104
uint16_t mQueryType;
1105
bool mIncludeHidden;
1106
uint16_t mSortingMode;
1107
uint32_t mMaxResults;
1108
1109
nsCString mQueryString;
1110
nsCString mGroupBy;
1111
bool mHasDateColumns;
1112
bool mSkipOrderBy;
1113
1114
nsNavHistory::StringHash& mAddParams;
1115
};
1116
1117
PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
1118
const nsCString& aConditions, const RefPtr<nsNavHistoryQuery>& aQuery,
1119
const RefPtr<nsNavHistoryQueryOptions>& aOptions, bool aUseLimit,
1120
nsNavHistory::StringHash& aAddParams, bool aHasSearchTerms)
1121
: mConditions(aConditions),
1122
mUseLimit(aUseLimit),
1123
mHasSearchTerms(aHasSearchTerms),
1124
mResultType(aOptions->ResultType()),
1125
mQueryType(aOptions->QueryType()),
1126
mIncludeHidden(aOptions->IncludeHidden()),
1127
mSortingMode(aOptions->SortingMode()),
1128
mMaxResults(aOptions->MaxResults()),
1129
mSkipOrderBy(false),
1130
mAddParams(aAddParams) {
1131
mHasDateColumns =
1132
(mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
1133
// Force the default sorting mode for tag queries.
1134
if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
1135
aQuery->Tags().Length() > 0) {
1136
mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1137
}
1138
}
1139
1140
nsresult PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) {
1141
nsresult rv = Select();
1142
NS_ENSURE_SUCCESS(rv, rv);
1143
rv = Where();
1144
NS_ENSURE_SUCCESS(rv, rv);
1145
rv = GroupBy();
1146
NS_ENSURE_SUCCESS(rv, rv);
1147
rv = OrderBy();
1148
NS_ENSURE_SUCCESS(rv, rv);
1149
rv = Limit();
1150
NS_ENSURE_SUCCESS(rv, rv);
1151
1152
aQueryString = mQueryString;
1153
return NS_OK;
1154
}
1155
1156
nsresult PlacesSQLQueryBuilder::Select() {
1157
nsresult rv;
1158
1159
switch (mResultType) {
1160
case nsINavHistoryQueryOptions::RESULTS_AS_URI:
1161
rv = SelectAsURI();
1162
NS_ENSURE_SUCCESS(rv, rv);
1163
break;
1164
1165
case nsINavHistoryQueryOptions::RESULTS_AS_VISIT:
1166
rv = SelectAsVisit();
1167
NS_ENSURE_SUCCESS(rv, rv);
1168
break;
1169
1170
case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY:
1171
case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY:
1172
rv = SelectAsDay();
1173
NS_ENSURE_SUCCESS(rv, rv);
1174
break;
1175
1176
case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY:
1177
rv = SelectAsSite();
1178
NS_ENSURE_SUCCESS(rv, rv);
1179
break;
1180
1181
case nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT:
1182
rv = SelectAsTag();
1183
NS_ENSURE_SUCCESS(rv, rv);
1184
break;
1185
1186
case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
1187
rv = SelectAsRoots();
1188
NS_ENSURE_SUCCESS(rv, rv);
1189
break;
1190
1191
case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
1192
rv = SelectAsLeftPane();
1193
NS_ENSURE_SUCCESS(rv, rv);
1194
break;
1195
1196
default:
1197
MOZ_ASSERT_UNREACHABLE("Invalid result type");
1198
}
1199
return NS_OK;
1200
}
1201
1202
nsresult PlacesSQLQueryBuilder::SelectAsURI() {
1203
nsNavHistory* history = nsNavHistory::GetHistoryService();
1204
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1205
nsAutoCString tagsSqlFragment;
1206
1207
switch (mQueryType) {
1208
case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
1209
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
1210
mHasSearchTerms, tagsSqlFragment);
1211
1212
mQueryString = NS_LITERAL_CSTRING(
1213
"SELECT h.id, h.url, h.title AS page_title, "
1214
"h.rev_host, h.visit_count, "
1215
"h.last_visit_date, null, null, null, null, null, ") +
1216
tagsSqlFragment +
1217
NS_LITERAL_CSTRING(
1218
", h.frecency, h.hidden, h.guid, "
1219
"null, null, null "
1220
"FROM moz_places h "
1221
// WHERE 1 is a no-op since additonal conditions will
1222
// start with AND.
1223
"WHERE 1 "
1224
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1225
"{ADDITIONAL_CONDITIONS} ");
1226
break;
1227
1228
case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
1229
1230
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("b.fk"),
1231
mHasSearchTerms, tagsSqlFragment);
1232
mQueryString =
1233
NS_LITERAL_CSTRING(
1234
"SELECT b.fk, h.url, b.title AS page_title, "
1235
"h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
1236
"b.dateAdded, b.lastModified, b.parent, ") +
1237
tagsSqlFragment +
1238
NS_LITERAL_CSTRING(
1239
", h.frecency, h.hidden, h.guid,"
1240
"null, null, null, b.guid, b.position, b.type, b.fk "
1241
"FROM moz_bookmarks b "
1242
"JOIN moz_places h ON b.fk = h.id "
1243
"WHERE NOT EXISTS "
1244
"(SELECT id FROM moz_bookmarks "
1245
"WHERE id = b.parent AND parent = ") +
1246
nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
1247
NS_LITERAL_CSTRING(
1248
") "
1249
"AND NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
1250
"hash('place', 'prefix_hi') "
1251
"{ADDITIONAL_CONDITIONS}");
1252
break;
1253
1254
default:
1255
return NS_ERROR_NOT_IMPLEMENTED;
1256
}
1257
return NS_OK;
1258
}
1259
1260
nsresult PlacesSQLQueryBuilder::SelectAsVisit() {
1261
nsNavHistory* history = nsNavHistory::GetHistoryService();
1262
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1263
nsAutoCString tagsSqlFragment;
1264
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
1265
mHasSearchTerms, tagsSqlFragment);
1266
mQueryString =
1267
NS_LITERAL_CSTRING(
1268
"SELECT h.id, h.url, h.title AS page_title, h.rev_host, "
1269
"h.visit_count, "
1270
"v.visit_date, null, null, null, null, null, ") +
1271
tagsSqlFragment +
1272
NS_LITERAL_CSTRING(
1273
", h.frecency, h.hidden, h.guid, "
1274
"v.id, v.from_visit, v.visit_type "
1275
"FROM moz_places h "
1276
"JOIN moz_historyvisits v ON h.id = v.place_id "
1277
// WHERE 1 is a no-op since additonal conditions will start with AND.
1278
"WHERE 1 "
1279
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
1280
"{ADDITIONAL_CONDITIONS} ");
1281
1282
return NS_OK;
1283
}
1284
1285
nsresult PlacesSQLQueryBuilder::SelectAsDay() {
1286
mSkipOrderBy = true;
1287
1288
// Sort child queries based on sorting mode if it's provided, otherwise
1289
// fallback to default sort by title ascending.
1290
uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
1291
if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE &&
1292
mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY)
1293
sortingMode = mSortingMode;
1294
1295
uint16_t resultType =
1296
mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY
1297
? (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI
1298
: (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
1299
1300
// beginTime will become the node's time property, we don't use endTime
1301
// because it could overlap, and we use time to sort containers and find
1302
// insert position in a result.
1303
mQueryString = nsPrintfCString(
1304
"SELECT null, "
1305
"'place:type=%d&sort=%d&beginTime='||beginTime||'&endTime='||endTime, "
1306
"dayTitle, null, null, beginTime, null, null, null, null, null, null, "
1307
"null, null, null "
1308
"FROM (", // TOUTER BEGIN
1309
resultType, sortingMode);
1310
1311
nsNavHistory* history = nsNavHistory::GetHistoryService();
1312
NS_ENSURE_STATE(history);
1313
1314
int32_t daysOfHistory = history->GetDaysOfHistory();
1315
for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) {
1316
nsAutoCString dateName;
1317
// Timeframes are calculated as BeginTime <= container < EndTime.
1318
// Notice times can't be relative to now, since to recognize a query we
1319
// must ensure it won't change based on the time it is built.
1320
// So, to select till now, we really select till start of tomorrow, that is
1321
// a fixed timestamp.
1322
// These are used as limits for the inside containers.
1323
nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime;
1324
// These are used to query if the container should be visible.
1325
nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime;
1326
switch (i) {
1327
case 0:
1328
// Today
1329
history->GetStringFromName("finduri-AgeInDays-is-0", dateName);
1330
// From start of today
1331
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1332
"(strftime('%s','now','localtime','start of day','utc')*1000000)");
1333
// To now (tomorrow)
1334
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1335
"(strftime('%s','now','localtime','start of day','+1 "
1336
"day','utc')*1000000)");
1337
// Search for the same timeframe.
1338
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1339
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1340
break;
1341
case 1:
1342
// Yesterday
1343
history->GetStringFromName("finduri-AgeInDays-is-1", dateName);
1344
// From start of yesterday
1345
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1346
"(strftime('%s','now','localtime','start of day','-1 "
1347
"day','utc')*1000000)");
1348
// To start of today
1349
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1350
"(strftime('%s','now','localtime','start of day','utc')*1000000)");
1351
// Search for the same timeframe.
1352
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1353
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1354
break;
1355
case 2:
1356
// Last 7 days
1357
history->GetAgeInDaysString(7, "finduri-AgeInDays-last-is", dateName);
1358
// From start of 7 days ago
1359
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1360
"(strftime('%s','now','localtime','start of day','-7 "
1361
"days','utc')*1000000)");
1362
// To now (tomorrow)
1363
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1364
"(strftime('%s','now','localtime','start of day','+1 "
1365
"day','utc')*1000000)");
1366
// This is an overlapped container, but we show it only if there are
1367
// visits older than yesterday.
1368
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1369
sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1370
"(strftime('%s','now','localtime','start of day','-1 "
1371
"day','utc')*1000000)");
1372
break;
1373
case 3:
1374
// This month
1375
history->GetStringFromName("finduri-AgeInMonths-is-0", dateName);
1376
// From start of this month
1377
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1378
"(strftime('%s','now','localtime','start of "
1379
"month','utc')*1000000)");
1380
// To now (tomorrow)
1381
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1382
"(strftime('%s','now','localtime','start of day','+1 "
1383
"day','utc')*1000000)");
1384
// This is an overlapped container, but we show it only if there are
1385
// visits older than 7 days ago.
1386
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1387
sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
1388
"(strftime('%s','now','localtime','start of day','-7 "
1389
"days','utc')*1000000)");
1390
break;
1391
default:
1392
if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) {
1393
// Older than 6 months
1394
history->GetAgeInDaysString(6, "finduri-AgeInMonths-isgreater",
1395
dateName);
1396
// From start of epoch
1397
sqlFragmentContainerBeginTime =
1398
NS_LITERAL_CSTRING("(datetime(0, 'unixepoch')*1000000)");
1399
// To start of 6 months ago ( 5 months + this month).
1400
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1401
"(strftime('%s','now','localtime','start of month','-5 "
1402
"months','utc')*1000000)");
1403
// Search for the same timeframe.
1404
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1405
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1406
break;
1407
}
1408
int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM;
1409
// Previous months' titles are month's name if inside this year,
1410
// month's name and year for previous years.
1411
PRExplodedTime tm;
1412
PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm);
1413
uint16_t currentYear = tm.tm_year;
1414
// Set day before month, setting month without day could cause issues.
1415
// For example setting month to February when today is 30, since
1416
// February has not 30 days, will return March instead.
1417
// Also, we use day 2 instead of day 1, so that the GMT month is always
1418
// the same as the local month. (Bug 603002)
1419
tm.tm_mday = 2;
1420
tm.tm_month -= MonthIndex;
1421
// Notice we use GMTParameters because we just want to get the first
1422
// day of each month. Using LocalTimeParameters would instead force us
1423
// to apply a DST correction that we don't really need here.
1424
PR_NormalizeTime(&tm, PR_GMTParameters);
1425
// If the container is for a past year, add the year to its title,
1426
// otherwise just show the month name.
1427
if (tm.tm_year < currentYear) {
1428
nsNavHistory::GetMonthYear(tm, dateName);
1429
} else {
1430
nsNavHistory::GetMonthName(tm, dateName);
1431
}
1432
1433
// From start of MonthIndex + 1 months ago
1434
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
1435
"(strftime('%s','now','localtime','start of month','-");
1436
sqlFragmentContainerBeginTime.AppendInt(MonthIndex);
1437
sqlFragmentContainerBeginTime.AppendLiteral(" months','utc')*1000000)");
1438
// To start of MonthIndex months ago
1439
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
1440
"(strftime('%s','now','localtime','start of month','-");
1441
sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1);
1442
sqlFragmentContainerEndTime.AppendLiteral(" months','utc')*1000000)");
1443
// Search for the same timeframe.
1444
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
1445
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
1446
break;
1447
}
1448
1449
nsPrintfCString dateParam("dayTitle%d", i);
1450
mAddParams.Put(dateParam, dateName);
1451
1452
nsPrintfCString dayRange(
1453
"SELECT :%s AS dayTitle, "
1454
"%s AS beginTime, "
1455
"%s AS endTime "
1456
"WHERE EXISTS ( "
1457
"SELECT id FROM moz_historyvisits "
1458
"WHERE visit_date >= %s "
1459
"AND visit_date < %s "
1460
"AND visit_type NOT IN (0,%d,%d) "
1461
"{QUERY_OPTIONS_VISITS} "
1462
"LIMIT 1 "
1463
") ",
1464
dateParam.get(), sqlFragmentContainerBeginTime.get(),
1465
sqlFragmentContainerEndTime.get(), sqlFragmentSearchBeginTime.get(),
1466
sqlFragmentSearchEndTime.get(), nsINavHistoryService::TRANSITION_EMBED,
1467
nsINavHistoryService::TRANSITION_FRAMED_LINK);
1468
1469
mQueryString.Append(dayRange);
1470
1471
if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
1472
mQueryString.AppendLiteral(" UNION ALL ");
1473
}
1474
1475
mQueryString.AppendLiteral(") "); // TOUTER END
1476
1477
return NS_OK;
1478
}
1479
1480
nsresult PlacesSQLQueryBuilder::SelectAsSite() {
1481
nsAutoCString localFiles;
1482
1483
nsNavHistory* history = nsNavHistory::GetHistoryService();
1484
NS_ENSURE_STATE(history);
1485
1486
history->GetStringFromName("localhost", localFiles);
1487
mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles);
1488
1489
// If there are additional conditions the query has to join on visits too.
1490
nsAutoCString visitsJoin;
1491
nsAutoCString additionalConditions;
1492
nsAutoCString timeConstraints;
1493
if (!mConditions.IsEmpty()) {
1494
visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id ");
1495
additionalConditions.AssignLiteral(
1496
"{QUERY_OPTIONS_VISITS} "
1497
"{QUERY_OPTIONS_PLACES} "
1498
"{ADDITIONAL_CONDITIONS} ");
1499
timeConstraints.AssignLiteral(
1500
"||'&beginTime='||:begin_time||"
1501
"'&endTime='||:end_time");
1502
}
1503
1504
mQueryString = nsPrintfCString(
1505
"SELECT null, 'place:type=%d&sort=%d&domain=&domainIsHost=true'%s, "
1506
":localhost, :localhost, null, null, null, null, null, null, null, "
1507
"null, null, null "
1508
"WHERE EXISTS ( "
1509
"SELECT h.id FROM moz_places h "
1510
"%s "
1511
"WHERE h.hidden = 0 "
1512
"AND h.visit_count > 0 "
1513
"AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
1514
"hash('file', 'prefix_hi') "
1515
"%s "
1516
"LIMIT 1 "
1517
") "
1518
"UNION ALL "
1519
"SELECT null, "
1520
"'place:type=%d&sort=%d&domain='||host||'&domainIsHost=true'%s, "
1521
"host, host, null, null, null, null, null, null, null, "
1522
"null, null, null "
1523
"FROM ( "
1524
"SELECT get_unreversed_host(h.rev_host) AS host "
1525
"FROM moz_places h "
1526
"%s "
1527
"WHERE h.hidden = 0 "
1528
"AND h.rev_host <> '.' "
1529
"AND h.visit_count > 0 "
1530
"%s "
1531
"GROUP BY h.rev_host "
1532
"ORDER BY host ASC "
1533
") ",
1534
nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
1535
timeConstraints.get(), visitsJoin.get(), additionalConditions.get(),
1536
nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
1537
timeConstraints.get(), visitsJoin.get(), additionalConditions.get());
1538
1539
return NS_OK;
1540
}
1541
1542
nsresult PlacesSQLQueryBuilder::SelectAsTag() {
1543
nsNavHistory* history = nsNavHistory::GetHistoryService();
1544
NS_ENSURE_STATE(history);
1545
1546
// This allows sorting by date fields what is not possible with
1547
// other history queries.
1548
mHasDateColumns = true;
1549
1550
// TODO (Bug 1449939): This is likely wrong, since the tag name should
1551
// probably be urlencoded, and we have no util for that in SQL, yet.
1552
// We could encode the tag when the user sets it though.
1553
mQueryString = nsPrintfCString(
1554
"SELECT null, 'place:tag=' || title, "
1555
"title, null, null, null, null, null, dateAdded, "
1556
"lastModified, null, null, null, null, null, null "
1557
"FROM moz_bookmarks "
1558
"WHERE parent = %" PRId64,
1559
history->GetTagsFolder());
1560
1561
return NS_OK;
1562
}
1563
1564
nsresult PlacesSQLQueryBuilder::SelectAsRoots() {
1565
nsNavHistory* history = nsNavHistory::GetHistoryService();
1566
NS_ENSURE_STATE(history);
1567
1568
nsAutoCString toolbarTitle;
1569
nsAutoCString menuTitle;
1570
nsAutoCString unfiledTitle;
1571
1572
history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
1573
mAddParams.Put(NS_LITERAL_CSTRING("BookmarksToolbarFolderTitle"),
1574
toolbarTitle);
1575
history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
1576
mAddParams.Put(NS_LITERAL_CSTRING("BookmarksMenuFolderTitle"), menuTitle);
1577
history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
1578
mAddParams.Put(NS_LITERAL_CSTRING("OtherBookmarksFolderTitle"), unfiledTitle);
1579
1580
nsAutoCString mobileString;
1581
1582
if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
1583
nsAutoCString mobileTitle;
1584
history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
1585
mAddParams.Put(NS_LITERAL_CSTRING("MobileBookmarksFolderTitle"),
1586
mobileTitle);
1587
1588
mobileString = NS_LITERAL_CSTRING(
1589
","
1590
"(null, 'place:parent=" MOBILE_ROOT_GUID
1591
"', :MobileBookmarksFolderTitle, null, null, null, "
1592
"null, null, 0, 0, null, null, null, null, "
1593
"'" MOBILE_BOOKMARKS_VIRTUAL_GUID "', null) ");
1594
}
1595
1596
mQueryString =
1597
NS_LITERAL_CSTRING(
1598
"SELECT * FROM ("
1599
"VALUES(null, 'place:parent=" TOOLBAR_ROOT_GUID
1600
"', :BookmarksToolbarFolderTitle, null, null, null, "
1601
"null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
1602
"(null, 'place:parent=" MENU_ROOT_GUID
1603
"', :BookmarksMenuFolderTitle, null, null, null, "
1604
"null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
1605
"(null, 'place:parent=" UNFILED_ROOT_GUID
1606
"', :OtherBookmarksFolderTitle, null, null, null, "
1607
"null, null, 0, 0, null, null, null, null, 'unfiled____v', null) ") +
1608
mobileString + NS_LITERAL_CSTRING(")");
1609
1610
return NS_OK;
1611
}
1612
1613
nsresult PlacesSQLQueryBuilder::SelectAsLeftPane() {
1614
nsNavHistory* history = nsNavHistory::GetHistoryService();
1615
NS_ENSURE_STATE(history);
1616
1617
nsAutoCString historyTitle;
1618
nsAutoCString downloadsTitle;
1619
nsAutoCString tagsTitle;
1620
nsAutoCString allBookmarksTitle;
1621
1622
history->GetStringFromName("OrganizerQueryHistory", historyTitle);
1623
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryHistory"), historyTitle);
1624
history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
1625
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryDownloads"), downloadsTitle);
1626
history->GetStringFromName("TagsFolderTitle", tagsTitle);
1627
mAddParams.Put(NS_LITERAL_CSTRING("TagsFolderTitle"), tagsTitle);
1628
history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
1629
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryAllBookmarks"),
1630
allBookmarksTitle);
1631
1632
mQueryString = nsPrintfCString(
1633
"SELECT * FROM ("
1634
"VALUES"
1635
"(null, 'place:type=%d&sort=%d', :OrganizerQueryHistory, null, null, "
1636
"null, "
1637
"null, null, 0, 0, null, null, null, null, 'history____v', null), "
1638
"(null, 'place:transition=%d&sort=%d', :OrganizerQueryDownloads, null, "
1639
"null, null, "
1640
"null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
1641
"(null, 'place:type=%d&sort=%d', :TagsFolderTitle, null, null, null, "
1642
"null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
1643
"(null, 'place:type=%d', :OrganizerQueryAllBookmarks, null, null, null, "
1644
"null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
1645
")",
1646
nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
1647
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1648
nsINavHistoryService::TRANSITION_DOWNLOAD,
1649
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
1650
nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT,
1651
nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
1652
nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY);
1653
return NS_OK;
1654
}
1655
1656
nsresult PlacesSQLQueryBuilder::Where() {
1657
// Set query options
1658
nsAutoCString additionalVisitsConditions;
1659
nsAutoCString additionalPlacesConditions;
1660
1661
if (!mIncludeHidden) {
1662
additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 ");
1663
}
1664
1665
if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
1666
// last_visit_date is updated for any kind of visit, so it's a good
1667
// indicator whether the page has visits.
1668
additionalPlacesConditions +=
1669
NS_LITERAL_CSTRING("AND last_visit_date NOTNULL ");
1670
}
1671
1672
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI &&
1673
!additionalVisitsConditions.IsEmpty()) {
1674
// URI results don't join on visits.
1675
nsAutoCString tmp = additionalVisitsConditions;
1676
additionalVisitsConditions =
1677
"AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id ";
1678
additionalVisitsConditions.Append(tmp);
1679
additionalVisitsConditions.AppendLiteral("LIMIT 1)");
1680
}
1681
1682
mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
1683
additionalVisitsConditions.get());
1684
mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
1685
additionalPlacesConditions.get());
1686
1687
// If we used WHERE already, we inject the conditions
1688
// in place of {ADDITIONAL_CONDITIONS}
1689
if (mQueryString.Find("{ADDITIONAL_CONDITIONS}") != kNotFound) {
1690
nsAutoCString innerCondition;
1691
// If we have condition AND it
1692
if (!mConditions.IsEmpty()) {
1693
innerCondition = " AND (";
1694
innerCondition += mConditions;
1695
innerCondition += ")";
1696
}
1697
mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}",
1698
innerCondition.get());
1699
1700
} else if (!mConditions.IsEmpty()) {
1701
mQueryString += "WHERE ";
1702
mQueryString += mConditions;
1703
}
1704
return NS_OK;
1705
}
1706
1707
nsresult PlacesSQLQueryBuilder::GroupBy() {
1708
mQueryString += mGroupBy;
1709
return NS_OK;
1710
}
1711
1712
nsresult PlacesSQLQueryBuilder::OrderBy() {
1713
if (mSkipOrderBy) return NS_OK;
1714
1715
// Sort clause: we will sort later, but if it comes out of the DB sorted,
1716
// our later sort will be basically free. The DB can sort these for free
1717
// most of the time anyway, because it has indices over these items.
1718
switch (mSortingMode) {
1719
case nsINavHistoryQueryOptions::SORT_BY_NONE:
1720
// Ensure sorting does not change based on tables status.
1721
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
1722
if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
1723
mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
1724
else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
1725
mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
1726
}
1727
break;
1728
case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
1729
case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
1730
// If the user wants few results, we limit them by date, necessitating
1731
// a sort by date here (see the IDL definition for maxResults).
1732
// Otherwise we will do actual sorting by title, but since we could need
1733
// to special sort for some locale we will repeat a second sorting at the
1734
// end in nsNavHistoryResult, that should be faster since the list will be
1735
// almost ordered.
1736
if (mMaxResults > 0)
1737
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1738
else if (mSortingMode ==
1739
nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING)
1740
OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title);
1741
else
1742
OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title);
1743
break;
1744
case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
1745
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate);
1746
break;
1747
case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
1748
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
1749
break;
1750
case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
1751
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL);
1752
break;
1753
case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
1754
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL);
1755
break;
1756
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
1757
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount);
1758
break;
1759
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
1760
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount);
1761
break;
1762
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
1763
if (mHasDateColumns)
1764
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1765
break;
1766
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
1767
if (mHasDateColumns)
1768
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
1769
break;
1770
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
1771
if (mHasDateColumns)
1772
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1773
break;
1774
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
1775
if (mHasDateColumns)
1776
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified);
1777
break;
1778
case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
1779
case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
1780
break; // Sort later in nsNavHistoryQueryResultNode::FillChildren()
1781
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
1782
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency);
1783
break;
1784
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
1785
OrderByColumnIndexDes