Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <stdio.h>
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "nsNavHistory.h"
#include "mozIPlacesAutoComplete.h"
#include "nsNavBookmarks.h"
#include "nsAnnotationService.h"
#include "nsFaviconService.h"
#include "nsPlacesMacros.h"
#include "nsPlacesTriggers.h"
#include "DateTimeFormat.h"
#include "History.h"
#include "Helpers.h"
#include "nsTArray.h"
#include "nsCollationCID.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsPromiseFlatString.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "prsystem.h"
#include "prtime.h"
#include "nsEscape.h"
#include "nsIEffectiveTLDService.h"
#include "nsIClassInfoImpl.h"
#include "nsIIDNService.h"
#include "nsQueryObject.h"
#include "nsThreadUtils.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsMathUtils.h"
#include "mozilla/storage.h"
#include "mozilla/Preferences.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::places;
// The maximum number of things that we will store in the recent events list
// before calling ExpireNonrecentEvents. This number should be big enough so it
// is very difficult to get that many unconsumed events (for example, typed but
// never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
// checking each one for every page visit, which will be somewhat slower.
#define RECENT_EVENT_QUEUE_MAX_LENGTH 128
// preference ID strings
#define PREF_HISTORY_ENABLED "places.history.enabled"
#define PREF_MATCH_DIACRITICS "places.search.matchDiacritics"
#define PREF_FREC_NUM_VISITS "places.frecency.numVisits"
#define PREF_FREC_NUM_VISITS_DEF 10
#define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff"
#define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4
#define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff"
#define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14
#define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff"
#define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31
#define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff"
#define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90
#define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight"
#define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100
#define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight"
#define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70
#define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight"
#define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50
#define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight"
#define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight"
#define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10
#define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus"
#define PREF_FREC_EMBED_VISIT_BONUS_DEF 0
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus"
#define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0
#define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus"
#define PREF_FREC_LINK_VISIT_BONUS_DEF 100
#define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus"
#define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000
#define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus"
#define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75
#define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus"
#define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS \
"places.frecency.permRedirectVisitBonus"
#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS \
"places.frecency.tempRedirectVisitBonus"
#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS \
"places.frecency.redirectSourceVisitBonus"
#define PREF_FREC_REDIR_SOURCE_VISIT_BONUS_DEF 25
#define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus"
#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS \
"places.frecency.unvisitedBookmarkBonus"
#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140
#define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus"
#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200
#define PREF_FREC_RELOAD_VISIT_BONUS "places.frecency.reloadVisitBonus"
#define PREF_FREC_RELOAD_VISIT_BONUS_DEF 0
// This is a 'hidden' pref for the purposes of unit tests.
#define PREF_FREC_DECAY_RATE "places.frecency.decayRate"
#define PREF_FREC_DECAY_RATE_DEF 0.975f
// An adaptive history entry is removed if unused for these many days.
#define ADAPTIVE_HISTORY_EXPIRE_DAYS 90
// In order to avoid calling PR_now() too often we use a cached "now" value
// for repeating stuff. These are milliseconds between "now" cache refreshes.
#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
// character-set annotation
#define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet")
// These macros are used when splitting history by date.
// These are the day containers and catch-all final container.
#define HISTORY_ADDITIONAL_DATE_CONT_NUM 3
// We use a guess of the number of months considering all of them 30 days
// long, but we split only the last 6 months.
#define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \
(HISTORY_ADDITIONAL_DATE_CONT_NUM + \
std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit / 30)))
// Max number of containers, used to initialize the params hash.
#define HISTORY_DATE_CONT_LENGTH 8
// Initial length of the recent events cache.
#define RECENT_EVENTS_INITIAL_CACHE_LENGTH 64
// Observed topics.
#define TOPIC_IDLE_DAILY "idle-daily"
#define TOPIC_PREF_CHANGED "nsPref:changed"
#define TOPIC_PROFILE_TEARDOWN "profile-change-teardown"
#define TOPIC_PROFILE_CHANGE "profile-before-change"
static const char* kObservedPrefs[] = {PREF_HISTORY_ENABLED,
PREF_MATCH_DIACRITICS,
PREF_FREC_NUM_VISITS,
PREF_FREC_FIRST_BUCKET_CUTOFF,
PREF_FREC_SECOND_BUCKET_CUTOFF,
PREF_FREC_THIRD_BUCKET_CUTOFF,
PREF_FREC_FOURTH_BUCKET_CUTOFF,
PREF_FREC_FIRST_BUCKET_WEIGHT,
PREF_FREC_SECOND_BUCKET_WEIGHT,
PREF_FREC_THIRD_BUCKET_WEIGHT,
PREF_FREC_FOURTH_BUCKET_WEIGHT,
PREF_FREC_DEFAULT_BUCKET_WEIGHT,
PREF_FREC_EMBED_VISIT_BONUS,
PREF_FREC_FRAMED_LINK_VISIT_BONUS,
PREF_FREC_LINK_VISIT_BONUS,
PREF_FREC_TYPED_VISIT_BONUS,
PREF_FREC_BOOKMARK_VISIT_BONUS,
PREF_FREC_DOWNLOAD_VISIT_BONUS,
PREF_FREC_PERM_REDIRECT_VISIT_BONUS,
PREF_FREC_TEMP_REDIRECT_VISIT_BONUS,
PREF_FREC_REDIR_SOURCE_VISIT_BONUS,
PREF_FREC_DEFAULT_VISIT_BONUS,
PREF_FREC_UNVISITED_BOOKMARK_BONUS,
PREF_FREC_UNVISITED_TYPED_BONUS,
nullptr};
NS_IMPL_ADDREF(nsNavHistory)
NS_IMPL_RELEASE(nsNavHistory)
NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON,
NS_NAVHISTORYSERVICE_CID)
NS_INTERFACE_MAP_BEGIN(nsNavHistory)
NS_INTERFACE_MAP_ENTRY(nsINavHistoryService)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
NS_INTERFACE_MAP_END
// We don't care about flattening everything
NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, nsINavHistoryService)
namespace {
static nsCString GetSimpleBookmarksQueryParent(
const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions);
static void ParseSearchTermsFromQuery(const RefPtr<nsNavHistoryQuery>& aQuery,
nsTArray<nsString>* aTerms);
void GetTagsSqlFragment(int64_t aTagsFolder, const nsACString& aRelation,
bool aHasSearchTerms, nsACString& _sqlFragment) {
if (!aHasSearchTerms)
_sqlFragment.AssignLiteral("null");
else {
// This subquery DOES NOT order tags for performance reasons.
_sqlFragment.Assign(
NS_LITERAL_CSTRING("(SELECT GROUP_CONCAT(t_t.title, ',') "
"FROM moz_bookmarks b_t "
"JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent "
"WHERE b_t.fk = ") +
aRelation +
NS_LITERAL_CSTRING(" "
"AND t_t.parent = ") +
nsPrintfCString("%" PRId64, aTagsFolder) +
NS_LITERAL_CSTRING(" "
")"));
}
_sqlFragment.AppendLiteral(" AS tags ");
}
/**
* Recalculates invalid frecencies in chunks on the storage thread, optionally
* decays frecencies, and notifies history observers on the main thread.
*/
class FixAndDecayFrecencyRunnable final : public Runnable {
public:
explicit FixAndDecayFrecencyRunnable(Database* aDB, float aDecayRate)
: Runnable("places::FixAndDecayFrecencyRunnable"),
mDB(aDB),
mDecayRate(aDecayRate),
mDecayReason(mozIStorageStatementCallback::REASON_FINISHED) {}
NS_IMETHOD
Run() override {
if (NS_IsMainThread()) {
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(navHistory);
navHistory->DecayFrecencyCompleted(mDecayReason);
return NS_OK;
}
MOZ_ASSERT(!NS_IsMainThread(),
"Frecencies should be recalculated on async thread");
nsCOMPtr<mozIStorageStatement> updateStmt = mDB->GetStatement(
"UPDATE moz_places "
"SET frecency = CALCULATE_FRECENCY(id) "
"WHERE id IN ("
"SELECT id FROM moz_places "
"WHERE frecency < 0 "
"ORDER BY frecency ASC "
"LIMIT 400"
")");
NS_ENSURE_STATE(updateStmt);
nsresult rv = updateStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> selectStmt = mDB->GetStatement(
"SELECT id FROM moz_places WHERE frecency < 0 "
"LIMIT 1");
NS_ENSURE_STATE(selectStmt);
bool hasResult = false;
rv = selectStmt->ExecuteStep(&hasResult);
NS_ENSURE_SUCCESS(rv, rv);
if (hasResult) {
// There are more invalid frecencies to fix. Re-dispatch to the async
// storage thread for the next chunk.
return NS_DispatchToCurrentThread(this);
}
mozStorageTransaction transaction(
mDB->MainConn(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
if (NS_WARN_IF(NS_FAILED(DecayFrecencies()))) {
mDecayReason = mozIStorageStatementCallback::REASON_ERROR;
}
// We've finished fixing and decaying frecencies. Trigger frecency updates
// for all affected origins.
nsCOMPtr<mozIStorageStatement> updateOriginFrecenciesStmt =
mDB->GetStatement("DELETE FROM moz_updateoriginsupdate_temp");
NS_ENSURE_STATE(updateOriginFrecenciesStmt);
rv = updateOriginFrecenciesStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
// Re-dispatch to the main thread to notify observers.
return NS_DispatchToMainThread(this);
}
private:
nsresult DecayFrecencies() {
TimeStamp start = TimeStamp::Now();
// Globally decay places frecency rankings to estimate reduced frecency
// values of pages that haven't been visited for a while, i.e., they do
// not get an updated frecency. A scaling factor of .975 results in .5 the
// original value after 28 days.
// When changing the scaling factor, ensure that the barrier in
// moz_places_afterupdate_frecency_trigger still ignores these changes.
nsCOMPtr<mozIStorageStatement> decayFrecency = mDB->GetStatement(
"UPDATE moz_places SET frecency = ROUND(frecency * :decay_rate) "
"WHERE frecency > 0");
NS_ENSURE_STATE(decayFrecency);
nsresult rv = decayFrecency->BindDoubleByName(
NS_LITERAL_CSTRING("decay_rate"), static_cast<double>(mDecayRate));
NS_ENSURE_SUCCESS(rv, rv);
rv = decayFrecency->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Decay potentially unused adaptive entries (e.g. those that are at 1)
// to allow better chances for new entries that will start at 1.
nsCOMPtr<mozIStorageStatement> decayAdaptive = mDB->GetStatement(
"UPDATE moz_inputhistory SET use_count = use_count * :decay_rate");
NS_ENSURE_STATE(decayAdaptive);
rv = decayAdaptive->BindDoubleByName(NS_LITERAL_CSTRING("decay_rate"),
static_cast<double>(mDecayRate));
NS_ENSURE_SUCCESS(rv, rv);
rv = decayAdaptive->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Delete any adaptive entries that won't help in ordering anymore.
nsCOMPtr<mozIStorageStatement> deleteAdaptive = mDB->GetStatement(
"DELETE FROM moz_inputhistory WHERE use_count < :use_count");
NS_ENSURE_STATE(deleteAdaptive);
rv = deleteAdaptive->BindDoubleByName(
NS_LITERAL_CSTRING("use_count"),
std::pow(static_cast<double>(mDecayRate),
ADAPTIVE_HISTORY_EXPIRE_DAYS));
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteAdaptive->Execute();
NS_ENSURE_SUCCESS(rv, rv);
Telemetry::AccumulateTimeDelta(
Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS, start);
return NS_OK;
}
RefPtr<Database> mDB;
float mDecayRate;
uint16_t mDecayReason;
};
} // namespace
// Queries rows indexes to bind or get values, if adding a new one, be sure to
// update nsNavBookmarks statements and its kGetChildrenIndex_* constants
const int32_t nsNavHistory::kGetInfoIndex_PageID = 0;
const int32_t nsNavHistory::kGetInfoIndex_URL = 1;
const int32_t nsNavHistory::kGetInfoIndex_Title = 2;
const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3;
const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4;
const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5;
const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6;
const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
// These columns are followed by corresponding constants in nsNavBookmarks.cpp,
// which must be kept in sync:
// nsNavBookmarks::kGetChildrenIndex_Guid = 18;
// nsNavBookmarks::kGetChildrenIndex_Position = 19;
// nsNavBookmarks::kGetChildrenIndex_Type = 20;
// nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
nsNavHistory::nsNavHistory()
: mCachedNow(0),
mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH),
mHistoryEnabled(true),
mMatchDiacritics(false),
mNumVisitsForFrecency(10),
mDecayFrecencyPendingCount(0),
mTagsFolder(-1),
mDaysOfHistory(-1),
mLastCachedStartOfDay(INT64_MAX),
mLastCachedEndOfDay(0),
mCanNotify(true)
#ifdef XP_WIN
,
mCryptoProviderInitialized(false)
#endif
{
NS_ASSERTION(!gHistoryService,
"Attempting to create two instances of the service!");
#ifdef XP_WIN
BOOL cryptoAcquired =
CryptAcquireContext(&mCryptoProvider, 0, 0, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
if (cryptoAcquired) {
mCryptoProviderInitialized = true;
}
#endif
gHistoryService = this;
}
nsNavHistory::~nsNavHistory() {
MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
// remove the static reference to the service. Check to make sure its us
// in case somebody creates an extra instance of the service.
NS_ASSERTION(gHistoryService == this,
"Deleting a non-singleton instance of the service");
if (gHistoryService == this) gHistoryService = nullptr;
#ifdef XP_WIN
if (mCryptoProviderInitialized) {
Unused << CryptReleaseContext(mCryptoProvider, 0);
}
#endif
}
nsresult nsNavHistory::Init() {
LoadPrefs();
mDB = Database::GetDatabase();
NS_ENSURE_STATE(mDB);
/*****************************************************************************
*** IMPORTANT NOTICE!
***
*** Nothing after these add observer calls should return anything but NS_OK.
*** If a failure code is returned, this nsNavHistory object will be held onto
*** by the observer service and the preference service.
****************************************************************************/
// Observe preferences changes.
Preferences::AddWeakObservers(this, kObservedPrefs);
nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
if (obsSvc) {
(void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
(void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true);
}
// Don't add code that can fail here! Do it up above, before we add our
// observers.
return NS_OK;
}
NS_IMETHODIMP
nsNavHistory::GetDatabaseStatus(uint16_t* aDatabaseStatus) {
NS_ENSURE_ARG_POINTER(aDatabaseStatus);
*aDatabaseStatus = mDB->GetDatabaseStatus();
return NS_OK;
}
uint32_t nsNavHistory::GetRecentFlags(nsIURI* aURI) {
uint32_t result = 0;
nsAutoCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
if (NS_SUCCEEDED(rv)) {
if (CheckIsRecentEvent(&mRecentTyped, spec)) result |= RECENT_TYPED;
if (CheckIsRecentEvent(&mRecentLink, spec)) result |= RECENT_ACTIVATED;
if (CheckIsRecentEvent(&mRecentBookmark, spec)) result |= RECENT_BOOKMARKED;
}
return result;
}
nsresult nsNavHistory::GetIdForPage(nsIURI* aURI, int64_t* _pageId,
nsCString& _GUID) {
*_pageId = 0;
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT id, url, title, rev_host, visit_count, guid "
"FROM moz_places "
"WHERE url_hash = hash(:page_url) AND url = :page_url ");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
bool hasEntry = false;
rv = stmt->ExecuteStep(&hasEntry);
NS_ENSURE_SUCCESS(rv, rv);
if (hasEntry) {
rv = stmt->GetInt64(0, _pageId);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->GetUTF8String(5, _GUID);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, int64_t* _pageId,
nsCString& _GUID) {
nsresult rv = GetIdForPage(aURI, _pageId, _GUID);
NS_ENSURE_SUCCESS(rv, rv);
if (*_pageId != 0) {
return NS_OK;
}
{
// Create a new hidden, untyped and unvisited entry.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, "
"guid) "
"VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, "
":guid) ");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
// host (reversed with trailing period)
nsAutoString revHost;
rv = GetReversedHostname(aURI, revHost);
// Not all URI types have hostnames, so this is optional.
if (NS_SUCCEEDED(rv)) {
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
} else {
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host"));
}
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
IsQueryURI(spec) ? 0 : -1);
NS_ENSURE_SUCCESS(rv, rv);
rv = GenerateGUID(_GUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
*_pageId = sLastInsertedPlaceId;
}
{
// Trigger the updates to the moz_origins tables
nsCOMPtr<mozIStorageStatement> stmt =
mDB->GetStatement("DELETE FROM moz_updateoriginsinsert_temp");
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
}
return NS_OK;
}
void nsNavHistory::LoadPrefs() {
// History preferences.
mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true);
mMatchDiacritics = Preferences::GetBool(PREF_MATCH_DIACRITICS, false);
// Frecency preferences.
#define FRECENCY_PREF(_prop, _pref) \
_prop = Preferences::GetInt(_pref, _pref##_DEF)
FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS);
FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF);
FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF);
FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF);
FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF);
FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS);
FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS);
FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS);
FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS);
FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS);
FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS);
FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS);
FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS);
FRECENCY_PREF(mRedirectSourceVisitBonus, PREF_FREC_REDIR_SOURCE_VISIT_BONUS);
FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS);
FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS);
FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS);
FRECENCY_PREF(mReloadVisitBonus, PREF_FREC_RELOAD_VISIT_BONUS);
FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT);
FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT);
FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT);
FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT);
FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT);
#undef FRECENCY_PREF
}
void nsNavHistory::UpdateDaysOfHistory(PRTime visitTime) {
if (mDaysOfHistory == 0) {
mDaysOfHistory = 1;
}
if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
mDaysOfHistory = -1;
}
}
void nsNavHistory::NotifyTitleChange(nsIURI* aURI, const nsString& aTitle,
const nsACString& aGUID) {
MOZ_ASSERT(!aGUID.IsEmpty());
NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
OnTitleChanged(aURI, aTitle, aGUID));
}
void nsNavHistory::NotifyFrecencyChanged(const nsACString& aSpec,
int32_t aNewFrecency,
const nsACString& aGUID, bool aHidden,
PRTime aLastVisitDate) {
MOZ_ASSERT(!aGUID.IsEmpty());
nsCOMPtr<nsIURI> uri;
Unused << NS_NewURI(getter_AddRefs(uri), aSpec);
// We cannot assert since some automated tests are checking this path.
NS_WARNING_ASSERTION(uri,
"Invalid URI in nsNavHistory::NotifyFrecencyChanged");
// Notify a frecency change only if we have a valid uri, otherwise
// the observer couldn't gather any useful data from the notification.
if (!uri) {
return;
}
NOTIFY_OBSERVERS(
mCanNotify, mObservers, nsINavHistoryObserver,
OnFrecencyChanged(uri, aNewFrecency, aGUID, aHidden, aLastVisitDate));
}
void nsNavHistory::NotifyManyFrecenciesChanged() {
NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
OnManyFrecenciesChanged());
}
void nsNavHistory::DispatchFrecencyChangedNotification(
const nsACString& aSpec, int32_t aNewFrecency, const nsACString& aGUID,
bool aHidden, PRTime aLastVisitDate) const {
Unused << NS_DispatchToMainThread(
NewRunnableMethod<nsCString, int32_t, nsCString, bool, PRTime>(
"nsNavHistory::NotifyFrecencyChanged",
const_cast<nsNavHistory*>(this), &nsNavHistory::NotifyFrecencyChanged,
aSpec, aNewFrecency, aGUID, aHidden, aLastVisitDate));
}
NS_IMETHODIMP
nsNavHistory::RecalculateOriginFrecencyStats(nsIObserver* aCallback) {
RefPtr<nsNavHistory> self(this);
nsMainThreadPtrHandle<nsIObserver> callback(
!aCallback ? nullptr
: new nsMainThreadPtrHolder<nsIObserver>(
"nsNavHistory::RecalculateOriginFrecencyStats callback",
aCallback));
nsCOMPtr<nsIEventTarget> target(do_GetInterface(mDB->MainConn()));
NS_ENSURE_STATE(target);
nsresult rv = target->Dispatch(NS_NewRunnableFunction(
"nsNavHistory::RecalculateOriginFrecencyStats", [self, callback] {
Unused << self->RecalculateOriginFrecencyStatsInternal();
Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
"nsNavHistory::RecalculateOriginFrecencyStats callback",
[callback] {
if (callback) {
Unused << callback->Observe(nullptr, "", nullptr);
}
}));
}));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsNavHistory::RecalculateOriginFrecencyStatsInternal() {
nsCOMPtr<mozIStorageConnection> conn(mDB->MainConn());
NS_ENSURE_STATE(conn);
nsresult rv = conn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT OR REPLACE INTO moz_meta(key, value) VALUES "
"( "
"'" MOZ_META_KEY_ORIGIN_FRECENCY_COUNT
"' , "
"(SELECT COUNT(*) FROM moz_origins WHERE frecency > 0) "
"), "
"( "
"'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM
"', "
"(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
"), "
"( "
"'" MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES
"' , "
"(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
") "));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
void // static
nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
const int64_t aLastInsertedId) {
if (aTable.EqualsLiteral("moz_places")) {
nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
} else if (aTable.EqualsLiteral("moz_historyvisits")) {
nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
} else {
MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
}
}
int32_t nsNavHistory::GetDaysOfHistory() {
MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
if (mDaysOfHistory != -1) return mDaysOfHistory;
// SQLite doesn't have a CEIL() function, so we must do that later.
// We should also take into account timers resolution, that may be as bad as
// 16ms on Windows, so in some cases the difference may be 0, if the
// check is done near the visit. Thus remember to check for NULL separately.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT CAST(( "
"strftime('%s','now','localtime','utc') - "
"(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) "
") AS DOUBLE) "
"/86400, "
"strftime('%s','now','localtime','+1 day','start of day','utc') * "
"1000000");
NS_ENSURE_TRUE(stmt, 0);
mozStorageStatementScoper scoper(stmt);
bool hasResult;
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
// If we get NULL, then there are no visits, otherwise there must always be
// at least 1 day of history.
bool hasNoVisits;
(void)stmt->GetIsNull(0, &hasNoVisits);
mDaysOfHistory =
hasNoVisits
? 0
: std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0))));
mLastCachedStartOfDay =
NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1.
}
return mDaysOfHistory;
}
PRTime nsNavHistory::GetNow() {
if (!mCachedNow) {
mCachedNow = PR_Now();
if (!mExpireNowTimer) mExpireNowTimer = NS_NewTimer();
if (mExpireNowTimer)
mExpireNowTimer->InitWithNamedFuncCallback(
expireNowTimerCallback, this, RENEW_CACHED_NOW_TIMEOUT,
nsITimer::TYPE_ONE_SHOT, "nsNavHistory::GetNow");
}
return mCachedNow;
}
void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) {
nsNavHistory* history = static_cast<nsNavHistory*>(aClosure);
if (history) {
history->mCachedNow = 0;
history->mExpireNowTimer = nullptr;
}
}
/**
* Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
* Pass in a pre-normalized now and a date, and we'll find the difference since
* midnight on each of the days.
*/
static PRTime NormalizeTimeRelativeToday(PRTime aTime) {
// round to midnight this morning
PRExplodedTime explodedTime;
PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
// set to midnight (0:00)
explodedTime.tm_min = explodedTime.tm_hour = explodedTime.tm_sec =
explodedTime.tm_usec = 0;
return PR_ImplodeTime(&explodedTime);
}
// nsNavHistory::NormalizeTime
//
// Converts a nsINavHistoryQuery reference+offset time into a PRTime
// relative to the epoch.
//
// It is important that this function NOT use the current time optimization.
// It is called to update queries, and we really need to know what right
// now is because those incoming values will also have current times that
// we will have to compare against.
PRTime // static
nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) {
PRTime ref;
switch (aRelative) {
case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
return aOffset;
case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
ref = NormalizeTimeRelativeToday(PR_Now());
break;
case nsINavHistoryQuery::TIME_RELATIVE_NOW:
ref = PR_Now();
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid relative time");
return 0;
}
return ref + aOffset;
}
// nsNavHistory::DomainNameFromURI
//
// This does the www.mozilla.org -> mozilla.org and
// foo.theregister.co.uk -> theregister.co.uk conversion
void nsNavHistory::DomainNameFromURI(nsIURI* aURI, nsACString& aDomainName) {
// lazily get the effective tld service
if (!mTLDService)
mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
if (mTLDService) {
// get the base domain for a given hostname.
// e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName);
if (NS_SUCCEEDED(rv)) return;
}
// just return the original hostname
// (it's also possible the host is an IP address)
aURI->GetAsciiHost(aDomainName);
}
bool nsNavHistory::hasHistoryEntries() { return GetDaysOfHistory() > 0; }
// Call this method before visiting a URL in order to help determine the
// transition type of the visit.
//
// @see MarkPageAsTyped
NS_IMETHODIMP
nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aURI);
// don't add when history is disabled
if (IsHistoryDisabled()) return NS_OK;
nsAutoCString uriString;
nsresult rv = aURI->GetSpec(uriString);
NS_ENSURE_SUCCESS(rv, rv);
mRecentBookmark.Put(uriString, GetNow());
if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
ExpireNonrecentEvents(&mRecentBookmark);
return NS_OK;
}
// nsNavHistory::CanAddURI
//
// Filter out unwanted URIs such as "chrome:", "mailbox:", etc.
//
// The model is if we don't know differently then add which basically means
// we are suppose to try all the things we know not to allow in and then if
// we don't bail go on and allow it in.
NS_IMETHODIMP
nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aURI);
NS_ENSURE_ARG_POINTER(canAdd);
// If history is disabled, don't add any entry.
if (IsHistoryDisabled()) {
*canAdd = false;
return NS_OK;
}
return CanAddURIToHistory(aURI, canAdd);
}
// nsNavHistory::CanAddURIToHistory
//
// Helper for nsNavHistory::CanAddURI to be callable from a child process
// static
nsresult nsNavHistory::CanAddURIToHistory(nsIURI* aURI, bool* aCanAdd) {
// Default to false.
*aCanAdd = false;
// If the url length is over a threshold, don't add it.
nsCString spec;
nsresult rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
if (spec.Length() > MaxURILength()) {
return NS_OK;
}
nsAutoCString scheme;
rv = aURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
// first check the most common cases
if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) {
*aCanAdd = true;
return NS_OK;
}
// now check for all bad things
*aCanAdd =
!scheme.EqualsLiteral("about") && !scheme.EqualsLiteral("blob") &&
!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("data") &&
!scheme.EqualsLiteral("imap") && !scheme.EqualsLiteral("javascript") &&
!scheme.EqualsLiteral("mailbox") && !scheme.EqualsLiteral("moz-anno") &&
!scheme.EqualsLiteral("news") && !scheme.EqualsLiteral("page-icon") &&
!scheme.EqualsLiteral("resource") && !scheme.EqualsLiteral("view-source");
return NS_OK;
}
// nsNavHistory::MaxURILength
// static
uint32_t nsNavHistory::MaxURILength() {
// Duplicates Database::MaxUrlLength() for use in
// child processes without a database instance.
static uint32_t maxSpecLength = 0;
if (!maxSpecLength) {
maxSpecLength = Preferences::GetInt(PREF_HISTORY_MAXURLLEN,
PREF_HISTORY_MAXURLLEN_DEFAULT);
if (maxSpecLength < 255 || maxSpecLength > INT32_MAX) {
maxSpecLength = PREF_HISTORY_MAXURLLEN_DEFAULT;
}
}
return maxSpecLength;
}
// nsNavHistory::GetNewQuery
NS_IMETHODIMP
nsNavHistory::GetNewQuery(nsINavHistoryQuery** _retval) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG_POINTER(_retval);
RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
query.forget(_retval);
return NS_OK;
}
// nsNavHistory::GetNewQueryOptions
NS_IMETHODIMP
nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions** _retval) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG_POINTER(_retval);
RefPtr<nsNavHistoryQueryOptions> queryOptions =
new nsNavHistoryQueryOptions();
queryOptions.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
nsNavHistory::ExecuteQuery(nsINavHistoryQuery* aQuery,
nsINavHistoryQueryOptions* aOptions,
nsINavHistoryResult** _retval) {
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aQuery);
NS_ENSURE_ARG(aOptions);
NS_ENSURE_ARG_POINTER(_retval);
// Clone the input query and options, because the caller might change the
// objects, but we always want to reflect the original parameters.
nsCOMPtr<nsINavHistoryQuery> queryClone;
aQuery->Clone(getter_AddRefs(queryClone));
NS_ENSURE_STATE(queryClone);
RefPtr<nsNavHistoryQuery> query = do_QueryObject(queryClone);
NS_ENSURE_STATE(query);
nsCOMPtr<nsINavHistoryQueryOptions> optionsClone;
aOptions->Clone(getter_AddRefs(optionsClone));
NS_ENSURE_STATE(optionsClone);
RefPtr<nsNavHistoryQueryOptions> options = do_QueryObject(optionsClone);
NS_ENSURE_STATE(options);
// Create the root node.
RefPtr<nsNavHistoryContainerResultNode> rootNode;
nsCString folderGuid = GetSimpleBookmarksQueryParent(query, options);
if (!folderGuid.IsEmpty()) {
// In the simple case where we're just querying children of a single
// bookmark folder, we can more efficiently generate results.
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
RefPtr<nsNavHistoryResultNode> tempRootNode;
nsresult rv = bookmarks->ResultNodeForContainer(
folderGuid, options, getter_AddRefs(tempRootNode));
if (NS_SUCCEEDED(rv)) {
rootNode = tempRootNode->GetAsContainer();
} else {
NS_WARNING("Generating a generic empty node for a broken query!");
// This is a perf hack to generate an empty query that skips filtering.
options->SetExcludeItems(true);
}
}
if (!rootNode) {
// Either this is not a folder shortcut, or is a broken one. In both cases
// just generate a query node.
nsAutoCString queryUri;
nsresult rv = QueryToQueryString(query, options, queryUri);
NS_ENSURE_SUCCESS(rv, rv);
rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), 0, queryUri,
query, options);
}
// Create the result that will hold nodes. Inject batching status into it.
RefPtr<nsNavHistoryResult> result =
new nsNavHistoryResult(rootNode, query, options);
result.forget(_retval);
return NS_OK;
}
// determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions
// if this is the place query from the history menu.
// from browser-menubar.inc, our history menu query is:
// place:sort=4&maxResults=10
// note, any maxResult > 0 will still be considered a history menu query
// or if this is the place query from the old "Most Visited" item in some
// profiles: folder: place:sort=8&maxResults=10 note, any maxResult > 0 will
// still be considered a Most Visited menu query
static bool IsOptimizableHistoryQuery(
const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions, uint16_t aSortMode) {
if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
return false;
if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
return false;
if (aOptions->SortingMode() != aSortMode) return false;
if (aOptions->MaxResults() <= 0) return false;
if (aOptions->ExcludeItems()) return false;
if (aOptions->IncludeHidden()) return false;
if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) return false;
if (aQuery->BeginTime() || aQuery->BeginTimeReference()) return false;
if (aQuery->EndTime() || aQuery->EndTimeReference()) return false;
if (!aQuery->SearchTerms().IsEmpty()) return false;
if (aQuery->OnlyBookmarked()) return false;
if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) return false;
if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
return false;
if (aQuery->Parents().Length() > 0) return false;
if (aQuery->Tags().Length() > 0) return false;
if (aQuery->Transitions().Length() > 0) return false;
return true;
}
static bool NeedToFilterResultSet(const RefPtr<nsNavHistoryQuery>& aQuery,
nsNavHistoryQueryOptions* aOptions) {
return aOptions->ExcludeQueries();
}
// ** Helper class for ConstructQueryString **/
class PlacesSQLQueryBuilder {
public:
PlacesSQLQueryBuilder(const nsCString& aConditions,
const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions,
bool aUseLimit, nsNavHistory::StringHash& aAddParams,
bool aHasSearchTerms);
nsresult GetQueryString(nsCString& aQueryString);
private:
nsresult Select();
nsresult SelectAsURI();
nsresult SelectAsVisit();
nsresult SelectAsDay();
nsresult SelectAsSite();
nsresult SelectAsTag();
nsresult SelectAsRoots();
nsresult SelectAsLeftPane();
nsresult Where();
nsresult GroupBy();
nsresult OrderBy();
nsresult Limit();
void OrderByColumnIndexAsc(int32_t aIndex);
void OrderByColumnIndexDesc(int32_t aIndex);
// Use these if you want a case insensitive sorting.
void OrderByTextColumnIndexAsc(int32_t aIndex);
void OrderByTextColumnIndexDesc(int32_t aIndex);
const nsCString& mConditions;
bool mUseLimit;
bool mHasSearchTerms;
uint16_t mResultType;
uint16_t mQueryType;
bool mIncludeHidden;
uint16_t mSortingMode;
uint32_t mMaxResults;
nsCString mQueryString;
nsCString mGroupBy;
bool mHasDateColumns;
bool mSkipOrderBy;
nsNavHistory::StringHash& mAddParams;
};
PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
const nsCString& aConditions, const RefPtr<nsNavHistoryQuery>& aQuery,
const RefPtr<nsNavHistoryQueryOptions>& aOptions, bool aUseLimit,
nsNavHistory::StringHash& aAddParams, bool aHasSearchTerms)
: mConditions(aConditions),
mUseLimit(aUseLimit),
mHasSearchTerms(aHasSearchTerms),
mResultType(aOptions->ResultType()),
mQueryType(aOptions->QueryType()),
mIncludeHidden(aOptions->IncludeHidden()),
mSortingMode(aOptions->SortingMode()),
mMaxResults(aOptions->MaxResults()),
mSkipOrderBy(false),
mAddParams(aAddParams) {
mHasDateColumns =
(mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
// Force the default sorting mode for tag queries.
if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_NONE &&
aQuery->Tags().Length() > 0) {
mSortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
}
}
nsresult PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) {
nsresult rv = Select();
NS_ENSURE_SUCCESS(rv, rv);
rv = Where();
NS_ENSURE_SUCCESS(rv, rv);
rv = GroupBy();
NS_ENSURE_SUCCESS(rv, rv);
rv = OrderBy();
NS_ENSURE_SUCCESS(rv, rv);
rv = Limit();
NS_ENSURE_SUCCESS(rv, rv);
aQueryString = mQueryString;
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::Select() {
nsresult rv;
switch (mResultType) {
case nsINavHistoryQueryOptions::RESULTS_AS_URI:
rv = SelectAsURI();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_VISIT:
rv = SelectAsVisit();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY:
case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY:
rv = SelectAsDay();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY:
rv = SelectAsSite();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT:
rv = SelectAsTag();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY:
rv = SelectAsRoots();
NS_ENSURE_SUCCESS(rv, rv);
break;
case nsINavHistoryQueryOptions::RESULTS_AS_LEFT_PANE_QUERY:
rv = SelectAsLeftPane();
NS_ENSURE_SUCCESS(rv, rv);
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid result type");
}
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsURI() {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
nsAutoCString tagsSqlFragment;
switch (mQueryType) {
case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
mHasSearchTerms, tagsSqlFragment);
mQueryString = NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title AS page_title, "
"h.rev_host, h.visit_count, "
"h.last_visit_date, null, null, null, null, null, ") +
tagsSqlFragment +
NS_LITERAL_CSTRING(
", h.frecency, h.hidden, h.guid, "
"null, null, null "
"FROM moz_places h "
// WHERE 1 is a no-op since additonal conditions will
// start with AND.
"WHERE 1 "
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
"{ADDITIONAL_CONDITIONS} ");
break;
case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("b.fk"),
mHasSearchTerms, tagsSqlFragment);
mQueryString =
NS_LITERAL_CSTRING(
"SELECT b.fk, h.url, b.title AS page_title, "
"h.rev_host, h.visit_count, h.last_visit_date, null, b.id, "
"b.dateAdded, b.lastModified, b.parent, ") +
tagsSqlFragment +
NS_LITERAL_CSTRING(
", h.frecency, h.hidden, h.guid,"
"null, null, null, b.guid, b.position, b.type, b.fk "
"FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
"WHERE NOT EXISTS "
"(SELECT id FROM moz_bookmarks "
"WHERE id = b.parent AND parent = ") +
nsPrintfCString("%" PRId64, history->GetTagsFolder()) +
NS_LITERAL_CSTRING(
") "
"AND NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
"hash('place', 'prefix_hi') "
"{ADDITIONAL_CONDITIONS}");
break;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsVisit() {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
nsAutoCString tagsSqlFragment;
GetTagsSqlFragment(history->GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
mHasSearchTerms, tagsSqlFragment);
mQueryString =
NS_LITERAL_CSTRING(
"SELECT h.id, h.url, h.title AS page_title, h.rev_host, "
"h.visit_count, "
"v.visit_date, null, null, null, null, null, ") +
tagsSqlFragment +
NS_LITERAL_CSTRING(
", h.frecency, h.hidden, h.guid, "
"v.id, v.from_visit, v.visit_type "
"FROM moz_places h "
"JOIN moz_historyvisits v ON h.id = v.place_id "
// WHERE 1 is a no-op since additonal conditions will start with AND.
"WHERE 1 "
"{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
"{ADDITIONAL_CONDITIONS} ");
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsDay() {
mSkipOrderBy = true;
// Sort child queries based on sorting mode if it's provided, otherwise
// fallback to default sort by title ascending.
uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE &&
mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY)
sortingMode = mSortingMode;
uint16_t resultType =
mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY
? (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI
: (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
// beginTime will become the node's time property, we don't use endTime
// because it could overlap, and we use time to sort containers and find
// insert position in a result.
mQueryString = nsPrintfCString(
"SELECT null, "
"'place:type=%d&sort=%d&beginTime='||beginTime||'&endTime='||endTime, "
"dayTitle, null, null, beginTime, null, null, null, null, null, null, "
"null, null, null "
"FROM (", // TOUTER BEGIN
resultType, sortingMode);
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
int32_t daysOfHistory = history->GetDaysOfHistory();
for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) {
nsAutoCString dateName;
// Timeframes are calculated as BeginTime <= container < EndTime.
// Notice times can't be relative to now, since to recognize a query we
// must ensure it won't change based on the time it is built.
// So, to select till now, we really select till start of tomorrow, that is
// a fixed timestamp.
// These are used as limits for the inside containers.
nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime;
// These are used to query if the container should be visible.
nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime;
switch (i) {
case 0:
// Today
history->GetStringFromName("finduri-AgeInDays-is-0", dateName);
// From start of today
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','utc')*1000000)");
// To now (tomorrow)
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','+1 "
"day','utc')*1000000)");
// Search for the same timeframe.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
break;
case 1:
// Yesterday
history->GetStringFromName("finduri-AgeInDays-is-1", dateName);
// From start of yesterday
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','-1 "
"day','utc')*1000000)");
// To start of today
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','utc')*1000000)");
// Search for the same timeframe.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
break;
case 2:
// Last 7 days
history->GetAgeInDaysString(7, "finduri-AgeInDays-last-is", dateName);
// From start of 7 days ago
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','-7 "
"days','utc')*1000000)");
// To now (tomorrow)
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','+1 "
"day','utc')*1000000)");
// This is an overlapped container, but we show it only if there are
// visits older than yesterday.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','-1 "
"day','utc')*1000000)");
break;
case 3:
// This month
history->GetStringFromName("finduri-AgeInMonths-is-0", dateName);
// From start of this month
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of "
"month','utc')*1000000)");
// To now (tomorrow)
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','+1 "
"day','utc')*1000000)");
// This is an overlapped container, but we show it only if there are
// visits older than 7 days ago.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of day','-7 "
"days','utc')*1000000)");
break;
default:
if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) {
// Older than 6 months
history->GetAgeInDaysString(6, "finduri-AgeInMonths-isgreater",
dateName);
// From start of epoch
sqlFragmentContainerBeginTime =
NS_LITERAL_CSTRING("(datetime(0, 'unixepoch')*1000000)");
// To start of 6 months ago ( 5 months + this month).
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of month','-5 "
"months','utc')*1000000)");
// Search for the same timeframe.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
break;
}
int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM;
// Previous months' titles are month's name if inside this year,
// month's name and year for previous years.
PRExplodedTime tm;
PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm);
uint16_t currentYear = tm.tm_year;
// Set day before month, setting month without day could cause issues.
// For example setting month to February when today is 30, since
// February has not 30 days, will return March instead.
// Also, we use day 2 instead of day 1, so that the GMT month is always
// the same as the local month. (Bug 603002)
tm.tm_mday = 2;
tm.tm_month -= MonthIndex;
// Notice we use GMTParameters because we just want to get the first
// day of each month. Using LocalTimeParameters would instead force us
// to apply a DST correction that we don't really need here.
PR_NormalizeTime(&tm, PR_GMTParameters);
// If the container is for a past year, add the year to its title,
// otherwise just show the month name.
if (tm.tm_year < currentYear) {
nsNavHistory::GetMonthYear(tm, dateName);
} else {
nsNavHistory::GetMonthName(tm, dateName);
}
// From start of MonthIndex + 1 months ago
sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of month','-");
sqlFragmentContainerBeginTime.AppendInt(MonthIndex);
sqlFragmentContainerBeginTime.AppendLiteral(" months','utc')*1000000)");
// To start of MonthIndex months ago
sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
"(strftime('%s','now','localtime','start of month','-");
sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1);
sqlFragmentContainerEndTime.AppendLiteral(" months','utc')*1000000)");
// Search for the same timeframe.
sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
break;
}
nsPrintfCString dateParam("dayTitle%d", i);
mAddParams.Put(dateParam, dateName);
nsPrintfCString dayRange(
"SELECT :%s AS dayTitle, "
"%s AS beginTime, "
"%s AS endTime "
"WHERE EXISTS ( "
"SELECT id FROM moz_historyvisits "
"WHERE visit_date >= %s "
"AND visit_date < %s "
"AND visit_type NOT IN (0,%d,%d) "
"{QUERY_OPTIONS_VISITS} "
"LIMIT 1 "
") ",
dateParam.get(), sqlFragmentContainerBeginTime.get(),
sqlFragmentContainerEndTime.get(), sqlFragmentSearchBeginTime.get(),
sqlFragmentSearchEndTime.get(), nsINavHistoryService::TRANSITION_EMBED,
nsINavHistoryService::TRANSITION_FRAMED_LINK);
mQueryString.Append(dayRange);
if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
mQueryString.AppendLiteral(" UNION ALL ");
}
mQueryString.AppendLiteral(") "); // TOUTER END
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsSite() {
nsAutoCString localFiles;
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
history->GetStringFromName("localhost", localFiles);
mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles);
// If there are additional conditions the query has to join on visits too.
nsAutoCString visitsJoin;
nsAutoCString additionalConditions;
nsAutoCString timeConstraints;
if (!mConditions.IsEmpty()) {
visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id ");
additionalConditions.AssignLiteral(
"{QUERY_OPTIONS_VISITS} "
"{QUERY_OPTIONS_PLACES} "
"{ADDITIONAL_CONDITIONS} ");
timeConstraints.AssignLiteral(
"||'&beginTime='||:begin_time||"
"'&endTime='||:end_time");
}
mQueryString = nsPrintfCString(
"SELECT null, 'place:type=%d&sort=%d&domain=&domainIsHost=true'%s, "
":localhost, :localhost, null, null, null, null, null, null, null, "
"null, null, null "
"WHERE EXISTS ( "
"SELECT h.id FROM moz_places h "
"%s "
"WHERE h.hidden = 0 "
"AND h.visit_count > 0 "
"AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
"hash('file', 'prefix_hi') "
"%s "
"LIMIT 1 "
") "
"UNION ALL "
"SELECT null, "
"'place:type=%d&sort=%d&domain='||host||'&domainIsHost=true'%s, "
"host, host, null, null, null, null, null, null, null, "
"null, null, null "
"FROM ( "
"SELECT get_unreversed_host(h.rev_host) AS host "
"FROM moz_places h "
"%s "
"WHERE h.hidden = 0 "
"AND h.rev_host <> '.' "
"AND h.visit_count > 0 "
"%s "
"GROUP BY h.rev_host "
"ORDER BY host ASC "
") ",
nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
timeConstraints.get(), visitsJoin.get(), additionalConditions.get(),
nsINavHistoryQueryOptions::RESULTS_AS_URI, mSortingMode,
timeConstraints.get(), visitsJoin.get(), additionalConditions.get());
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsTag() {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
// This allows sorting by date fields what is not possible with
// other history queries.
mHasDateColumns = true;
// TODO (Bug 1449939): This is likely wrong, since the tag name should
// probably be urlencoded, and we have no util for that in SQL, yet.
// We could encode the tag when the user sets it though.
mQueryString = nsPrintfCString(
"SELECT null, 'place:tag=' || title, "
"title, null, null, null, null, null, dateAdded, "
"lastModified, null, null, null, null, null, null "
"FROM moz_bookmarks "
"WHERE parent = %" PRId64,
history->GetTagsFolder());
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsRoots() {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
nsAutoCString toolbarTitle;
nsAutoCString menuTitle;
nsAutoCString unfiledTitle;
history->GetStringFromName("BookmarksToolbarFolderTitle", toolbarTitle);
mAddParams.Put(NS_LITERAL_CSTRING("BookmarksToolbarFolderTitle"),
toolbarTitle);
history->GetStringFromName("BookmarksMenuFolderTitle", menuTitle);
mAddParams.Put(NS_LITERAL_CSTRING("BookmarksMenuFolderTitle"), menuTitle);
history->GetStringFromName("OtherBookmarksFolderTitle", unfiledTitle);
mAddParams.Put(NS_LITERAL_CSTRING("OtherBookmarksFolderTitle"), unfiledTitle);
nsAutoCString mobileString;
if (Preferences::GetBool(MOBILE_BOOKMARKS_PREF, false)) {
nsAutoCString mobileTitle;
history->GetStringFromName("MobileBookmarksFolderTitle", mobileTitle);
mAddParams.Put(NS_LITERAL_CSTRING("MobileBookmarksFolderTitle"),
mobileTitle);
mobileString = NS_LITERAL_CSTRING(
","
"(null, 'place:parent=" MOBILE_ROOT_GUID
"', :MobileBookmarksFolderTitle, null, null, null, "
"null, null, 0, 0, null, null, null, null, "
"'" MOBILE_BOOKMARKS_VIRTUAL_GUID "', null) ");
}
mQueryString =
NS_LITERAL_CSTRING(
"SELECT * FROM ("
"VALUES(null, 'place:parent=" TOOLBAR_ROOT_GUID
"', :BookmarksToolbarFolderTitle, null, null, null, "
"null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
"(null, 'place:parent=" MENU_ROOT_GUID
"', :BookmarksMenuFolderTitle, null, null, null, "
"null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
"(null, 'place:parent=" UNFILED_ROOT_GUID
"', :OtherBookmarksFolderTitle, null, null, null, "
"null, null, 0, 0, null, null, null, null, 'unfiled____v', null) ") +
mobileString + NS_LITERAL_CSTRING(")");
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::SelectAsLeftPane() {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
nsAutoCString historyTitle;
nsAutoCString downloadsTitle;
nsAutoCString tagsTitle;
nsAutoCString allBookmarksTitle;
history->GetStringFromName("OrganizerQueryHistory", historyTitle);
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryHistory"), historyTitle);
history->GetStringFromName("OrganizerQueryDownloads", downloadsTitle);
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryDownloads"), downloadsTitle);
history->GetStringFromName("TagsFolderTitle", tagsTitle);
mAddParams.Put(NS_LITERAL_CSTRING("TagsFolderTitle"), tagsTitle);
history->GetStringFromName("OrganizerQueryAllBookmarks", allBookmarksTitle);
mAddParams.Put(NS_LITERAL_CSTRING("OrganizerQueryAllBookmarks"),
allBookmarksTitle);
mQueryString = nsPrintfCString(
"SELECT * FROM ("
"VALUES"
"(null, 'place:type=%d&sort=%d', :OrganizerQueryHistory, null, null, "
"null, "
"null, null, 0, 0, null, null, null, null, 'history____v', null), "
"(null, 'place:transition=%d&sort=%d', :OrganizerQueryDownloads, null, "
"null, null, "
"null, null, 0, 0, null, null, null, null, 'downloads__v', null), "
"(null, 'place:type=%d&sort=%d', :TagsFolderTitle, null, null, null, "
"null, null, 0, 0, null, null, null, null, 'tags_______v', null), "
"(null, 'place:type=%d', :OrganizerQueryAllBookmarks, null, null, null, "
"null, null, 0, 0, null, null, null, null, 'allbms_____v', null) "
")",
nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY,
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
nsINavHistoryService::TRANSITION_DOWNLOAD,
nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING,
nsINavHistoryQueryOptions::RESULTS_AS_TAGS_ROOT,
nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING,
nsINavHistoryQueryOptions::RESULTS_AS_ROOTS_QUERY);
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::Where() {
// Set query options
nsAutoCString additionalVisitsConditions;
nsAutoCString additionalPlacesConditions;
if (!mIncludeHidden) {
additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 ");
}
if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
// last_visit_date is updated for any kind of visit, so it's a good
// indicator whether the page has visits.
additionalPlacesConditions +=
NS_LITERAL_CSTRING("AND last_visit_date NOTNULL ");
}
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI &&
!additionalVisitsConditions.IsEmpty()) {
// URI results don't join on visits.
nsAutoCString tmp = additionalVisitsConditions;
additionalVisitsConditions =
"AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id ";
additionalVisitsConditions.Append(tmp);
additionalVisitsConditions.AppendLiteral("LIMIT 1)");
}
mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
additionalVisitsConditions.get());
mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
additionalPlacesConditions.get());
// If we used WHERE already, we inject the conditions
// in place of {ADDITIONAL_CONDITIONS}
if (mQueryString.Find("{ADDITIONAL_CONDITIONS}") != kNotFound) {
nsAutoCString innerCondition;
// If we have condition AND it
if (!mConditions.IsEmpty()) {
innerCondition = " AND (";
innerCondition += mConditions;
innerCondition += ")";
}
mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}",
innerCondition.get());
} else if (!mConditions.IsEmpty()) {
mQueryString += "WHERE ";
mQueryString += mConditions;
}
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::GroupBy() {
mQueryString += mGroupBy;
return NS_OK;
}
nsresult PlacesSQLQueryBuilder::OrderBy() {
if (mSkipOrderBy) return NS_OK;
// Sort clause: we will sort later, but if it comes out of the DB sorted,
// our later sort will be basically free. The DB can sort these for free
// most of the time anyway, because it has indices over these items.
switch (mSortingMode) {
case nsINavHistoryQueryOptions::SORT_BY_NONE:
// Ensure sorting does not change based on tables status.
if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
}
break;
case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
// If the user wants few results, we limit them by date, necessitating
// a sort by date here (see the IDL definition for maxResults).
// Otherwise we will do actual sorting by title, but since we could need
// to special sort for some locale we will repeat a second sorting at the
// end in nsNavHistoryResult, that should be faster since the list will be
// almost ordered.
if (mMaxResults > 0)
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
else if (mSortingMode ==
nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING)
OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title);
else
OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title);
break;
case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate);
break;
case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
break;
case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL);
break;
case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL);
break;
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount);
break;
case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount);
break;
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
if (mHasDateColumns)
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
break;
case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
if (mHasDateColumns)
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
break;
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
if (mHasDateColumns)
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified);
break;
case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
if (mHasDateColumns)
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified);
break;
case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
break; // Sort later in nsNavHistoryQueryResultNode::FillChildren()
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency);
break;
case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency);
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid sorting mode");
}
return NS_OK;
}
void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex) {
mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex + 1);
}
void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex) {
mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex + 1);
}
void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex) {
mQueryString +=
nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC", aIndex + 1);
}