1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/nsNavHistory.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4607 @@ 1.4 +//* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include <stdio.h> 1.10 + 1.11 +#include "mozilla/DebugOnly.h" 1.12 + 1.13 +#include "nsNavHistory.h" 1.14 + 1.15 +#include "mozIPlacesAutoComplete.h" 1.16 +#include "nsNavBookmarks.h" 1.17 +#include "nsAnnotationService.h" 1.18 +#include "nsFaviconService.h" 1.19 +#include "nsPlacesMacros.h" 1.20 +#include "History.h" 1.21 +#include "Helpers.h" 1.22 + 1.23 +#include "nsTArray.h" 1.24 +#include "nsCollationCID.h" 1.25 +#include "nsILocaleService.h" 1.26 +#include "nsNetUtil.h" 1.27 +#include "nsPrintfCString.h" 1.28 +#include "nsPromiseFlatString.h" 1.29 +#include "nsString.h" 1.30 +#include "nsUnicharUtils.h" 1.31 +#include "prsystem.h" 1.32 +#include "prtime.h" 1.33 +#include "nsEscape.h" 1.34 +#include "nsIEffectiveTLDService.h" 1.35 +#include "nsIClassInfoImpl.h" 1.36 +#include "nsThreadUtils.h" 1.37 +#include "nsAppDirectoryServiceDefs.h" 1.38 +#include "nsMathUtils.h" 1.39 +#include "mozilla/storage.h" 1.40 +#include "mozilla/Preferences.h" 1.41 +#include <algorithm> 1.42 + 1.43 +#ifdef MOZ_XUL 1.44 +#include "nsIAutoCompleteInput.h" 1.45 +#include "nsIAutoCompletePopup.h" 1.46 +#endif 1.47 + 1.48 +using namespace mozilla; 1.49 +using namespace mozilla::places; 1.50 + 1.51 +// The maximum number of things that we will store in the recent events list 1.52 +// before calling ExpireNonrecentEvents. This number should be big enough so it 1.53 +// is very difficult to get that many unconsumed events (for example, typed but 1.54 +// never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start 1.55 +// checking each one for every page visit, which will be somewhat slower. 1.56 +#define RECENT_EVENT_QUEUE_MAX_LENGTH 128 1.57 + 1.58 +// preference ID strings 1.59 +#define PREF_HISTORY_ENABLED "places.history.enabled" 1.60 + 1.61 +#define PREF_FREC_NUM_VISITS "places.frecency.numVisits" 1.62 +#define PREF_FREC_NUM_VISITS_DEF 10 1.63 +#define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff" 1.64 +#define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4 1.65 +#define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff" 1.66 +#define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14 1.67 +#define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff" 1.68 +#define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31 1.69 +#define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff" 1.70 +#define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90 1.71 +#define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight" 1.72 +#define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100 1.73 +#define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight" 1.74 +#define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70 1.75 +#define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight" 1.76 +#define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50 1.77 +#define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight" 1.78 +#define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30 1.79 +#define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight" 1.80 +#define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10 1.81 +#define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus" 1.82 +#define PREF_FREC_EMBED_VISIT_BONUS_DEF 0 1.83 +#define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus" 1.84 +#define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0 1.85 +#define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus" 1.86 +#define PREF_FREC_LINK_VISIT_BONUS_DEF 100 1.87 +#define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus" 1.88 +#define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000 1.89 +#define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus" 1.90 +#define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75 1.91 +#define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus" 1.92 +#define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0 1.93 +#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS "places.frecency.permRedirectVisitBonus" 1.94 +#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0 1.95 +#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS "places.frecency.tempRedirectVisitBonus" 1.96 +#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0 1.97 +#define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus" 1.98 +#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0 1.99 +#define PREF_FREC_UNVISITED_BOOKMARK_BONUS "places.frecency.unvisitedBookmarkBonus" 1.100 +#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140 1.101 +#define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus" 1.102 +#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200 1.103 + 1.104 +// In order to avoid calling PR_now() too often we use a cached "now" value 1.105 +// for repeating stuff. These are milliseconds between "now" cache refreshes. 1.106 +#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC) 1.107 + 1.108 +static const int64_t USECS_PER_DAY = (int64_t)PR_USEC_PER_SEC * 60 * 60 * 24; 1.109 + 1.110 +// character-set annotation 1.111 +#define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet") 1.112 + 1.113 +// These macros are used when splitting history by date. 1.114 +// These are the day containers and catch-all final container. 1.115 +#define HISTORY_ADDITIONAL_DATE_CONT_NUM 3 1.116 +// We use a guess of the number of months considering all of them 30 days 1.117 +// long, but we split only the last 6 months. 1.118 +#define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \ 1.119 + (HISTORY_ADDITIONAL_DATE_CONT_NUM + \ 1.120 + std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit/30))) 1.121 +// Max number of containers, used to initialize the params hash. 1.122 +#define HISTORY_DATE_CONT_MAX 10 1.123 + 1.124 +// Initial size of the embed visits cache. 1.125 +#define EMBED_VISITS_INITIAL_CACHE_SIZE 128 1.126 + 1.127 +// Initial size of the recent events caches. 1.128 +#define RECENT_EVENTS_INITIAL_CACHE_SIZE 128 1.129 + 1.130 +// Observed topics. 1.131 +#ifdef MOZ_XUL 1.132 +#define TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING "autocomplete-will-enter-text" 1.133 +#endif 1.134 +#define TOPIC_IDLE_DAILY "idle-daily" 1.135 +#define TOPIC_PREF_CHANGED "nsPref:changed" 1.136 +#define TOPIC_PROFILE_TEARDOWN "profile-change-teardown" 1.137 +#define TOPIC_PROFILE_CHANGE "profile-before-change" 1.138 + 1.139 +static const char* kObservedPrefs[] = { 1.140 + PREF_HISTORY_ENABLED 1.141 +, PREF_FREC_NUM_VISITS 1.142 +, PREF_FREC_FIRST_BUCKET_CUTOFF 1.143 +, PREF_FREC_SECOND_BUCKET_CUTOFF 1.144 +, PREF_FREC_THIRD_BUCKET_CUTOFF 1.145 +, PREF_FREC_FOURTH_BUCKET_CUTOFF 1.146 +, PREF_FREC_FIRST_BUCKET_WEIGHT 1.147 +, PREF_FREC_SECOND_BUCKET_WEIGHT 1.148 +, PREF_FREC_THIRD_BUCKET_WEIGHT 1.149 +, PREF_FREC_FOURTH_BUCKET_WEIGHT 1.150 +, PREF_FREC_DEFAULT_BUCKET_WEIGHT 1.151 +, PREF_FREC_EMBED_VISIT_BONUS 1.152 +, PREF_FREC_FRAMED_LINK_VISIT_BONUS 1.153 +, PREF_FREC_LINK_VISIT_BONUS 1.154 +, PREF_FREC_TYPED_VISIT_BONUS 1.155 +, PREF_FREC_BOOKMARK_VISIT_BONUS 1.156 +, PREF_FREC_DOWNLOAD_VISIT_BONUS 1.157 +, PREF_FREC_PERM_REDIRECT_VISIT_BONUS 1.158 +, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS 1.159 +, PREF_FREC_DEFAULT_VISIT_BONUS 1.160 +, PREF_FREC_UNVISITED_BOOKMARK_BONUS 1.161 +, PREF_FREC_UNVISITED_TYPED_BONUS 1.162 +, nullptr 1.163 +}; 1.164 + 1.165 +NS_IMPL_ADDREF(nsNavHistory) 1.166 +NS_IMPL_RELEASE(nsNavHistory) 1.167 + 1.168 +NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON, 1.169 + NS_NAVHISTORYSERVICE_CID) 1.170 +NS_INTERFACE_MAP_BEGIN(nsNavHistory) 1.171 + NS_INTERFACE_MAP_ENTRY(nsINavHistoryService) 1.172 + NS_INTERFACE_MAP_ENTRY(nsIBrowserHistory) 1.173 + NS_INTERFACE_MAP_ENTRY(nsIObserver) 1.174 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.175 + NS_INTERFACE_MAP_ENTRY(nsPIPlacesDatabase) 1.176 + NS_INTERFACE_MAP_ENTRY(nsPIPlacesHistoryListenersNotifier) 1.177 + NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant) 1.178 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService) 1.179 + NS_IMPL_QUERY_CLASSINFO(nsNavHistory) 1.180 +NS_INTERFACE_MAP_END 1.181 + 1.182 +// We don't care about flattening everything 1.183 +NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory, 1.184 + nsINavHistoryService, 1.185 + nsIBrowserHistory) 1.186 + 1.187 +namespace { 1.188 + 1.189 +static int64_t GetSimpleBookmarksQueryFolder( 1.190 + const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.191 + nsNavHistoryQueryOptions* aOptions); 1.192 +static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.193 + nsTArray<nsTArray<nsString>*>* aTerms); 1.194 + 1.195 +void GetTagsSqlFragment(int64_t aTagsFolder, 1.196 + const nsACString& aRelation, 1.197 + bool aHasSearchTerms, 1.198 + nsACString& _sqlFragment) { 1.199 + if (!aHasSearchTerms) 1.200 + _sqlFragment.AssignLiteral("null"); 1.201 + else { 1.202 + // This subquery DOES NOT order tags for performance reasons. 1.203 + _sqlFragment.Assign(NS_LITERAL_CSTRING( 1.204 + "(SELECT GROUP_CONCAT(t_t.title, ',') " 1.205 + "FROM moz_bookmarks b_t " 1.206 + "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent " 1.207 + "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" " 1.208 + "AND t_t.parent = ") + 1.209 + nsPrintfCString("%lld", aTagsFolder) + NS_LITERAL_CSTRING(" " 1.210 + ")")); 1.211 + } 1.212 + 1.213 + _sqlFragment.AppendLiteral(" AS tags "); 1.214 +} 1.215 + 1.216 +/** 1.217 + * This class sets begin/end of batch updates to correspond to C++ scopes so 1.218 + * we can be sure end always gets called. 1.219 + */ 1.220 +class UpdateBatchScoper 1.221 +{ 1.222 +public: 1.223 + UpdateBatchScoper(nsNavHistory& aNavHistory) : mNavHistory(aNavHistory) 1.224 + { 1.225 + mNavHistory.BeginUpdateBatch(); 1.226 + } 1.227 + ~UpdateBatchScoper() 1.228 + { 1.229 + mNavHistory.EndUpdateBatch(); 1.230 + } 1.231 +protected: 1.232 + nsNavHistory& mNavHistory; 1.233 +}; 1.234 + 1.235 +} // anonymouse namespace 1.236 + 1.237 + 1.238 +// Queries rows indexes to bind or get values, if adding a new one, be sure to 1.239 +// update nsNavBookmarks statements and its kGetChildrenIndex_* constants 1.240 +const int32_t nsNavHistory::kGetInfoIndex_PageID = 0; 1.241 +const int32_t nsNavHistory::kGetInfoIndex_URL = 1; 1.242 +const int32_t nsNavHistory::kGetInfoIndex_Title = 2; 1.243 +const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3; 1.244 +const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4; 1.245 +const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5; 1.246 +const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6; 1.247 +const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7; 1.248 +const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8; 1.249 +const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9; 1.250 +const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10; 1.251 +const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11; 1.252 +const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12; 1.253 +const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13; 1.254 +const int32_t nsNavHistory::kGetInfoIndex_Guid = 14; 1.255 + 1.256 +PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService) 1.257 + 1.258 + 1.259 +nsNavHistory::nsNavHistory() 1.260 + : mBatchLevel(0) 1.261 + , mBatchDBTransaction(nullptr) 1.262 + , mCachedNow(0) 1.263 + , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_SIZE) 1.264 + , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_SIZE) 1.265 + , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_SIZE) 1.266 + , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_SIZE) 1.267 + , mHistoryEnabled(true) 1.268 + , mNumVisitsForFrecency(10) 1.269 + , mTagsFolder(-1) 1.270 + , mDaysOfHistory(-1) 1.271 + , mLastCachedStartOfDay(INT64_MAX) 1.272 + , mLastCachedEndOfDay(0) 1.273 + , mCanNotify(true) 1.274 + , mCacheObservers("history-observers") 1.275 +{ 1.276 + NS_ASSERTION(!gHistoryService, 1.277 + "Attempting to create two instances of the service!"); 1.278 + gHistoryService = this; 1.279 +} 1.280 + 1.281 + 1.282 +nsNavHistory::~nsNavHistory() 1.283 +{ 1.284 + // remove the static reference to the service. Check to make sure its us 1.285 + // in case somebody creates an extra instance of the service. 1.286 + NS_ASSERTION(gHistoryService == this, 1.287 + "Deleting a non-singleton instance of the service"); 1.288 + if (gHistoryService == this) 1.289 + gHistoryService = nullptr; 1.290 +} 1.291 + 1.292 + 1.293 +nsresult 1.294 +nsNavHistory::Init() 1.295 +{ 1.296 + LoadPrefs(); 1.297 + 1.298 + mDB = Database::GetDatabase(); 1.299 + NS_ENSURE_STATE(mDB); 1.300 + 1.301 + /***************************************************************************** 1.302 + *** IMPORTANT NOTICE! 1.303 + *** 1.304 + *** Nothing after these add observer calls should return anything but NS_OK. 1.305 + *** If a failure code is returned, this nsNavHistory object will be held onto 1.306 + *** by the observer service and the preference service. 1.307 + ****************************************************************************/ 1.308 + 1.309 + // Observe preferences changes. 1.310 + Preferences::AddWeakObservers(this, kObservedPrefs); 1.311 + 1.312 + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); 1.313 + if (obsSvc) { 1.314 + (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true); 1.315 + (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true); 1.316 +#ifdef MOZ_XUL 1.317 + (void)obsSvc->AddObserver(this, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING, true); 1.318 +#endif 1.319 + } 1.320 + 1.321 + // Don't add code that can fail here! Do it up above, before we add our 1.322 + // observers. 1.323 + 1.324 + return NS_OK; 1.325 +} 1.326 + 1.327 +NS_IMETHODIMP 1.328 +nsNavHistory::GetDatabaseStatus(uint16_t *aDatabaseStatus) 1.329 +{ 1.330 + NS_ENSURE_ARG_POINTER(aDatabaseStatus); 1.331 + *aDatabaseStatus = mDB->GetDatabaseStatus(); 1.332 + return NS_OK; 1.333 +} 1.334 + 1.335 +uint32_t 1.336 +nsNavHistory::GetRecentFlags(nsIURI *aURI) 1.337 +{ 1.338 + uint32_t result = 0; 1.339 + nsAutoCString spec; 1.340 + nsresult rv = aURI->GetSpec(spec); 1.341 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to get aURI's spec"); 1.342 + 1.343 + if (NS_SUCCEEDED(rv)) { 1.344 + if (CheckIsRecentEvent(&mRecentTyped, spec)) 1.345 + result |= RECENT_TYPED; 1.346 + if (CheckIsRecentEvent(&mRecentLink, spec)) 1.347 + result |= RECENT_ACTIVATED; 1.348 + if (CheckIsRecentEvent(&mRecentBookmark, spec)) 1.349 + result |= RECENT_BOOKMARKED; 1.350 + } 1.351 + 1.352 + return result; 1.353 +} 1.354 + 1.355 +nsresult 1.356 +nsNavHistory::GetIdForPage(nsIURI* aURI, 1.357 + int64_t* _pageId, 1.358 + nsCString& _GUID) 1.359 +{ 1.360 + *_pageId = 0; 1.361 + 1.362 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( 1.363 + "SELECT id, url, title, rev_host, visit_count, guid " 1.364 + "FROM moz_places " 1.365 + "WHERE url = :page_url " 1.366 + ); 1.367 + NS_ENSURE_STATE(stmt); 1.368 + mozStorageStatementScoper scoper(stmt); 1.369 + 1.370 + nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); 1.371 + NS_ENSURE_SUCCESS(rv, rv); 1.372 + 1.373 + bool hasEntry = false; 1.374 + rv = stmt->ExecuteStep(&hasEntry); 1.375 + NS_ENSURE_SUCCESS(rv, rv); 1.376 + 1.377 + if (hasEntry) { 1.378 + rv = stmt->GetInt64(0, _pageId); 1.379 + NS_ENSURE_SUCCESS(rv, rv); 1.380 + rv = stmt->GetUTF8String(5, _GUID); 1.381 + NS_ENSURE_SUCCESS(rv, rv); 1.382 + } 1.383 + 1.384 + return NS_OK; 1.385 +} 1.386 + 1.387 +nsresult 1.388 +nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI, 1.389 + int64_t* _pageId, 1.390 + nsCString& _GUID) 1.391 +{ 1.392 + nsresult rv = GetIdForPage(aURI, _pageId, _GUID); 1.393 + NS_ENSURE_SUCCESS(rv, rv); 1.394 + 1.395 + if (*_pageId != 0) { 1.396 + return NS_OK; 1.397 + } 1.398 + 1.399 + // Create a new hidden, untyped and unvisited entry. 1.400 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( 1.401 + "INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid) " 1.402 + "VALUES (:page_url, :rev_host, :hidden, :frecency, GENERATE_GUID()) " 1.403 + ); 1.404 + NS_ENSURE_STATE(stmt); 1.405 + mozStorageStatementScoper scoper(stmt); 1.406 + 1.407 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); 1.408 + NS_ENSURE_SUCCESS(rv, rv); 1.409 + // host (reversed with trailing period) 1.410 + nsAutoString revHost; 1.411 + rv = GetReversedHostname(aURI, revHost); 1.412 + // Not all URI types have hostnames, so this is optional. 1.413 + if (NS_SUCCEEDED(rv)) { 1.414 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost); 1.415 + } else { 1.416 + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host")); 1.417 + } 1.418 + NS_ENSURE_SUCCESS(rv, rv); 1.419 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1); 1.420 + NS_ENSURE_SUCCESS(rv, rv); 1.421 + nsAutoCString spec; 1.422 + rv = aURI->GetSpec(spec); 1.423 + NS_ENSURE_SUCCESS(rv, rv); 1.424 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), 1.425 + IsQueryURI(spec) ? 0 : -1); 1.426 + NS_ENSURE_SUCCESS(rv, rv); 1.427 + 1.428 + rv = stmt->Execute(); 1.429 + NS_ENSURE_SUCCESS(rv, rv); 1.430 + 1.431 + { 1.432 + nsCOMPtr<mozIStorageStatement> getIdStmt = mDB->GetStatement( 1.433 + "SELECT id, guid FROM moz_places WHERE url = :page_url " 1.434 + ); 1.435 + NS_ENSURE_STATE(getIdStmt); 1.436 + mozStorageStatementScoper getIdScoper(getIdStmt); 1.437 + 1.438 + rv = URIBinder::Bind(getIdStmt, NS_LITERAL_CSTRING("page_url"), aURI); 1.439 + NS_ENSURE_SUCCESS(rv, rv); 1.440 + 1.441 + bool hasResult = false; 1.442 + rv = getIdStmt->ExecuteStep(&hasResult); 1.443 + NS_ENSURE_SUCCESS(rv, rv); 1.444 + NS_ASSERTION(hasResult, "hasResult is false but the call succeeded?"); 1.445 + *_pageId = getIdStmt->AsInt64(0); 1.446 + rv = getIdStmt->GetUTF8String(1, _GUID); 1.447 + NS_ENSURE_SUCCESS(rv, rv); 1.448 + } 1.449 + 1.450 + return NS_OK; 1.451 +} 1.452 + 1.453 + 1.454 +void 1.455 +nsNavHistory::LoadPrefs() 1.456 +{ 1.457 + // History preferences. 1.458 + mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true); 1.459 + 1.460 + // Frecency preferences. 1.461 +#define FRECENCY_PREF(_prop, _pref) \ 1.462 + _prop = Preferences::GetInt(_pref, _pref##_DEF) 1.463 + 1.464 + FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS); 1.465 + FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF); 1.466 + FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF); 1.467 + FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF); 1.468 + FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF); 1.469 + FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS); 1.470 + FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS); 1.471 + FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS); 1.472 + FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS); 1.473 + FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS); 1.474 + FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS); 1.475 + FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS); 1.476 + FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS); 1.477 + FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS); 1.478 + FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS); 1.479 + FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS); 1.480 + FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT); 1.481 + FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT); 1.482 + FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT); 1.483 + FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT); 1.484 + FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT); 1.485 + 1.486 +#undef FRECENCY_PREF 1.487 +} 1.488 + 1.489 + 1.490 +void 1.491 +nsNavHistory::NotifyOnVisit(nsIURI* aURI, 1.492 + int64_t aVisitID, 1.493 + PRTime aTime, 1.494 + int64_t referringVisitID, 1.495 + int32_t aTransitionType, 1.496 + const nsACString& aGUID, 1.497 + bool aHidden) 1.498 +{ 1.499 + MOZ_ASSERT(!aGUID.IsEmpty()); 1.500 + // If there's no history, this visit will surely add a day. If the visit is 1.501 + // added before or after the last cached day, the day count may have changed. 1.502 + // Otherwise adding multiple visits in the same day should not invalidate 1.503 + // the cache. 1.504 + if (mDaysOfHistory == 0) { 1.505 + mDaysOfHistory = 1; 1.506 + } else if (aTime > mLastCachedEndOfDay || aTime < mLastCachedStartOfDay) { 1.507 + mDaysOfHistory = -1; 1.508 + } 1.509 + 1.510 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.511 + nsINavHistoryObserver, 1.512 + OnVisit(aURI, aVisitID, aTime, 0, 1.513 + referringVisitID, aTransitionType, aGUID, aHidden)); 1.514 +} 1.515 + 1.516 +void 1.517 +nsNavHistory::NotifyTitleChange(nsIURI* aURI, 1.518 + const nsString& aTitle, 1.519 + const nsACString& aGUID) 1.520 +{ 1.521 + MOZ_ASSERT(!aGUID.IsEmpty()); 1.522 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.523 + nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID)); 1.524 +} 1.525 + 1.526 +void 1.527 +nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI, 1.528 + int32_t aNewFrecency, 1.529 + const nsACString& aGUID, 1.530 + bool aHidden, 1.531 + PRTime aLastVisitDate) 1.532 +{ 1.533 + MOZ_ASSERT(!aGUID.IsEmpty()); 1.534 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.535 + nsINavHistoryObserver, 1.536 + OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden, 1.537 + aLastVisitDate)); 1.538 +} 1.539 + 1.540 +void 1.541 +nsNavHistory::NotifyManyFrecenciesChanged() 1.542 +{ 1.543 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.544 + nsINavHistoryObserver, 1.545 + OnManyFrecenciesChanged()); 1.546 +} 1.547 + 1.548 +namespace { 1.549 + 1.550 +class FrecencyNotification : public nsRunnable 1.551 +{ 1.552 +public: 1.553 + FrecencyNotification(const nsACString& aSpec, 1.554 + int32_t aNewFrecency, 1.555 + const nsACString& aGUID, 1.556 + bool aHidden, 1.557 + PRTime aLastVisitDate) 1.558 + : mSpec(aSpec) 1.559 + , mNewFrecency(aNewFrecency) 1.560 + , mGUID(aGUID) 1.561 + , mHidden(aHidden) 1.562 + , mLastVisitDate(aLastVisitDate) 1.563 + { 1.564 + } 1.565 + 1.566 + NS_IMETHOD Run() 1.567 + { 1.568 + MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread"); 1.569 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.570 + if (navHistory) { 1.571 + nsCOMPtr<nsIURI> uri; 1.572 + (void)NS_NewURI(getter_AddRefs(uri), mSpec); 1.573 + navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden, 1.574 + mLastVisitDate); 1.575 + } 1.576 + return NS_OK; 1.577 + } 1.578 + 1.579 +private: 1.580 + nsCString mSpec; 1.581 + int32_t mNewFrecency; 1.582 + nsCString mGUID; 1.583 + bool mHidden; 1.584 + PRTime mLastVisitDate; 1.585 +}; 1.586 + 1.587 +} // anonymous namespace 1.588 + 1.589 +void 1.590 +nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec, 1.591 + int32_t aNewFrecency, 1.592 + const nsACString& aGUID, 1.593 + bool aHidden, 1.594 + PRTime aLastVisitDate) const 1.595 +{ 1.596 + nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency, 1.597 + aGUID, aHidden, 1.598 + aLastVisitDate); 1.599 + (void)NS_DispatchToMainThread(notif); 1.600 +} 1.601 + 1.602 +int32_t 1.603 +nsNavHistory::GetDaysOfHistory() { 1.604 + MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); 1.605 + 1.606 + if (mDaysOfHistory != -1) 1.607 + return mDaysOfHistory; 1.608 + 1.609 + // SQLite doesn't have a CEIL() function, so we must do that later. 1.610 + // We should also take into account timers resolution, that may be as bad as 1.611 + // 16ms on Windows, so in some cases the difference may be 0, if the 1.612 + // check is done near the visit. Thus remember to check for NULL separately. 1.613 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( 1.614 + "SELECT CAST(( " 1.615 + "strftime('%s','now','localtime','utc') - " 1.616 + "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) " 1.617 + ") AS DOUBLE) " 1.618 + "/86400, " 1.619 + "strftime('%s','now','localtime','+1 day','start of day','utc') * 1000000" 1.620 + ); 1.621 + NS_ENSURE_TRUE(stmt, 0); 1.622 + mozStorageStatementScoper scoper(stmt); 1.623 + 1.624 + bool hasResult; 1.625 + if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { 1.626 + // If we get NULL, then there are no visits, otherwise there must always be 1.627 + // at least 1 day of history. 1.628 + bool hasNoVisits; 1.629 + (void)stmt->GetIsNull(0, &hasNoVisits); 1.630 + mDaysOfHistory = hasNoVisits ? 1.631 + 0 : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0)))); 1.632 + mLastCachedStartOfDay = 1.633 + NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0); 1.634 + mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1. 1.635 + } 1.636 + 1.637 + return mDaysOfHistory; 1.638 +} 1.639 + 1.640 +PRTime 1.641 +nsNavHistory::GetNow() 1.642 +{ 1.643 + if (!mCachedNow) { 1.644 + mCachedNow = PR_Now(); 1.645 + if (!mExpireNowTimer) 1.646 + mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.647 + if (mExpireNowTimer) 1.648 + mExpireNowTimer->InitWithFuncCallback(expireNowTimerCallback, this, 1.649 + RENEW_CACHED_NOW_TIMEOUT, 1.650 + nsITimer::TYPE_ONE_SHOT); 1.651 + } 1.652 + return mCachedNow; 1.653 +} 1.654 + 1.655 + 1.656 +void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure) 1.657 +{ 1.658 + nsNavHistory *history = static_cast<nsNavHistory *>(aClosure); 1.659 + if (history) { 1.660 + history->mCachedNow = 0; 1.661 + history->mExpireNowTimer = 0; 1.662 + } 1.663 +} 1.664 + 1.665 + 1.666 +/** 1.667 + * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp 1.668 + * Pass in a pre-normalized now and a date, and we'll find the difference since 1.669 + * midnight on each of the days. 1.670 + */ 1.671 +static PRTime 1.672 +NormalizeTimeRelativeToday(PRTime aTime) 1.673 +{ 1.674 + // round to midnight this morning 1.675 + PRExplodedTime explodedTime; 1.676 + PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); 1.677 + 1.678 + // set to midnight (0:00) 1.679 + explodedTime.tm_min = 1.680 + explodedTime.tm_hour = 1.681 + explodedTime.tm_sec = 1.682 + explodedTime.tm_usec = 0; 1.683 + 1.684 + return PR_ImplodeTime(&explodedTime); 1.685 +} 1.686 + 1.687 +// nsNavHistory::NormalizeTime 1.688 +// 1.689 +// Converts a nsINavHistoryQuery reference+offset time into a PRTime 1.690 +// relative to the epoch. 1.691 +// 1.692 +// It is important that this function NOT use the current time optimization. 1.693 +// It is called to update queries, and we really need to know what right 1.694 +// now is because those incoming values will also have current times that 1.695 +// we will have to compare against. 1.696 + 1.697 +PRTime // static 1.698 +nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset) 1.699 +{ 1.700 + PRTime ref; 1.701 + switch (aRelative) 1.702 + { 1.703 + case nsINavHistoryQuery::TIME_RELATIVE_EPOCH: 1.704 + return aOffset; 1.705 + case nsINavHistoryQuery::TIME_RELATIVE_TODAY: 1.706 + ref = NormalizeTimeRelativeToday(PR_Now()); 1.707 + break; 1.708 + case nsINavHistoryQuery::TIME_RELATIVE_NOW: 1.709 + ref = PR_Now(); 1.710 + break; 1.711 + default: 1.712 + NS_NOTREACHED("Invalid relative time"); 1.713 + return 0; 1.714 + } 1.715 + return ref + aOffset; 1.716 +} 1.717 + 1.718 +// nsNavHistory::GetUpdateRequirements 1.719 +// 1.720 +// Returns conditions for query update. 1.721 +// 1.722 +// QUERYUPDATE_TIME: 1.723 +// This query is only limited by an inclusive time range on the first 1.724 +// query object. The caller can quickly evaluate the time itself if it 1.725 +// chooses. This is even simpler than "simple" below. 1.726 +// QUERYUPDATE_SIMPLE: 1.727 +// This query is evaluatable using EvaluateQueryForNode to do live 1.728 +// updating. 1.729 +// QUERYUPDATE_COMPLEX: 1.730 +// This query is not evaluatable using EvaluateQueryForNode. When something 1.731 +// happens that this query updates, you will need to re-run the query. 1.732 +// QUERYUPDATE_COMPLEX_WITH_BOOKMARKS: 1.733 +// A complex query that additionally has dependence on bookmarks. All 1.734 +// bookmark-dependent queries fall under this category. 1.735 +// 1.736 +// aHasSearchTerms will be set to true if the query has any dependence on 1.737 +// keywords. When there is no dependence on keywords, we can handle title 1.738 +// change operations as simple instead of complex. 1.739 + 1.740 +uint32_t 1.741 +nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.742 + nsNavHistoryQueryOptions* aOptions, 1.743 + bool* aHasSearchTerms) 1.744 +{ 1.745 + NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query"); 1.746 + 1.747 + // first check if there are search terms 1.748 + *aHasSearchTerms = false; 1.749 + int32_t i; 1.750 + for (i = 0; i < aQueries.Count(); i ++) { 1.751 + aQueries[i]->GetHasSearchTerms(aHasSearchTerms); 1.752 + if (*aHasSearchTerms) 1.753 + break; 1.754 + } 1.755 + 1.756 + bool nonTimeBasedItems = false; 1.757 + bool domainBasedItems = false; 1.758 + 1.759 + for (i = 0; i < aQueries.Count(); i ++) { 1.760 + nsNavHistoryQuery* query = aQueries[i]; 1.761 + 1.762 + if (query->Folders().Length() > 0 || 1.763 + query->OnlyBookmarked() || 1.764 + query->Tags().Length() > 0) { 1.765 + return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; 1.766 + } 1.767 + 1.768 + // Note: we don't currently have any complex non-bookmarked items, but these 1.769 + // are expected to be added. Put detection of these items here. 1.770 + if (!query->SearchTerms().IsEmpty() || 1.771 + !query->Domain().IsVoid() || 1.772 + query->Uri() != nullptr) 1.773 + nonTimeBasedItems = true; 1.774 + 1.775 + if (! query->Domain().IsVoid()) 1.776 + domainBasedItems = true; 1.777 + } 1.778 + 1.779 + if (aOptions->ResultType() == 1.780 + nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) 1.781 + return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS; 1.782 + 1.783 + // Whenever there is a maximum number of results, 1.784 + // and we are not a bookmark query we must requery. This 1.785 + // is because we can't generally know if any given addition/change causes 1.786 + // the item to be in the top N items in the database. 1.787 + if (aOptions->MaxResults() > 0) 1.788 + return QUERYUPDATE_COMPLEX; 1.789 + 1.790 + if (aQueries.Count() == 1 && domainBasedItems) 1.791 + return QUERYUPDATE_HOST; 1.792 + if (aQueries.Count() == 1 && !nonTimeBasedItems) 1.793 + return QUERYUPDATE_TIME; 1.794 + 1.795 + return QUERYUPDATE_SIMPLE; 1.796 +} 1.797 + 1.798 + 1.799 +// nsNavHistory::EvaluateQueryForNode 1.800 +// 1.801 +// This runs the node through the given queries to see if satisfies the 1.802 +// query conditions. Not every query parameters are handled by this code, 1.803 +// but we handle the most common ones so that performance is better. 1.804 +// 1.805 +// We assume that the time on the node is the time that we want to compare. 1.806 +// This is not necessarily true because URL nodes have the last access time, 1.807 +// which is not necessarily the same. However, since this is being called 1.808 +// to update the list, we assume that the last access time is the current 1.809 +// access time that we are being asked to compare so it works out. 1.810 +// 1.811 +// Returns true if node matches the query, false if not. 1.812 + 1.813 +bool 1.814 +nsNavHistory::EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.815 + nsNavHistoryQueryOptions* aOptions, 1.816 + nsNavHistoryResultNode* aNode) 1.817 +{ 1.818 + // lazily created from the node's string when we need to match URIs 1.819 + nsCOMPtr<nsIURI> nodeUri; 1.820 + 1.821 + // --- hidden --- 1.822 + if (aNode->mHidden && !aOptions->IncludeHidden()) 1.823 + return false; 1.824 + 1.825 + for (int32_t i = 0; i < aQueries.Count(); i ++) { 1.826 + bool hasIt; 1.827 + nsCOMPtr<nsNavHistoryQuery> query = aQueries[i]; 1.828 + 1.829 + // --- begin time --- 1.830 + query->GetHasBeginTime(&hasIt); 1.831 + if (hasIt) { 1.832 + PRTime beginTime = NormalizeTime(query->BeginTimeReference(), 1.833 + query->BeginTime()); 1.834 + if (aNode->mTime < beginTime) 1.835 + continue; // before our time range 1.836 + } 1.837 + 1.838 + // --- end time --- 1.839 + query->GetHasEndTime(&hasIt); 1.840 + if (hasIt) { 1.841 + PRTime endTime = NormalizeTime(query->EndTimeReference(), 1.842 + query->EndTime()); 1.843 + if (aNode->mTime > endTime) 1.844 + continue; // after our time range 1.845 + } 1.846 + 1.847 + // --- search terms --- 1.848 + if (! query->SearchTerms().IsEmpty()) { 1.849 + // we can use the existing filtering code, just give it our one object in 1.850 + // an array. 1.851 + nsCOMArray<nsNavHistoryResultNode> inputSet; 1.852 + inputSet.AppendObject(aNode); 1.853 + nsCOMArray<nsNavHistoryQuery> queries; 1.854 + queries.AppendObject(query); 1.855 + nsCOMArray<nsNavHistoryResultNode> filteredSet; 1.856 + nsresult rv = FilterResultSet(nullptr, inputSet, &filteredSet, queries, aOptions); 1.857 + if (NS_FAILED(rv)) 1.858 + continue; 1.859 + if (! filteredSet.Count()) 1.860 + continue; // did not make it through the filter, doesn't match 1.861 + } 1.862 + 1.863 + // --- domain/host matching --- 1.864 + query->GetHasDomain(&hasIt); 1.865 + if (hasIt) { 1.866 + if (! nodeUri) { 1.867 + // lazy creation of nodeUri, which might be checked for multiple queries 1.868 + if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) 1.869 + continue; 1.870 + } 1.871 + nsAutoCString asciiRequest; 1.872 + if (NS_FAILED(AsciiHostNameFromHostString(query->Domain(), asciiRequest))) 1.873 + continue; 1.874 + 1.875 + if (query->DomainIsHost()) { 1.876 + nsAutoCString host; 1.877 + if (NS_FAILED(nodeUri->GetAsciiHost(host))) 1.878 + continue; 1.879 + 1.880 + if (! asciiRequest.Equals(host)) 1.881 + continue; // host names don't match 1.882 + } 1.883 + // check domain names 1.884 + nsAutoCString domain; 1.885 + DomainNameFromURI(nodeUri, domain); 1.886 + if (! asciiRequest.Equals(domain)) 1.887 + continue; // domain names don't match 1.888 + } 1.889 + 1.890 + // --- URI matching --- 1.891 + if (query->Uri()) { 1.892 + if (! nodeUri) { // lazy creation of nodeUri 1.893 + if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI))) 1.894 + continue; 1.895 + } 1.896 + if (! query->UriIsPrefix()) { 1.897 + // easy case: the URI is an exact match 1.898 + bool equals; 1.899 + nsresult rv = query->Uri()->Equals(nodeUri, &equals); 1.900 + NS_ENSURE_SUCCESS(rv, false); 1.901 + if (! equals) 1.902 + continue; 1.903 + } else { 1.904 + // harder case: match prefix, note that we need to get the ASCII string 1.905 + // from the node's parsed URI instead of using the node's mUrl string, 1.906 + // because that might not be normalized 1.907 + nsAutoCString nodeUriString; 1.908 + nodeUri->GetAsciiSpec(nodeUriString); 1.909 + nsAutoCString queryUriString; 1.910 + query->Uri()->GetAsciiSpec(queryUriString); 1.911 + if (queryUriString.Length() > nodeUriString.Length()) 1.912 + continue; // not long enough to match as prefix 1.913 + nodeUriString.SetLength(queryUriString.Length()); 1.914 + if (! nodeUriString.Equals(queryUriString)) 1.915 + continue; // prefixes don't match 1.916 + } 1.917 + } 1.918 + 1.919 + // Transitions matching. 1.920 + const nsTArray<uint32_t>& transitions = query->Transitions(); 1.921 + if (aNode->mTransitionType > 0 && 1.922 + transitions.Length() && 1.923 + !transitions.Contains(aNode->mTransitionType)) { 1.924 + continue; // transition doesn't match. 1.925 + } 1.926 + 1.927 + // If we ever make it to the bottom of this loop, that means it passed all 1.928 + // tests for the given query. Since queries are ORed together, that means 1.929 + // it passed everything and we are done. 1.930 + return true; 1.931 + } 1.932 + 1.933 + // didn't match any query 1.934 + return false; 1.935 +} 1.936 + 1.937 + 1.938 +// nsNavHistory::AsciiHostNameFromHostString 1.939 +// 1.940 +// We might have interesting encodings and different case in the host name. 1.941 +// This will convert that host name into an ASCII host name by sending it 1.942 +// through the URI canonicalization. The result can be used for comparison 1.943 +// with other ASCII host name strings. 1.944 +nsresult // static 1.945 +nsNavHistory::AsciiHostNameFromHostString(const nsACString& aHostName, 1.946 + nsACString& aAscii) 1.947 +{ 1.948 + // To properly generate a uri we must provide a protocol. 1.949 + nsAutoCString fakeURL("http://"); 1.950 + fakeURL.Append(aHostName); 1.951 + nsCOMPtr<nsIURI> uri; 1.952 + nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL); 1.953 + NS_ENSURE_SUCCESS(rv, rv); 1.954 + rv = uri->GetAsciiHost(aAscii); 1.955 + NS_ENSURE_SUCCESS(rv, rv); 1.956 + return NS_OK; 1.957 +} 1.958 + 1.959 + 1.960 +// nsNavHistory::DomainNameFromURI 1.961 +// 1.962 +// This does the www.mozilla.org -> mozilla.org and 1.963 +// foo.theregister.co.uk -> theregister.co.uk conversion 1.964 +void 1.965 +nsNavHistory::DomainNameFromURI(nsIURI *aURI, 1.966 + nsACString& aDomainName) 1.967 +{ 1.968 + // lazily get the effective tld service 1.969 + if (!mTLDService) 1.970 + mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 1.971 + 1.972 + if (mTLDService) { 1.973 + // get the base domain for a given hostname. 1.974 + // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk". 1.975 + nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName); 1.976 + if (NS_SUCCEEDED(rv)) 1.977 + return; 1.978 + } 1.979 + 1.980 + // just return the original hostname 1.981 + // (it's also possible the host is an IP address) 1.982 + aURI->GetAsciiHost(aDomainName); 1.983 +} 1.984 + 1.985 + 1.986 +NS_IMETHODIMP 1.987 +nsNavHistory::GetHasHistoryEntries(bool* aHasEntries) 1.988 +{ 1.989 + NS_ENSURE_ARG_POINTER(aHasEntries); 1.990 + *aHasEntries = GetDaysOfHistory() > 0; 1.991 + return NS_OK; 1.992 +} 1.993 + 1.994 + 1.995 +namespace { 1.996 + 1.997 +class InvalidateAllFrecenciesCallback : public AsyncStatementCallback 1.998 +{ 1.999 +public: 1.1000 + InvalidateAllFrecenciesCallback() 1.1001 + { 1.1002 + } 1.1003 + 1.1004 + NS_IMETHOD HandleCompletion(uint16_t aReason) 1.1005 + { 1.1006 + if (aReason == REASON_FINISHED) { 1.1007 + nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); 1.1008 + NS_ENSURE_STATE(navHistory); 1.1009 + navHistory->NotifyManyFrecenciesChanged(); 1.1010 + } 1.1011 + return NS_OK; 1.1012 + } 1.1013 +}; 1.1014 + 1.1015 +} // anonymous namespace 1.1016 + 1.1017 +nsresult 1.1018 +nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString) 1.1019 +{ 1.1020 + // Exclude place: queries by setting their frecency to zero. 1.1021 + nsCString invalidFrecenciesSQLFragment( 1.1022 + "UPDATE moz_places SET frecency = " 1.1023 + ); 1.1024 + if (!aPlaceIdsQueryString.IsEmpty()) 1.1025 + invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY("); 1.1026 + invalidFrecenciesSQLFragment.AppendLiteral( 1.1027 + "(CASE " 1.1028 + "WHEN url BETWEEN 'place:' AND 'place;' " 1.1029 + "THEN 0 " 1.1030 + "ELSE -1 " 1.1031 + "END) " 1.1032 + ); 1.1033 + if (!aPlaceIdsQueryString.IsEmpty()) { 1.1034 + invalidFrecenciesSQLFragment.AppendLiteral( 1.1035 + ", url, guid, hidden, last_visit_date) " 1.1036 + ); 1.1037 + } 1.1038 + invalidFrecenciesSQLFragment.AppendLiteral( 1.1039 + "WHERE frecency > 0 " 1.1040 + ); 1.1041 + if (!aPlaceIdsQueryString.IsEmpty()) { 1.1042 + invalidFrecenciesSQLFragment.AppendLiteral("AND id IN("); 1.1043 + invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString); 1.1044 + invalidFrecenciesSQLFragment.AppendLiteral(")"); 1.1045 + } 1.1046 + nsRefPtr<InvalidateAllFrecenciesCallback> cb = 1.1047 + aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback() 1.1048 + : nullptr; 1.1049 + 1.1050 + nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( 1.1051 + invalidFrecenciesSQLFragment 1.1052 + ); 1.1053 + NS_ENSURE_STATE(stmt); 1.1054 + 1.1055 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.1056 + nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps)); 1.1057 + NS_ENSURE_SUCCESS(rv, rv); 1.1058 + 1.1059 + return NS_OK; 1.1060 +} 1.1061 + 1.1062 + 1.1063 +// Call this method before visiting a URL in order to help determine the 1.1064 +// transition type of the visit. 1.1065 +// 1.1066 +// @see MarkPageAsTyped 1.1067 + 1.1068 +NS_IMETHODIMP 1.1069 +nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI) 1.1070 +{ 1.1071 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1072 + NS_ENSURE_ARG(aURI); 1.1073 + 1.1074 + // don't add when history is disabled 1.1075 + if (IsHistoryDisabled()) 1.1076 + return NS_OK; 1.1077 + 1.1078 + nsAutoCString uriString; 1.1079 + nsresult rv = aURI->GetSpec(uriString); 1.1080 + NS_ENSURE_SUCCESS(rv, rv); 1.1081 + 1.1082 + // if URL is already in the bookmark queue, then we need to remove the old one 1.1083 + int64_t unusedEventTime; 1.1084 + if (mRecentBookmark.Get(uriString, &unusedEventTime)) 1.1085 + mRecentBookmark.Remove(uriString); 1.1086 + 1.1087 + if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) 1.1088 + ExpireNonrecentEvents(&mRecentBookmark); 1.1089 + 1.1090 + mRecentBookmark.Put(uriString, GetNow()); 1.1091 + return NS_OK; 1.1092 +} 1.1093 + 1.1094 + 1.1095 +// nsNavHistory::CanAddURI 1.1096 +// 1.1097 +// Filter out unwanted URIs such as "chrome:", "mailbox:", etc. 1.1098 +// 1.1099 +// The model is if we don't know differently then add which basically means 1.1100 +// we are suppose to try all the things we know not to allow in and then if 1.1101 +// we don't bail go on and allow it in. 1.1102 + 1.1103 +NS_IMETHODIMP 1.1104 +nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd) 1.1105 +{ 1.1106 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1107 + NS_ENSURE_ARG(aURI); 1.1108 + NS_ENSURE_ARG_POINTER(canAdd); 1.1109 + 1.1110 + // If history is disabled, don't add any entry. 1.1111 + if (IsHistoryDisabled()) { 1.1112 + *canAdd = false; 1.1113 + return NS_OK; 1.1114 + } 1.1115 + 1.1116 + nsAutoCString scheme; 1.1117 + nsresult rv = aURI->GetScheme(scheme); 1.1118 + NS_ENSURE_SUCCESS(rv, rv); 1.1119 + 1.1120 + // first check the most common cases (HTTP, HTTPS) to allow in to avoid most 1.1121 + // of the work 1.1122 + if (scheme.EqualsLiteral("http")) { 1.1123 + *canAdd = true; 1.1124 + return NS_OK; 1.1125 + } 1.1126 + if (scheme.EqualsLiteral("https")) { 1.1127 + *canAdd = true; 1.1128 + return NS_OK; 1.1129 + } 1.1130 + 1.1131 + // now check for all bad things 1.1132 + if (scheme.EqualsLiteral("about") || 1.1133 + scheme.EqualsLiteral("imap") || 1.1134 + scheme.EqualsLiteral("news") || 1.1135 + scheme.EqualsLiteral("mailbox") || 1.1136 + scheme.EqualsLiteral("moz-anno") || 1.1137 + scheme.EqualsLiteral("view-source") || 1.1138 + scheme.EqualsLiteral("chrome") || 1.1139 + scheme.EqualsLiteral("resource") || 1.1140 + scheme.EqualsLiteral("data") || 1.1141 + scheme.EqualsLiteral("wyciwyg") || 1.1142 + scheme.EqualsLiteral("javascript") || 1.1143 + scheme.EqualsLiteral("blob")) { 1.1144 + *canAdd = false; 1.1145 + return NS_OK; 1.1146 + } 1.1147 + *canAdd = true; 1.1148 + return NS_OK; 1.1149 +} 1.1150 + 1.1151 +// nsNavHistory::GetNewQuery 1.1152 + 1.1153 +NS_IMETHODIMP 1.1154 +nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval) 1.1155 +{ 1.1156 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1157 + NS_ENSURE_ARG_POINTER(_retval); 1.1158 + 1.1159 + nsRefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery(); 1.1160 + query.forget(_retval); 1.1161 + return NS_OK; 1.1162 +} 1.1163 + 1.1164 +// nsNavHistory::GetNewQueryOptions 1.1165 + 1.1166 +NS_IMETHODIMP 1.1167 +nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions **_retval) 1.1168 +{ 1.1169 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1170 + NS_ENSURE_ARG_POINTER(_retval); 1.1171 + 1.1172 + nsRefPtr<nsNavHistoryQueryOptions> queryOptions = new nsNavHistoryQueryOptions(); 1.1173 + queryOptions.forget(_retval); 1.1174 + return NS_OK; 1.1175 +} 1.1176 + 1.1177 +// nsNavHistory::ExecuteQuery 1.1178 +// 1.1179 + 1.1180 +NS_IMETHODIMP 1.1181 +nsNavHistory::ExecuteQuery(nsINavHistoryQuery *aQuery, nsINavHistoryQueryOptions *aOptions, 1.1182 + nsINavHistoryResult** _retval) 1.1183 +{ 1.1184 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1185 + NS_ENSURE_ARG(aQuery); 1.1186 + NS_ENSURE_ARG(aOptions); 1.1187 + NS_ENSURE_ARG_POINTER(_retval); 1.1188 + 1.1189 + return ExecuteQueries(&aQuery, 1, aOptions, _retval); 1.1190 +} 1.1191 + 1.1192 + 1.1193 +// nsNavHistory::ExecuteQueries 1.1194 +// 1.1195 +// This function is actually very simple, we just create the proper root node (either 1.1196 +// a bookmark folder or a complex query node) and assign it to the result. The node 1.1197 +// will then populate itself accordingly. 1.1198 +// 1.1199 +// Quick overview of query operation: When you call this function, we will construct 1.1200 +// the correct container node and set the options you give it. This node will then 1.1201 +// fill itself. Folder nodes will call nsNavBookmarks::QueryFolderChildren, and 1.1202 +// all other queries will call GetQueryResults. If these results contain other 1.1203 +// queries, those will be populated when the container is opened. 1.1204 + 1.1205 +NS_IMETHODIMP 1.1206 +nsNavHistory::ExecuteQueries(nsINavHistoryQuery** aQueries, uint32_t aQueryCount, 1.1207 + nsINavHistoryQueryOptions *aOptions, 1.1208 + nsINavHistoryResult** _retval) 1.1209 +{ 1.1210 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.1211 + NS_ENSURE_ARG(aQueries); 1.1212 + NS_ENSURE_ARG(aOptions); 1.1213 + NS_ENSURE_ARG(aQueryCount); 1.1214 + NS_ENSURE_ARG_POINTER(_retval); 1.1215 + 1.1216 + nsresult rv; 1.1217 + // concrete options 1.1218 + nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); 1.1219 + NS_ENSURE_TRUE(options, NS_ERROR_INVALID_ARG); 1.1220 + 1.1221 + // concrete queries array 1.1222 + nsCOMArray<nsNavHistoryQuery> queries; 1.1223 + for (uint32_t i = 0; i < aQueryCount; i ++) { 1.1224 + nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i], &rv); 1.1225 + NS_ENSURE_SUCCESS(rv, rv); 1.1226 + queries.AppendObject(query); 1.1227 + } 1.1228 + 1.1229 + // Create the root node. 1.1230 + nsRefPtr<nsNavHistoryContainerResultNode> rootNode; 1.1231 + int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); 1.1232 + if (folderId) { 1.1233 + // In the simple case where we're just querying children of a single 1.1234 + // bookmark folder, we can more efficiently generate results. 1.1235 + nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); 1.1236 + NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); 1.1237 + nsRefPtr<nsNavHistoryResultNode> tempRootNode; 1.1238 + rv = bookmarks->ResultNodeForContainer(folderId, options, 1.1239 + getter_AddRefs(tempRootNode)); 1.1240 + if (NS_SUCCEEDED(rv)) { 1.1241 + rootNode = tempRootNode->GetAsContainer(); 1.1242 + } 1.1243 + else { 1.1244 + NS_WARNING("Generating a generic empty node for a broken query!"); 1.1245 + // This is a perf hack to generate an empty query that skips filtering. 1.1246 + options->SetExcludeItems(true); 1.1247 + } 1.1248 + } 1.1249 + 1.1250 + if (!rootNode) { 1.1251 + // Either this is not a folder shortcut, or is a broken one. In both cases 1.1252 + // just generate a query node. 1.1253 + rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), EmptyCString(), 1.1254 + queries, options); 1.1255 + } 1.1256 + 1.1257 + // Create the result that will hold nodes. Inject batching status into it. 1.1258 + nsRefPtr<nsNavHistoryResult> result; 1.1259 + rv = nsNavHistoryResult::NewHistoryResult(aQueries, aQueryCount, options, 1.1260 + rootNode, isBatching(), 1.1261 + getter_AddRefs(result)); 1.1262 + NS_ENSURE_SUCCESS(rv, rv); 1.1263 + 1.1264 + result.forget(_retval); 1.1265 + return NS_OK; 1.1266 +} 1.1267 + 1.1268 +// determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions 1.1269 +// if this is the place query from the history menu. 1.1270 +// from browser-menubar.inc, our history menu query is: 1.1271 +// place:sort=4&maxResults=10 1.1272 +// note, any maxResult > 0 will still be considered a history menu query 1.1273 +// or if this is the place query from the "Most Visited" item in the 1.1274 +// "Smart Bookmarks" folder: place:sort=8&maxResults=10 1.1275 +// note, any maxResult > 0 will still be considered a Most Visited menu query 1.1276 +static 1.1277 +bool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.1278 + nsNavHistoryQueryOptions *aOptions, 1.1279 + uint16_t aSortMode) 1.1280 +{ 1.1281 + if (aQueries.Count() != 1) 1.1282 + return false; 1.1283 + 1.1284 + nsNavHistoryQuery *aQuery = aQueries[0]; 1.1285 + 1.1286 + if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) 1.1287 + return false; 1.1288 + 1.1289 + if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI) 1.1290 + return false; 1.1291 + 1.1292 + if (aOptions->SortingMode() != aSortMode) 1.1293 + return false; 1.1294 + 1.1295 + if (aOptions->MaxResults() <= 0) 1.1296 + return false; 1.1297 + 1.1298 + if (aOptions->ExcludeItems()) 1.1299 + return false; 1.1300 + 1.1301 + if (aOptions->IncludeHidden()) 1.1302 + return false; 1.1303 + 1.1304 + if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1) 1.1305 + return false; 1.1306 + 1.1307 + if (aQuery->BeginTime() || aQuery->BeginTimeReference()) 1.1308 + return false; 1.1309 + 1.1310 + if (aQuery->EndTime() || aQuery->EndTimeReference()) 1.1311 + return false; 1.1312 + 1.1313 + if (!aQuery->SearchTerms().IsEmpty()) 1.1314 + return false; 1.1315 + 1.1316 + if (aQuery->OnlyBookmarked()) 1.1317 + return false; 1.1318 + 1.1319 + if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty()) 1.1320 + return false; 1.1321 + 1.1322 + if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty()) 1.1323 + return false; 1.1324 + 1.1325 + if (aQuery->UriIsPrefix() || aQuery->Uri()) 1.1326 + return false; 1.1327 + 1.1328 + if (aQuery->Folders().Length() > 0) 1.1329 + return false; 1.1330 + 1.1331 + if (aQuery->Tags().Length() > 0) 1.1332 + return false; 1.1333 + 1.1334 + if (aQuery->Transitions().Length() > 0) 1.1335 + return false; 1.1336 + 1.1337 + return true; 1.1338 +} 1.1339 + 1.1340 +static 1.1341 +bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.1342 + nsNavHistoryQueryOptions *aOptions) 1.1343 +{ 1.1344 + uint16_t resultType = aOptions->ResultType(); 1.1345 + return resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS; 1.1346 +} 1.1347 + 1.1348 +// ** Helper class for ConstructQueryString **/ 1.1349 + 1.1350 +class PlacesSQLQueryBuilder 1.1351 +{ 1.1352 +public: 1.1353 + PlacesSQLQueryBuilder(const nsCString& aConditions, 1.1354 + nsNavHistoryQueryOptions* aOptions, 1.1355 + bool aUseLimit, 1.1356 + nsNavHistory::StringHash& aAddParams, 1.1357 + bool aHasSearchTerms); 1.1358 + 1.1359 + nsresult GetQueryString(nsCString& aQueryString); 1.1360 + 1.1361 +private: 1.1362 + nsresult Select(); 1.1363 + 1.1364 + nsresult SelectAsURI(); 1.1365 + nsresult SelectAsVisit(); 1.1366 + nsresult SelectAsDay(); 1.1367 + nsresult SelectAsSite(); 1.1368 + nsresult SelectAsTag(); 1.1369 + 1.1370 + nsresult Where(); 1.1371 + nsresult GroupBy(); 1.1372 + nsresult OrderBy(); 1.1373 + nsresult Limit(); 1.1374 + 1.1375 + void OrderByColumnIndexAsc(int32_t aIndex); 1.1376 + void OrderByColumnIndexDesc(int32_t aIndex); 1.1377 + // Use these if you want a case insensitive sorting. 1.1378 + void OrderByTextColumnIndexAsc(int32_t aIndex); 1.1379 + void OrderByTextColumnIndexDesc(int32_t aIndex); 1.1380 + 1.1381 + const nsCString& mConditions; 1.1382 + bool mUseLimit; 1.1383 + bool mHasSearchTerms; 1.1384 + 1.1385 + uint16_t mResultType; 1.1386 + uint16_t mQueryType; 1.1387 + bool mIncludeHidden; 1.1388 + uint16_t mSortingMode; 1.1389 + uint32_t mMaxResults; 1.1390 + 1.1391 + nsCString mQueryString; 1.1392 + nsCString mGroupBy; 1.1393 + bool mHasDateColumns; 1.1394 + bool mSkipOrderBy; 1.1395 + nsNavHistory::StringHash& mAddParams; 1.1396 +}; 1.1397 + 1.1398 +PlacesSQLQueryBuilder::PlacesSQLQueryBuilder( 1.1399 + const nsCString& aConditions, 1.1400 + nsNavHistoryQueryOptions* aOptions, 1.1401 + bool aUseLimit, 1.1402 + nsNavHistory::StringHash& aAddParams, 1.1403 + bool aHasSearchTerms) 1.1404 +: mConditions(aConditions) 1.1405 +, mUseLimit(aUseLimit) 1.1406 +, mHasSearchTerms(aHasSearchTerms) 1.1407 +, mResultType(aOptions->ResultType()) 1.1408 +, mQueryType(aOptions->QueryType()) 1.1409 +, mIncludeHidden(aOptions->IncludeHidden()) 1.1410 +, mSortingMode(aOptions->SortingMode()) 1.1411 +, mMaxResults(aOptions->MaxResults()) 1.1412 +, mSkipOrderBy(false) 1.1413 +, mAddParams(aAddParams) 1.1414 +{ 1.1415 + mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS); 1.1416 +} 1.1417 + 1.1418 +nsresult 1.1419 +PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString) 1.1420 +{ 1.1421 + nsresult rv = Select(); 1.1422 + NS_ENSURE_SUCCESS(rv, rv); 1.1423 + rv = Where(); 1.1424 + NS_ENSURE_SUCCESS(rv, rv); 1.1425 + rv = GroupBy(); 1.1426 + NS_ENSURE_SUCCESS(rv, rv); 1.1427 + rv = OrderBy(); 1.1428 + NS_ENSURE_SUCCESS(rv, rv); 1.1429 + rv = Limit(); 1.1430 + NS_ENSURE_SUCCESS(rv, rv); 1.1431 + 1.1432 + aQueryString = mQueryString; 1.1433 + return NS_OK; 1.1434 +} 1.1435 + 1.1436 +nsresult 1.1437 +PlacesSQLQueryBuilder::Select() 1.1438 +{ 1.1439 + nsresult rv; 1.1440 + 1.1441 + switch (mResultType) 1.1442 + { 1.1443 + case nsINavHistoryQueryOptions::RESULTS_AS_URI: 1.1444 + case nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS: 1.1445 + rv = SelectAsURI(); 1.1446 + NS_ENSURE_SUCCESS(rv, rv); 1.1447 + break; 1.1448 + 1.1449 + case nsINavHistoryQueryOptions::RESULTS_AS_VISIT: 1.1450 + case nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: 1.1451 + rv = SelectAsVisit(); 1.1452 + NS_ENSURE_SUCCESS(rv, rv); 1.1453 + break; 1.1454 + 1.1455 + case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY: 1.1456 + case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY: 1.1457 + rv = SelectAsDay(); 1.1458 + NS_ENSURE_SUCCESS(rv, rv); 1.1459 + break; 1.1460 + 1.1461 + case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY: 1.1462 + rv = SelectAsSite(); 1.1463 + NS_ENSURE_SUCCESS(rv, rv); 1.1464 + break; 1.1465 + 1.1466 + case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY: 1.1467 + rv = SelectAsTag(); 1.1468 + NS_ENSURE_SUCCESS(rv, rv); 1.1469 + break; 1.1470 + 1.1471 + default: 1.1472 + NS_NOTREACHED("Invalid result type"); 1.1473 + } 1.1474 + return NS_OK; 1.1475 +} 1.1476 + 1.1477 +nsresult 1.1478 +PlacesSQLQueryBuilder::SelectAsURI() 1.1479 +{ 1.1480 + nsNavHistory *history = nsNavHistory::GetHistoryService(); 1.1481 + NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); 1.1482 + nsAutoCString tagsSqlFragment; 1.1483 + 1.1484 + switch (mQueryType) { 1.1485 + case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY: 1.1486 + GetTagsSqlFragment(history->GetTagsFolder(), 1.1487 + NS_LITERAL_CSTRING("h.id"), 1.1488 + mHasSearchTerms, 1.1489 + tagsSqlFragment); 1.1490 + 1.1491 + mQueryString = NS_LITERAL_CSTRING( 1.1492 + "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " 1.1493 + "h.last_visit_date, f.url, null, null, null, null, ") + 1.1494 + tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.1495 + "FROM moz_places h " 1.1496 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.1497 + // WHERE 1 is a no-op since additonal conditions will start with AND. 1.1498 + "WHERE 1 " 1.1499 + "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " 1.1500 + "{ADDITIONAL_CONDITIONS} "); 1.1501 + break; 1.1502 + 1.1503 + case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS: 1.1504 + if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { 1.1505 + // Order-by clause is hardcoded because we need to discard duplicates 1.1506 + // in FilterResultSet. We will retain only the last modified item, 1.1507 + // so we are ordering by place id and last modified to do a faster 1.1508 + // filtering. 1.1509 + mSkipOrderBy = true; 1.1510 + 1.1511 + GetTagsSqlFragment(history->GetTagsFolder(), 1.1512 + NS_LITERAL_CSTRING("b2.fk"), 1.1513 + mHasSearchTerms, 1.1514 + tagsSqlFragment); 1.1515 + 1.1516 + mQueryString = NS_LITERAL_CSTRING( 1.1517 + "SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, " 1.1518 + "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, " 1.1519 + "b2.dateAdded, b2.lastModified, b2.parent, ") + 1.1520 + tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.1521 + "FROM moz_bookmarks b2 " 1.1522 + "JOIN (SELECT b.fk " 1.1523 + "FROM moz_bookmarks b " 1.1524 + // ADDITIONAL_CONDITIONS will filter on parent. 1.1525 + "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} " 1.1526 + ") AS seed ON b2.fk = seed.fk " 1.1527 + "JOIN moz_places h ON h.id = b2.fk " 1.1528 + "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " 1.1529 + "WHERE NOT EXISTS ( " 1.1530 + "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") + 1.1531 + nsPrintfCString("%lld", history->GetTagsFolder()) + 1.1532 + NS_LITERAL_CSTRING(") " 1.1533 + "ORDER BY b2.fk DESC, b2.lastModified DESC"); 1.1534 + } 1.1535 + else { 1.1536 + GetTagsSqlFragment(history->GetTagsFolder(), 1.1537 + NS_LITERAL_CSTRING("b.fk"), 1.1538 + mHasSearchTerms, 1.1539 + tagsSqlFragment); 1.1540 + mQueryString = NS_LITERAL_CSTRING( 1.1541 + "SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, " 1.1542 + "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " 1.1543 + "b.dateAdded, b.lastModified, b.parent, ") + 1.1544 + tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.1545 + "FROM moz_bookmarks b " 1.1546 + "JOIN moz_places h ON b.fk = h.id " 1.1547 + "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " 1.1548 + "WHERE NOT EXISTS " 1.1549 + "(SELECT id FROM moz_bookmarks " 1.1550 + "WHERE id = b.parent AND parent = ") + 1.1551 + nsPrintfCString("%lld", history->GetTagsFolder()) + 1.1552 + NS_LITERAL_CSTRING(") " 1.1553 + "{ADDITIONAL_CONDITIONS}"); 1.1554 + } 1.1555 + break; 1.1556 + 1.1557 + default: 1.1558 + return NS_ERROR_NOT_IMPLEMENTED; 1.1559 + } 1.1560 + return NS_OK; 1.1561 +} 1.1562 + 1.1563 +nsresult 1.1564 +PlacesSQLQueryBuilder::SelectAsVisit() 1.1565 +{ 1.1566 + nsNavHistory *history = nsNavHistory::GetHistoryService(); 1.1567 + NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY); 1.1568 + nsAutoCString tagsSqlFragment; 1.1569 + GetTagsSqlFragment(history->GetTagsFolder(), 1.1570 + NS_LITERAL_CSTRING("h.id"), 1.1571 + mHasSearchTerms, 1.1572 + tagsSqlFragment); 1.1573 + mQueryString = NS_LITERAL_CSTRING( 1.1574 + "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, " 1.1575 + "v.visit_date, f.url, null, null, null, null, ") + 1.1576 + tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.1577 + "FROM moz_places h " 1.1578 + "JOIN moz_historyvisits v ON h.id = v.place_id " 1.1579 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.1580 + // WHERE 1 is a no-op since additonal conditions will start with AND. 1.1581 + "WHERE 1 " 1.1582 + "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} " 1.1583 + "{ADDITIONAL_CONDITIONS} "); 1.1584 + 1.1585 + return NS_OK; 1.1586 +} 1.1587 + 1.1588 +nsresult 1.1589 +PlacesSQLQueryBuilder::SelectAsDay() 1.1590 +{ 1.1591 + mSkipOrderBy = true; 1.1592 + 1.1593 + // Sort child queries based on sorting mode if it's provided, otherwise 1.1594 + // fallback to default sort by title ascending. 1.1595 + uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING; 1.1596 + if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE && 1.1597 + mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY) 1.1598 + sortingMode = mSortingMode; 1.1599 + 1.1600 + uint16_t resultType = 1.1601 + mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ? 1.1602 + (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI : 1.1603 + (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY; 1.1604 + 1.1605 + // beginTime will become the node's time property, we don't use endTime 1.1606 + // because it could overlap, and we use time to sort containers and find 1.1607 + // insert position in a result. 1.1608 + mQueryString = nsPrintfCString( 1.1609 + "SELECT null, " 1.1610 + "'place:type=%ld&sort=%ld&beginTime='||beginTime||'&endTime='||endTime, " 1.1611 + "dayTitle, null, null, beginTime, null, null, null, null, null, null " 1.1612 + "FROM (", // TOUTER BEGIN 1.1613 + resultType, 1.1614 + sortingMode); 1.1615 + 1.1616 + nsNavHistory *history = nsNavHistory::GetHistoryService(); 1.1617 + NS_ENSURE_STATE(history); 1.1618 + 1.1619 + int32_t daysOfHistory = history->GetDaysOfHistory(); 1.1620 + for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) { 1.1621 + nsAutoCString dateName; 1.1622 + // Timeframes are calculated as BeginTime <= container < EndTime. 1.1623 + // Notice times can't be relative to now, since to recognize a query we 1.1624 + // must ensure it won't change based on the time it is built. 1.1625 + // So, to select till now, we really select till start of tomorrow, that is 1.1626 + // a fixed timestamp. 1.1627 + // These are used as limits for the inside containers. 1.1628 + nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime; 1.1629 + // These are used to query if the container should be visible. 1.1630 + nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime; 1.1631 + switch(i) { 1.1632 + case 0: 1.1633 + // Today 1.1634 + history->GetStringFromName( 1.1635 + MOZ_UTF16("finduri-AgeInDays-is-0"), dateName); 1.1636 + // From start of today 1.1637 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1638 + "(strftime('%s','now','localtime','start of day','utc')*1000000)"); 1.1639 + // To now (tomorrow) 1.1640 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1641 + "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); 1.1642 + // Search for the same timeframe. 1.1643 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1644 + sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; 1.1645 + break; 1.1646 + case 1: 1.1647 + // Yesterday 1.1648 + history->GetStringFromName( 1.1649 + MOZ_UTF16("finduri-AgeInDays-is-1"), dateName); 1.1650 + // From start of yesterday 1.1651 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1652 + "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)"); 1.1653 + // To start of today 1.1654 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1655 + "(strftime('%s','now','localtime','start of day','utc')*1000000)"); 1.1656 + // Search for the same timeframe. 1.1657 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1658 + sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; 1.1659 + break; 1.1660 + case 2: 1.1661 + // Last 7 days 1.1662 + history->GetAgeInDaysString(7, 1.1663 + MOZ_UTF16("finduri-AgeInDays-last-is"), dateName); 1.1664 + // From start of 7 days ago 1.1665 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1666 + "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); 1.1667 + // To now (tomorrow) 1.1668 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1669 + "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); 1.1670 + // This is an overlapped container, but we show it only if there are 1.1671 + // visits older than yesterday. 1.1672 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1673 + sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( 1.1674 + "(strftime('%s','now','localtime','start of day','-2 days','utc')*1000000)"); 1.1675 + break; 1.1676 + case 3: 1.1677 + // This month 1.1678 + history->GetStringFromName( 1.1679 + MOZ_UTF16("finduri-AgeInMonths-is-0"), dateName); 1.1680 + // From start of this month 1.1681 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1682 + "(strftime('%s','now','localtime','start of month','utc')*1000000)"); 1.1683 + // To now (tomorrow) 1.1684 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1685 + "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)"); 1.1686 + // This is an overlapped container, but we show it only if there are 1.1687 + // visits older than 7 days ago. 1.1688 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1689 + sqlFragmentSearchEndTime = NS_LITERAL_CSTRING( 1.1690 + "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)"); 1.1691 + break; 1.1692 + default: 1.1693 + if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) { 1.1694 + // Older than 6 months 1.1695 + history->GetAgeInDaysString(6, 1.1696 + MOZ_UTF16("finduri-AgeInMonths-isgreater"), dateName); 1.1697 + // From start of epoch 1.1698 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1699 + "(datetime(0, 'unixepoch')*1000000)"); 1.1700 + // To start of 6 months ago ( 5 months + this month). 1.1701 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1702 + "(strftime('%s','now','localtime','start of month','-5 months','utc')*1000000)"); 1.1703 + // Search for the same timeframe. 1.1704 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1705 + sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; 1.1706 + break; 1.1707 + } 1.1708 + int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM; 1.1709 + // Previous months' titles are month's name if inside this year, 1.1710 + // month's name and year for previous years. 1.1711 + PRExplodedTime tm; 1.1712 + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm); 1.1713 + uint16_t currentYear = tm.tm_year; 1.1714 + // Set day before month, setting month without day could cause issues. 1.1715 + // For example setting month to February when today is 30, since 1.1716 + // February has not 30 days, will return March instead. 1.1717 + // Also, we use day 2 instead of day 1, so that the GMT month is always 1.1718 + // the same as the local month. (Bug 603002) 1.1719 + tm.tm_mday = 2; 1.1720 + tm.tm_month -= MonthIndex; 1.1721 + // Notice we use GMTParameters because we just want to get the first 1.1722 + // day of each month. Using LocalTimeParameters would instead force us 1.1723 + // to apply a DST correction that we don't really need here. 1.1724 + PR_NormalizeTime(&tm, PR_GMTParameters); 1.1725 + // If the container is for a past year, add the year to its title, 1.1726 + // otherwise just show the month name. 1.1727 + // Note that tm_month starts from 0, while we need a 1-based index. 1.1728 + if (tm.tm_year < currentYear) { 1.1729 + history->GetMonthYear(tm.tm_month + 1, tm.tm_year, dateName); 1.1730 + } 1.1731 + else { 1.1732 + history->GetMonthName(tm.tm_month + 1, dateName); 1.1733 + } 1.1734 + 1.1735 + // From start of MonthIndex + 1 months ago 1.1736 + sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING( 1.1737 + "(strftime('%s','now','localtime','start of month','-"); 1.1738 + sqlFragmentContainerBeginTime.AppendInt(MonthIndex); 1.1739 + sqlFragmentContainerBeginTime.Append(NS_LITERAL_CSTRING( 1.1740 + " months','utc')*1000000)")); 1.1741 + // To start of MonthIndex months ago 1.1742 + sqlFragmentContainerEndTime = NS_LITERAL_CSTRING( 1.1743 + "(strftime('%s','now','localtime','start of month','-"); 1.1744 + sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1); 1.1745 + sqlFragmentContainerEndTime.Append(NS_LITERAL_CSTRING( 1.1746 + " months','utc')*1000000)")); 1.1747 + // Search for the same timeframe. 1.1748 + sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime; 1.1749 + sqlFragmentSearchEndTime = sqlFragmentContainerEndTime; 1.1750 + break; 1.1751 + } 1.1752 + 1.1753 + nsPrintfCString dateParam("dayTitle%d", i); 1.1754 + mAddParams.Put(dateParam, dateName); 1.1755 + 1.1756 + nsPrintfCString dayRange( 1.1757 + "SELECT :%s AS dayTitle, " 1.1758 + "%s AS beginTime, " 1.1759 + "%s AS endTime " 1.1760 + "WHERE EXISTS ( " 1.1761 + "SELECT id FROM moz_historyvisits " 1.1762 + "WHERE visit_date >= %s " 1.1763 + "AND visit_date < %s " 1.1764 + "AND visit_type NOT IN (0,%d,%d) " 1.1765 + "{QUERY_OPTIONS_VISITS} " 1.1766 + "LIMIT 1 " 1.1767 + ") ", 1.1768 + dateParam.get(), 1.1769 + sqlFragmentContainerBeginTime.get(), 1.1770 + sqlFragmentContainerEndTime.get(), 1.1771 + sqlFragmentSearchBeginTime.get(), 1.1772 + sqlFragmentSearchEndTime.get(), 1.1773 + nsINavHistoryService::TRANSITION_EMBED, 1.1774 + nsINavHistoryService::TRANSITION_FRAMED_LINK 1.1775 + ); 1.1776 + 1.1777 + mQueryString.Append(dayRange); 1.1778 + 1.1779 + if (i < HISTORY_DATE_CONT_NUM(daysOfHistory)) 1.1780 + mQueryString.Append(NS_LITERAL_CSTRING(" UNION ALL ")); 1.1781 + } 1.1782 + 1.1783 + mQueryString.Append(NS_LITERAL_CSTRING(") ")); // TOUTER END 1.1784 + 1.1785 + return NS_OK; 1.1786 +} 1.1787 + 1.1788 +nsresult 1.1789 +PlacesSQLQueryBuilder::SelectAsSite() 1.1790 +{ 1.1791 + nsAutoCString localFiles; 1.1792 + 1.1793 + nsNavHistory *history = nsNavHistory::GetHistoryService(); 1.1794 + NS_ENSURE_STATE(history); 1.1795 + 1.1796 + history->GetStringFromName(MOZ_UTF16("localhost"), localFiles); 1.1797 + mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles); 1.1798 + 1.1799 + // If there are additional conditions the query has to join on visits too. 1.1800 + nsAutoCString visitsJoin; 1.1801 + nsAutoCString additionalConditions; 1.1802 + nsAutoCString timeConstraints; 1.1803 + if (!mConditions.IsEmpty()) { 1.1804 + visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id "); 1.1805 + additionalConditions.AssignLiteral("{QUERY_OPTIONS_VISITS} " 1.1806 + "{QUERY_OPTIONS_PLACES} " 1.1807 + "{ADDITIONAL_CONDITIONS} "); 1.1808 + timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||" 1.1809 + "'&endTime='||:end_time"); 1.1810 + } 1.1811 + 1.1812 + mQueryString = nsPrintfCString( 1.1813 + "SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, " 1.1814 + ":localhost, :localhost, null, null, null, null, null, null, null " 1.1815 + "WHERE EXISTS ( " 1.1816 + "SELECT h.id FROM moz_places h " 1.1817 + "%s " 1.1818 + "WHERE h.hidden = 0 " 1.1819 + "AND h.visit_count > 0 " 1.1820 + "AND h.url BETWEEN 'file://' AND 'file:/~' " 1.1821 + "%s " 1.1822 + "LIMIT 1 " 1.1823 + ") " 1.1824 + "UNION ALL " 1.1825 + "SELECT null, " 1.1826 + "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, " 1.1827 + "host, host, null, null, null, null, null, null, null " 1.1828 + "FROM ( " 1.1829 + "SELECT get_unreversed_host(h.rev_host) AS host " 1.1830 + "FROM moz_places h " 1.1831 + "%s " 1.1832 + "WHERE h.hidden = 0 " 1.1833 + "AND h.rev_host <> '.' " 1.1834 + "AND h.visit_count > 0 " 1.1835 + "%s " 1.1836 + "GROUP BY h.rev_host " 1.1837 + "ORDER BY host ASC " 1.1838 + ") ", 1.1839 + nsINavHistoryQueryOptions::RESULTS_AS_URI, 1.1840 + mSortingMode, 1.1841 + timeConstraints.get(), 1.1842 + visitsJoin.get(), 1.1843 + additionalConditions.get(), 1.1844 + nsINavHistoryQueryOptions::RESULTS_AS_URI, 1.1845 + mSortingMode, 1.1846 + timeConstraints.get(), 1.1847 + visitsJoin.get(), 1.1848 + additionalConditions.get() 1.1849 + ); 1.1850 + 1.1851 + return NS_OK; 1.1852 +} 1.1853 + 1.1854 +nsresult 1.1855 +PlacesSQLQueryBuilder::SelectAsTag() 1.1856 +{ 1.1857 + nsNavHistory *history = nsNavHistory::GetHistoryService(); 1.1858 + NS_ENSURE_STATE(history); 1.1859 + 1.1860 + // This allows sorting by date fields what is not possible with 1.1861 + // other history queries. 1.1862 + mHasDateColumns = true; 1.1863 + 1.1864 + mQueryString = nsPrintfCString( 1.1865 + "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', " 1.1866 + "title, null, null, null, null, null, dateAdded, " 1.1867 + "lastModified, null, null, null " 1.1868 + "FROM moz_bookmarks " 1.1869 + "WHERE parent = %lld", 1.1870 + nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS, 1.1871 + nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS, 1.1872 + history->GetTagsFolder() 1.1873 + ); 1.1874 + 1.1875 + return NS_OK; 1.1876 +} 1.1877 + 1.1878 +nsresult 1.1879 +PlacesSQLQueryBuilder::Where() 1.1880 +{ 1.1881 + 1.1882 + // Set query options 1.1883 + nsAutoCString additionalVisitsConditions; 1.1884 + nsAutoCString additionalPlacesConditions; 1.1885 + 1.1886 + if (!mIncludeHidden) { 1.1887 + additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 "); 1.1888 + } 1.1889 + 1.1890 + if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) { 1.1891 + // last_visit_date is updated for any kind of visit, so it's a good 1.1892 + // indicator whether the page has visits. 1.1893 + additionalPlacesConditions += NS_LITERAL_CSTRING( 1.1894 + "AND last_visit_date NOTNULL " 1.1895 + ); 1.1896 + } 1.1897 + 1.1898 + if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI && 1.1899 + !additionalVisitsConditions.IsEmpty()) { 1.1900 + // URI results don't join on visits. 1.1901 + nsAutoCString tmp = additionalVisitsConditions; 1.1902 + additionalVisitsConditions = "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id "; 1.1903 + additionalVisitsConditions.Append(tmp); 1.1904 + additionalVisitsConditions.Append("LIMIT 1)"); 1.1905 + } 1.1906 + 1.1907 + mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}", 1.1908 + additionalVisitsConditions.get()); 1.1909 + mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}", 1.1910 + additionalPlacesConditions.get()); 1.1911 + 1.1912 + // If we used WHERE already, we inject the conditions 1.1913 + // in place of {ADDITIONAL_CONDITIONS} 1.1914 + if (mQueryString.Find("{ADDITIONAL_CONDITIONS}", 0) != kNotFound) { 1.1915 + nsAutoCString innerCondition; 1.1916 + // If we have condition AND it 1.1917 + if (!mConditions.IsEmpty()) { 1.1918 + innerCondition = " AND ("; 1.1919 + innerCondition += mConditions; 1.1920 + innerCondition += ")"; 1.1921 + } 1.1922 + mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}", 1.1923 + innerCondition.get()); 1.1924 + 1.1925 + } else if (!mConditions.IsEmpty()) { 1.1926 + 1.1927 + mQueryString += "WHERE "; 1.1928 + mQueryString += mConditions; 1.1929 + 1.1930 + } 1.1931 + return NS_OK; 1.1932 +} 1.1933 + 1.1934 +nsresult 1.1935 +PlacesSQLQueryBuilder::GroupBy() 1.1936 +{ 1.1937 + mQueryString += mGroupBy; 1.1938 + return NS_OK; 1.1939 +} 1.1940 + 1.1941 +nsresult 1.1942 +PlacesSQLQueryBuilder::OrderBy() 1.1943 +{ 1.1944 + if (mSkipOrderBy) 1.1945 + return NS_OK; 1.1946 + 1.1947 + // Sort clause: we will sort later, but if it comes out of the DB sorted, 1.1948 + // our later sort will be basically free. The DB can sort these for free 1.1949 + // most of the time anyway, because it has indices over these items. 1.1950 + switch(mSortingMode) 1.1951 + { 1.1952 + case nsINavHistoryQueryOptions::SORT_BY_NONE: 1.1953 + // Ensure sorting does not change based on tables status. 1.1954 + if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) { 1.1955 + if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS) 1.1956 + mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC "); 1.1957 + else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) 1.1958 + mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC "); 1.1959 + } 1.1960 + break; 1.1961 + case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING: 1.1962 + case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING: 1.1963 + // If the user wants few results, we limit them by date, necessitating 1.1964 + // a sort by date here (see the IDL definition for maxResults). 1.1965 + // Otherwise we will do actual sorting by title, but since we could need 1.1966 + // to special sort for some locale we will repeat a second sorting at the 1.1967 + // end in nsNavHistoryResult, that should be faster since the list will be 1.1968 + // almost ordered. 1.1969 + if (mMaxResults > 0) 1.1970 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); 1.1971 + else if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING) 1.1972 + OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title); 1.1973 + else 1.1974 + OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title); 1.1975 + break; 1.1976 + case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING: 1.1977 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate); 1.1978 + break; 1.1979 + case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING: 1.1980 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate); 1.1981 + break; 1.1982 + case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING: 1.1983 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL); 1.1984 + break; 1.1985 + case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING: 1.1986 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL); 1.1987 + break; 1.1988 + case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING: 1.1989 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount); 1.1990 + break; 1.1991 + case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING: 1.1992 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount); 1.1993 + break; 1.1994 + case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING: 1.1995 + if (mHasDateColumns) 1.1996 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded); 1.1997 + break; 1.1998 + case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING: 1.1999 + if (mHasDateColumns) 1.2000 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded); 1.2001 + break; 1.2002 + case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING: 1.2003 + if (mHasDateColumns) 1.2004 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified); 1.2005 + break; 1.2006 + case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING: 1.2007 + if (mHasDateColumns) 1.2008 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified); 1.2009 + break; 1.2010 + case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING: 1.2011 + case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING: 1.2012 + case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING: 1.2013 + case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING: 1.2014 + break; // Sort later in nsNavHistoryQueryResultNode::FillChildren() 1.2015 + case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING: 1.2016 + OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency); 1.2017 + break; 1.2018 + case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING: 1.2019 + OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency); 1.2020 + break; 1.2021 + default: 1.2022 + NS_NOTREACHED("Invalid sorting mode"); 1.2023 + } 1.2024 + return NS_OK; 1.2025 +} 1.2026 + 1.2027 +void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex) 1.2028 +{ 1.2029 + mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex+1); 1.2030 +} 1.2031 + 1.2032 +void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex) 1.2033 +{ 1.2034 + mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex+1); 1.2035 +} 1.2036 + 1.2037 +void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex) 1.2038 +{ 1.2039 + mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC", 1.2040 + aIndex+1); 1.2041 +} 1.2042 + 1.2043 +void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex) 1.2044 +{ 1.2045 + mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC", 1.2046 + aIndex+1); 1.2047 +} 1.2048 + 1.2049 +nsresult 1.2050 +PlacesSQLQueryBuilder::Limit() 1.2051 +{ 1.2052 + if (mUseLimit && mMaxResults > 0) { 1.2053 + mQueryString += NS_LITERAL_CSTRING(" LIMIT "); 1.2054 + mQueryString.AppendInt(mMaxResults); 1.2055 + mQueryString.AppendLiteral(" "); 1.2056 + } 1.2057 + return NS_OK; 1.2058 +} 1.2059 + 1.2060 +nsresult 1.2061 +nsNavHistory::ConstructQueryString( 1.2062 + const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.2063 + nsNavHistoryQueryOptions* aOptions, 1.2064 + nsCString& queryString, 1.2065 + bool& aParamsPresent, 1.2066 + nsNavHistory::StringHash& aAddParams) 1.2067 +{ 1.2068 + // For information about visit_type see nsINavHistoryService.idl. 1.2069 + // visitType == 0 is undefined (see bug #375777 for details). 1.2070 + // Some sites, especially Javascript-heavy ones, load things in frames to 1.2071 + // display them, resulting in a lot of these entries. This is the reason 1.2072 + // why such visits are filtered out. 1.2073 + nsresult rv; 1.2074 + aParamsPresent = false; 1.2075 + 1.2076 + int32_t sortingMode = aOptions->SortingMode(); 1.2077 + NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE && 1.2078 + sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING, 1.2079 + "Invalid sortingMode found while building query!"); 1.2080 + 1.2081 + bool hasSearchTerms = false; 1.2082 + for (int32_t i = 0; i < aQueries.Count() && !hasSearchTerms; i++) { 1.2083 + aQueries[i]->GetHasSearchTerms(&hasSearchTerms); 1.2084 + } 1.2085 + 1.2086 + nsAutoCString tagsSqlFragment; 1.2087 + GetTagsSqlFragment(GetTagsFolder(), 1.2088 + NS_LITERAL_CSTRING("h.id"), 1.2089 + hasSearchTerms, 1.2090 + tagsSqlFragment); 1.2091 + 1.2092 + if (IsOptimizableHistoryQuery(aQueries, aOptions, 1.2093 + nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) || 1.2094 + IsOptimizableHistoryQuery(aQueries, aOptions, 1.2095 + nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) { 1.2096 + // Generate an optimized query for the history menu and most visited 1.2097 + // smart bookmark. 1.2098 + queryString = NS_LITERAL_CSTRING( 1.2099 + "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, " 1.2100 + "f.url, null, null, null, null, ") + 1.2101 + tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.2102 + "FROM moz_places h " 1.2103 + "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id " 1.2104 + "WHERE h.hidden = 0 " 1.2105 + "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id " 1.2106 + "AND visit_type NOT IN ") + 1.2107 + nsPrintfCString("(0,%d,%d) ", 1.2108 + nsINavHistoryService::TRANSITION_EMBED, 1.2109 + nsINavHistoryService::TRANSITION_FRAMED_LINK) + 1.2110 + NS_LITERAL_CSTRING("LIMIT 1) " 1.2111 + "{QUERY_OPTIONS} " 1.2112 + ); 1.2113 + 1.2114 + queryString.Append(NS_LITERAL_CSTRING("ORDER BY ")); 1.2115 + if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) 1.2116 + queryString.Append(NS_LITERAL_CSTRING("last_visit_date DESC ")); 1.2117 + else 1.2118 + queryString.Append(NS_LITERAL_CSTRING("visit_count DESC ")); 1.2119 + 1.2120 + queryString.Append(NS_LITERAL_CSTRING("LIMIT ")); 1.2121 + queryString.AppendInt(aOptions->MaxResults()); 1.2122 + 1.2123 + nsAutoCString additionalQueryOptions; 1.2124 + 1.2125 + queryString.ReplaceSubstring("{QUERY_OPTIONS}", 1.2126 + additionalQueryOptions.get()); 1.2127 + return NS_OK; 1.2128 + } 1.2129 + 1.2130 + nsAutoCString conditions; 1.2131 + for (int32_t i = 0; i < aQueries.Count(); i++) { 1.2132 + nsCString queryClause; 1.2133 + rv = QueryToSelectClause(aQueries[i], aOptions, i, &queryClause); 1.2134 + NS_ENSURE_SUCCESS(rv, rv); 1.2135 + if (! queryClause.IsEmpty()) { 1.2136 + aParamsPresent = true; 1.2137 + if (! conditions.IsEmpty()) // exists previous clause: multiple ones are ORed 1.2138 + conditions += NS_LITERAL_CSTRING(" OR "); 1.2139 + conditions += NS_LITERAL_CSTRING("(") + queryClause + 1.2140 + NS_LITERAL_CSTRING(")"); 1.2141 + } 1.2142 + } 1.2143 + 1.2144 + // Determine whether we can push maxResults constraints into the queries 1.2145 + // as LIMIT, or if we need to do result count clamping later 1.2146 + // using FilterResultSet() 1.2147 + bool useLimitClause = !NeedToFilterResultSet(aQueries, aOptions); 1.2148 + 1.2149 + PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions, 1.2150 + useLimitClause, aAddParams, 1.2151 + hasSearchTerms); 1.2152 + rv = queryStringBuilder.GetQueryString(queryString); 1.2153 + NS_ENSURE_SUCCESS(rv, rv); 1.2154 + 1.2155 + return NS_OK; 1.2156 +} 1.2157 + 1.2158 +PLDHashOperator BindAdditionalParameter(nsNavHistory::StringHash::KeyType aParamName, 1.2159 + nsCString aParamValue, 1.2160 + void* aStatement) 1.2161 +{ 1.2162 + mozIStorageStatement* stmt = static_cast<mozIStorageStatement*>(aStatement); 1.2163 + 1.2164 + nsresult rv = stmt->BindUTF8StringByName(aParamName, aParamValue); 1.2165 + if (NS_FAILED(rv)) 1.2166 + return PL_DHASH_STOP; 1.2167 + 1.2168 + return PL_DHASH_NEXT; 1.2169 +} 1.2170 + 1.2171 +// nsNavHistory::GetQueryResults 1.2172 +// 1.2173 +// Call this to get the results from a complex query. This is used by 1.2174 +// nsNavHistoryQueryResultNode to populate its children. For simple bookmark 1.2175 +// queries, use nsNavBookmarks::QueryFolderChildren. 1.2176 +// 1.2177 +// THIS DOES NOT DO SORTING. You will need to sort the container yourself 1.2178 +// when you get the results. This is because sorting depends on tree 1.2179 +// statistics that will be built from the perspective of the tree. See 1.2180 +// nsNavHistoryQueryResultNode::FillChildren 1.2181 +// 1.2182 +// FIXME: This only does keyword searching for the first query, and does 1.2183 +// it ANDed with the all the rest of the queries. 1.2184 + 1.2185 +nsresult 1.2186 +nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode, 1.2187 + const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.2188 + nsNavHistoryQueryOptions *aOptions, 1.2189 + nsCOMArray<nsNavHistoryResultNode>* aResults) 1.2190 +{ 1.2191 + NS_ENSURE_ARG_POINTER(aOptions); 1.2192 + NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty"); 1.2193 + if (! aQueries.Count()) 1.2194 + return NS_ERROR_INVALID_ARG; 1.2195 + 1.2196 + nsCString queryString; 1.2197 + bool paramsPresent = false; 1.2198 + nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); 1.2199 + nsresult rv = ConstructQueryString(aQueries, aOptions, queryString, 1.2200 + paramsPresent, addParams); 1.2201 + NS_ENSURE_SUCCESS(rv,rv); 1.2202 + 1.2203 + // create statement 1.2204 + nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString); 1.2205 +#ifdef DEBUG 1.2206 + if (!statement) { 1.2207 + nsAutoCString lastErrorString; 1.2208 + (void)mDB->MainConn()->GetLastErrorString(lastErrorString); 1.2209 + int32_t lastError = 0; 1.2210 + (void)mDB->MainConn()->GetLastError(&lastError); 1.2211 + printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", 1.2212 + queryString.get(), lastError, lastErrorString.get()); 1.2213 + } 1.2214 +#endif 1.2215 + NS_ENSURE_STATE(statement); 1.2216 + mozStorageStatementScoper scoper(statement); 1.2217 + 1.2218 + if (paramsPresent) { 1.2219 + // bind parameters 1.2220 + int32_t i; 1.2221 + for (i = 0; i < aQueries.Count(); i++) { 1.2222 + rv = BindQueryClauseParameters(statement, i, aQueries[i], aOptions); 1.2223 + NS_ENSURE_SUCCESS(rv, rv); 1.2224 + } 1.2225 + } 1.2226 + 1.2227 + addParams.EnumerateRead(BindAdditionalParameter, statement.get()); 1.2228 + 1.2229 + // Optimize the case where there is no need for any post-query filtering. 1.2230 + if (NeedToFilterResultSet(aQueries, aOptions)) { 1.2231 + // Generate the top-level results. 1.2232 + nsCOMArray<nsNavHistoryResultNode> toplevel; 1.2233 + rv = ResultsAsList(statement, aOptions, &toplevel); 1.2234 + NS_ENSURE_SUCCESS(rv, rv); 1.2235 + 1.2236 + FilterResultSet(aResultNode, toplevel, aResults, aQueries, aOptions); 1.2237 + } else { 1.2238 + rv = ResultsAsList(statement, aOptions, aResults); 1.2239 + NS_ENSURE_SUCCESS(rv, rv); 1.2240 + } 1.2241 + 1.2242 + return NS_OK; 1.2243 +} 1.2244 + 1.2245 + 1.2246 +// nsNavHistory::AddObserver 1.2247 + 1.2248 +NS_IMETHODIMP 1.2249 +nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak) 1.2250 +{ 1.2251 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2252 + NS_ENSURE_ARG(aObserver); 1.2253 + 1.2254 + return mObservers.AppendWeakElement(aObserver, aOwnsWeak); 1.2255 +} 1.2256 + 1.2257 + 1.2258 +// nsNavHistory::RemoveObserver 1.2259 + 1.2260 +NS_IMETHODIMP 1.2261 +nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver) 1.2262 +{ 1.2263 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2264 + NS_ENSURE_ARG(aObserver); 1.2265 + 1.2266 + return mObservers.RemoveWeakElement(aObserver); 1.2267 +} 1.2268 + 1.2269 +// nsNavHistory::BeginUpdateBatch 1.2270 +// See RunInBatchMode 1.2271 +nsresult 1.2272 +nsNavHistory::BeginUpdateBatch() 1.2273 +{ 1.2274 + if (mBatchLevel++ == 0) { 1.2275 + mBatchDBTransaction = new mozStorageTransaction(mDB->MainConn(), false); 1.2276 + 1.2277 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.2278 + nsINavHistoryObserver, OnBeginUpdateBatch()); 1.2279 + } 1.2280 + return NS_OK; 1.2281 +} 1.2282 + 1.2283 +// nsNavHistory::EndUpdateBatch 1.2284 +nsresult 1.2285 +nsNavHistory::EndUpdateBatch() 1.2286 +{ 1.2287 + if (--mBatchLevel == 0) { 1.2288 + if (mBatchDBTransaction) { 1.2289 + DebugOnly<nsresult> rv = mBatchDBTransaction->Commit(); 1.2290 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Batch failed to commit transaction"); 1.2291 + delete mBatchDBTransaction; 1.2292 + mBatchDBTransaction = nullptr; 1.2293 + } 1.2294 + 1.2295 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.2296 + nsINavHistoryObserver, OnEndUpdateBatch()); 1.2297 + } 1.2298 + return NS_OK; 1.2299 +} 1.2300 + 1.2301 +NS_IMETHODIMP 1.2302 +nsNavHistory::RunInBatchMode(nsINavHistoryBatchCallback* aCallback, 1.2303 + nsISupports* aUserData) 1.2304 +{ 1.2305 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2306 + NS_ENSURE_ARG(aCallback); 1.2307 + 1.2308 + UpdateBatchScoper batch(*this); 1.2309 + return aCallback->RunBatched(aUserData); 1.2310 +} 1.2311 + 1.2312 +NS_IMETHODIMP 1.2313 +nsNavHistory::GetHistoryDisabled(bool *_retval) 1.2314 +{ 1.2315 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2316 + NS_ENSURE_ARG_POINTER(_retval); 1.2317 + 1.2318 + *_retval = IsHistoryDisabled(); 1.2319 + return NS_OK; 1.2320 +} 1.2321 + 1.2322 +// Browser history ************************************************************* 1.2323 + 1.2324 + 1.2325 +// nsNavHistory::RemovePagesInternal 1.2326 +// 1.2327 +// Deletes a list of placeIds from history. 1.2328 +// This is an internal method used by RemovePages, RemovePagesFromHost and 1.2329 +// RemovePagesByTimeframe. 1.2330 +// Takes a comma separated list of place ids. 1.2331 +// This method does not do any observer notification. 1.2332 + 1.2333 +nsresult 1.2334 +nsNavHistory::RemovePagesInternal(const nsCString& aPlaceIdsQueryString) 1.2335 +{ 1.2336 + // Return early if there is nothing to delete. 1.2337 + if (aPlaceIdsQueryString.IsEmpty()) 1.2338 + return NS_OK; 1.2339 + 1.2340 + mozStorageTransaction transaction(mDB->MainConn(), false); 1.2341 + 1.2342 + // Delete all visits for the specified place ids. 1.2343 + nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( 1.2344 + NS_LITERAL_CSTRING( 1.2345 + "DELETE FROM moz_historyvisits WHERE place_id IN (") + 1.2346 + aPlaceIdsQueryString + 1.2347 + NS_LITERAL_CSTRING(")") 1.2348 + ); 1.2349 + NS_ENSURE_SUCCESS(rv, rv); 1.2350 + 1.2351 + rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString); 1.2352 + NS_ENSURE_SUCCESS(rv, rv); 1.2353 + 1.2354 + // Invalidate the cached value for whether there's history or not. 1.2355 + mDaysOfHistory = -1; 1.2356 + 1.2357 + return transaction.Commit(); 1.2358 +} 1.2359 + 1.2360 + 1.2361 +/** 1.2362 + * Performs cleanup on places that just had all their visits removed, including 1.2363 + * deletion of those places. This is an internal method used by 1.2364 + * RemovePagesInternal and RemoveVisitsByTimeframe. This method does not 1.2365 + * execute in a transaction, so callers should make sure they begin one if 1.2366 + * needed. 1.2367 + * 1.2368 + * @param aPlaceIdsQueryString 1.2369 + * A comma-separated list of place IDs, each of which just had all its 1.2370 + * visits removed 1.2371 + */ 1.2372 +nsresult 1.2373 +nsNavHistory::CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString) 1.2374 +{ 1.2375 + // Return early if there is nothing to delete. 1.2376 + if (aPlaceIdsQueryString.IsEmpty()) 1.2377 + return NS_OK; 1.2378 + 1.2379 + // Collect about-to-be-deleted URIs to notify onDeleteURI. 1.2380 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( 1.2381 + "SELECT h.id, h.url, h.guid, " 1.2382 + "(SUBSTR(h.url, 1, 6) <> 'place:' " 1.2383 + " AND NOT EXISTS (SELECT b.id FROM moz_bookmarks b " 1.2384 + "WHERE b.fk = h.id LIMIT 1)) as whole_entry " 1.2385 + "FROM moz_places h " 1.2386 + "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(")") 1.2387 + ); 1.2388 + NS_ENSURE_STATE(stmt); 1.2389 + mozStorageStatementScoper scoper(stmt); 1.2390 + 1.2391 + nsCString filteredPlaceIds; 1.2392 + nsCOMArray<nsIURI> URIs; 1.2393 + nsTArray<nsCString> GUIDs; 1.2394 + bool hasMore; 1.2395 + while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) { 1.2396 + int64_t placeId; 1.2397 + nsresult rv = stmt->GetInt64(0, &placeId); 1.2398 + NS_ENSURE_SUCCESS(rv, rv); 1.2399 + nsAutoCString URLString; 1.2400 + rv = stmt->GetUTF8String(1, URLString); 1.2401 + nsCString guid; 1.2402 + rv = stmt->GetUTF8String(2, guid); 1.2403 + int32_t wholeEntry; 1.2404 + rv = stmt->GetInt32(3, &wholeEntry); 1.2405 + nsCOMPtr<nsIURI> uri; 1.2406 + rv = NS_NewURI(getter_AddRefs(uri), URLString); 1.2407 + NS_ENSURE_SUCCESS(rv, rv); 1.2408 + if (wholeEntry) { 1.2409 + if (!filteredPlaceIds.IsEmpty()) { 1.2410 + filteredPlaceIds.AppendLiteral(","); 1.2411 + } 1.2412 + filteredPlaceIds.AppendInt(placeId); 1.2413 + URIs.AppendObject(uri); 1.2414 + GUIDs.AppendElement(guid); 1.2415 + } 1.2416 + else { 1.2417 + // Notify that we will delete all visits for this page, but not the page 1.2418 + // itself, since it's bookmarked or a place: query. 1.2419 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.2420 + nsINavHistoryObserver, 1.2421 + OnDeleteVisits(uri, 0, guid, nsINavHistoryObserver::REASON_DELETED, 0)); 1.2422 + } 1.2423 + } 1.2424 + 1.2425 + // if the entry is not bookmarked and is not a place: uri 1.2426 + // then we can remove it from moz_places. 1.2427 + // Note that we do NOT delete favicons. Any unreferenced favicons will be 1.2428 + // deleted next time the browser is shut down. 1.2429 + nsresult rv = mDB->MainConn()->ExecuteSimpleSQL( 1.2430 + NS_LITERAL_CSTRING( 1.2431 + "DELETE FROM moz_places WHERE id IN ( " 1.2432 + ) + filteredPlaceIds + NS_LITERAL_CSTRING( 1.2433 + ") " 1.2434 + ) 1.2435 + ); 1.2436 + NS_ENSURE_SUCCESS(rv, rv); 1.2437 + 1.2438 + // Invalidate frecencies of touched places, since they need recalculation. 1.2439 + rv = invalidateFrecencies(aPlaceIdsQueryString); 1.2440 + NS_ENSURE_SUCCESS(rv, rv); 1.2441 + 1.2442 + // Finally notify about the removed URIs. 1.2443 + for (int32_t i = 0; i < URIs.Count(); ++i) { 1.2444 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.2445 + nsINavHistoryObserver, 1.2446 + OnDeleteURI(URIs[i], GUIDs[i], nsINavHistoryObserver::REASON_DELETED)); 1.2447 + } 1.2448 + 1.2449 + return NS_OK; 1.2450 +} 1.2451 + 1.2452 + 1.2453 +// nsNavHistory::RemovePages 1.2454 +// 1.2455 +// Removes a bunch of uris from history. 1.2456 +// Has better performance than RemovePage when deleting a lot of history. 1.2457 +// We don't do duplicates removal, URIs array should be cleaned-up before. 1.2458 + 1.2459 +NS_IMETHODIMP 1.2460 +nsNavHistory::RemovePages(nsIURI **aURIs, uint32_t aLength) 1.2461 +{ 1.2462 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2463 + NS_ENSURE_ARG(aURIs); 1.2464 + 1.2465 + nsresult rv; 1.2466 + // build a list of place ids to delete 1.2467 + nsCString deletePlaceIdsQueryString; 1.2468 + for (uint32_t i = 0; i < aLength; i++) { 1.2469 + int64_t placeId; 1.2470 + nsAutoCString guid; 1.2471 + rv = GetIdForPage(aURIs[i], &placeId, guid); 1.2472 + NS_ENSURE_SUCCESS(rv, rv); 1.2473 + if (placeId != 0) { 1.2474 + if (!deletePlaceIdsQueryString.IsEmpty()) 1.2475 + deletePlaceIdsQueryString.AppendLiteral(","); 1.2476 + deletePlaceIdsQueryString.AppendInt(placeId); 1.2477 + } 1.2478 + } 1.2479 + 1.2480 + UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers 1.2481 + 1.2482 + rv = RemovePagesInternal(deletePlaceIdsQueryString); 1.2483 + NS_ENSURE_SUCCESS(rv, rv); 1.2484 + 1.2485 + // Clear the registered embed visits. 1.2486 + clearEmbedVisits(); 1.2487 + 1.2488 + return NS_OK; 1.2489 +} 1.2490 + 1.2491 + 1.2492 +// nsNavHistory::RemovePage 1.2493 +// 1.2494 +// Removes all visits and the main history entry for the given URI. 1.2495 +// Silently fails if we have no knowledge of the page. 1.2496 + 1.2497 +NS_IMETHODIMP 1.2498 +nsNavHistory::RemovePage(nsIURI *aURI) 1.2499 +{ 1.2500 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2501 + NS_ENSURE_ARG(aURI); 1.2502 + 1.2503 + // Build a list of place ids to delete. 1.2504 + int64_t placeId; 1.2505 + nsAutoCString guid; 1.2506 + nsresult rv = GetIdForPage(aURI, &placeId, guid); 1.2507 + NS_ENSURE_SUCCESS(rv, rv); 1.2508 + if (placeId == 0) { 1.2509 + return NS_OK; 1.2510 + } 1.2511 + nsAutoCString deletePlaceIdQueryString; 1.2512 + deletePlaceIdQueryString.AppendInt(placeId); 1.2513 + 1.2514 + rv = RemovePagesInternal(deletePlaceIdQueryString); 1.2515 + NS_ENSURE_SUCCESS(rv, rv); 1.2516 + 1.2517 + // Clear the registered embed visits. 1.2518 + clearEmbedVisits(); 1.2519 + 1.2520 + return NS_OK; 1.2521 +} 1.2522 + 1.2523 + 1.2524 +// nsNavHistory::RemovePagesFromHost 1.2525 +// 1.2526 +// This function will delete all history information about pages from a 1.2527 +// given host. If aEntireDomain is set, we will also delete pages from 1.2528 +// sub hosts (so if we are passed in "microsoft.com" we delete 1.2529 +// "www.microsoft.com", "msdn.microsoft.com", etc.). An empty host name 1.2530 +// means local files and anything else with no host name. You can also pass 1.2531 +// in the localized "(local files)" title given to you from a history query. 1.2532 +// 1.2533 +// Silently fails if we have no knowledge of the host. 1.2534 +// 1.2535 +// This sends onBeginUpdateBatch/onEndUpdateBatch to observers 1.2536 + 1.2537 +NS_IMETHODIMP 1.2538 +nsNavHistory::RemovePagesFromHost(const nsACString& aHost, bool aEntireDomain) 1.2539 +{ 1.2540 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2541 + 1.2542 + nsresult rv; 1.2543 + // Local files don't have any host name. We don't want to delete all files in 1.2544 + // history when we get passed an empty string, so force to exact match 1.2545 + if (aHost.IsEmpty()) 1.2546 + aEntireDomain = false; 1.2547 + 1.2548 + // translate "(local files)" to an empty host name 1.2549 + // be sure to use the TitleForDomain to get the localized name 1.2550 + nsCString localFiles; 1.2551 + TitleForDomain(EmptyCString(), localFiles); 1.2552 + nsAutoString host16; 1.2553 + if (!aHost.Equals(localFiles)) 1.2554 + CopyUTF8toUTF16(aHost, host16); 1.2555 + 1.2556 + // nsISupports version of the host string for passing to observers 1.2557 + nsCOMPtr<nsISupportsString> hostSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); 1.2558 + NS_ENSURE_SUCCESS(rv, rv); 1.2559 + rv = hostSupports->SetData(host16); 1.2560 + NS_ENSURE_SUCCESS(rv, rv); 1.2561 + 1.2562 + // see BindQueryClauseParameters for how this host selection works 1.2563 + nsAutoString revHostDot; 1.2564 + GetReversedHostname(host16, revHostDot); 1.2565 + NS_ASSERTION(revHostDot[revHostDot.Length() - 1] == '.', "Invalid rev. host"); 1.2566 + nsAutoString revHostSlash(revHostDot); 1.2567 + revHostSlash.Truncate(revHostSlash.Length() - 1); 1.2568 + revHostSlash.Append(NS_LITERAL_STRING("/")); 1.2569 + 1.2570 + // build condition string based on host selection type 1.2571 + nsAutoCString conditionString; 1.2572 + if (aEntireDomain) 1.2573 + conditionString.AssignLiteral("rev_host >= ?1 AND rev_host < ?2 "); 1.2574 + else 1.2575 + conditionString.AssignLiteral("rev_host = ?1 "); 1.2576 + 1.2577 + // create statement depending on delete type 1.2578 + nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement( 1.2579 + NS_LITERAL_CSTRING("SELECT id FROM moz_places WHERE ") + conditionString 1.2580 + ); 1.2581 + NS_ENSURE_STATE(statement); 1.2582 + mozStorageStatementScoper scoper(statement); 1.2583 + 1.2584 + rv = statement->BindStringByIndex(0, revHostDot); 1.2585 + NS_ENSURE_SUCCESS(rv, rv); 1.2586 + if (aEntireDomain) { 1.2587 + rv = statement->BindStringByIndex(1, revHostSlash); 1.2588 + NS_ENSURE_SUCCESS(rv, rv); 1.2589 + } 1.2590 + 1.2591 + nsCString hostPlaceIds; 1.2592 + bool hasMore = false; 1.2593 + while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { 1.2594 + if (!hostPlaceIds.IsEmpty()) 1.2595 + hostPlaceIds.AppendLiteral(","); 1.2596 + int64_t placeId; 1.2597 + rv = statement->GetInt64(0, &placeId); 1.2598 + NS_ENSURE_SUCCESS(rv, rv); 1.2599 + hostPlaceIds.AppendInt(placeId); 1.2600 + } 1.2601 + 1.2602 + // force a full refresh calling onEndUpdateBatch (will call Refresh()) 1.2603 + UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers 1.2604 + 1.2605 + rv = RemovePagesInternal(hostPlaceIds); 1.2606 + NS_ENSURE_SUCCESS(rv, rv); 1.2607 + 1.2608 + // Clear the registered embed visits. 1.2609 + clearEmbedVisits(); 1.2610 + 1.2611 + return NS_OK; 1.2612 +} 1.2613 + 1.2614 + 1.2615 +// nsNavHistory::RemovePagesByTimeframe 1.2616 +// 1.2617 +// This function will delete all history information about 1.2618 +// pages for a given timeframe. 1.2619 +// Limits are included: aBeginTime <= timeframe <= aEndTime 1.2620 +// 1.2621 +// This method sends onBeginUpdateBatch/onEndUpdateBatch to observers 1.2622 + 1.2623 +NS_IMETHODIMP 1.2624 +nsNavHistory::RemovePagesByTimeframe(PRTime aBeginTime, PRTime aEndTime) 1.2625 +{ 1.2626 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2627 + 1.2628 + nsresult rv; 1.2629 + // build a list of place ids to delete 1.2630 + nsCString deletePlaceIdsQueryString; 1.2631 + 1.2632 + // we only need to know if a place has a visit into the given timeframe 1.2633 + // this query is faster than actually selecting in moz_historyvisits 1.2634 + nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( 1.2635 + "SELECT h.id FROM moz_places h WHERE " 1.2636 + "EXISTS " 1.2637 + "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id " 1.2638 + "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)" 1.2639 + ); 1.2640 + NS_ENSURE_STATE(selectByTime); 1.2641 + mozStorageStatementScoper selectByTimeScoper(selectByTime); 1.2642 + 1.2643 + rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); 1.2644 + NS_ENSURE_SUCCESS(rv, rv); 1.2645 + rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); 1.2646 + NS_ENSURE_SUCCESS(rv, rv); 1.2647 + 1.2648 + bool hasMore = false; 1.2649 + while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { 1.2650 + int64_t placeId; 1.2651 + rv = selectByTime->GetInt64(0, &placeId); 1.2652 + NS_ENSURE_SUCCESS(rv, rv); 1.2653 + if (placeId != 0) { 1.2654 + if (!deletePlaceIdsQueryString.IsEmpty()) 1.2655 + deletePlaceIdsQueryString.AppendLiteral(","); 1.2656 + deletePlaceIdsQueryString.AppendInt(placeId); 1.2657 + } 1.2658 + } 1.2659 + 1.2660 + // force a full refresh calling onEndUpdateBatch (will call Refresh()) 1.2661 + UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers 1.2662 + 1.2663 + rv = RemovePagesInternal(deletePlaceIdsQueryString); 1.2664 + NS_ENSURE_SUCCESS(rv, rv); 1.2665 + 1.2666 + // Clear the registered embed visits. 1.2667 + clearEmbedVisits(); 1.2668 + 1.2669 + return NS_OK; 1.2670 +} 1.2671 + 1.2672 + 1.2673 +/** 1.2674 + * Removes all visits in a given timeframe. Limits are included: 1.2675 + * aBeginTime <= timeframe <= aEndTime. Any place that becomes unvisited 1.2676 + * as a result will also be deleted. 1.2677 + * 1.2678 + * Note that removal is performed in batch, so observers will not be 1.2679 + * notified of individual places that are deleted. Instead they will be 1.2680 + * notified onBeginUpdateBatch and onEndUpdateBatch. 1.2681 + * 1.2682 + * @param aBeginTime 1.2683 + * The start of the timeframe, inclusive 1.2684 + * @param aEndTime 1.2685 + * The end of the timeframe, inclusive 1.2686 + */ 1.2687 +NS_IMETHODIMP 1.2688 +nsNavHistory::RemoveVisitsByTimeframe(PRTime aBeginTime, PRTime aEndTime) 1.2689 +{ 1.2690 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2691 + 1.2692 + nsresult rv; 1.2693 + 1.2694 + // Build a list of place IDs whose visits fall entirely within the timespan. 1.2695 + // These places will be deleted by the call to CleanupPlacesOnVisitsDelete 1.2696 + // below. 1.2697 + nsCString deletePlaceIdsQueryString; 1.2698 + { 1.2699 + nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement( 1.2700 + "SELECT place_id " 1.2701 + "FROM moz_historyvisits " 1.2702 + "WHERE :from_date <= visit_date AND visit_date <= :to_date " 1.2703 + "EXCEPT " 1.2704 + "SELECT place_id " 1.2705 + "FROM moz_historyvisits " 1.2706 + "WHERE visit_date < :from_date OR :to_date < visit_date" 1.2707 + ); 1.2708 + NS_ENSURE_STATE(selectByTime); 1.2709 + mozStorageStatementScoper selectByTimeScoper(selectByTime); 1.2710 + rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); 1.2711 + NS_ENSURE_SUCCESS(rv, rv); 1.2712 + rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); 1.2713 + NS_ENSURE_SUCCESS(rv, rv); 1.2714 + 1.2715 + bool hasMore = false; 1.2716 + while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) { 1.2717 + int64_t placeId; 1.2718 + rv = selectByTime->GetInt64(0, &placeId); 1.2719 + NS_ENSURE_SUCCESS(rv, rv); 1.2720 + // placeId should not be <= 0, but be defensive. 1.2721 + if (placeId > 0) { 1.2722 + if (!deletePlaceIdsQueryString.IsEmpty()) 1.2723 + deletePlaceIdsQueryString.AppendLiteral(","); 1.2724 + deletePlaceIdsQueryString.AppendInt(placeId); 1.2725 + } 1.2726 + } 1.2727 + } 1.2728 + 1.2729 + // force a full refresh calling onEndUpdateBatch (will call Refresh()) 1.2730 + UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers 1.2731 + 1.2732 + mozStorageTransaction transaction(mDB->MainConn(), false); 1.2733 + 1.2734 + // Delete all visits within the timeframe. 1.2735 + nsCOMPtr<mozIStorageStatement> deleteVisitsStmt = mDB->GetStatement( 1.2736 + "DELETE FROM moz_historyvisits " 1.2737 + "WHERE :from_date <= visit_date AND visit_date <= :to_date" 1.2738 + ); 1.2739 + NS_ENSURE_STATE(deleteVisitsStmt); 1.2740 + mozStorageStatementScoper deletevisitsScoper(deleteVisitsStmt); 1.2741 + 1.2742 + rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime); 1.2743 + NS_ENSURE_SUCCESS(rv, rv); 1.2744 + rv = deleteVisitsStmt->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime); 1.2745 + NS_ENSURE_SUCCESS(rv, rv); 1.2746 + rv = deleteVisitsStmt->Execute(); 1.2747 + NS_ENSURE_SUCCESS(rv, rv); 1.2748 + 1.2749 + rv = CleanupPlacesOnVisitsDelete(deletePlaceIdsQueryString); 1.2750 + NS_ENSURE_SUCCESS(rv, rv); 1.2751 + 1.2752 + rv = transaction.Commit(); 1.2753 + NS_ENSURE_SUCCESS(rv, rv); 1.2754 + 1.2755 + // Clear the registered embed visits. 1.2756 + clearEmbedVisits(); 1.2757 + 1.2758 + // Invalidate the cached value for whether there's history or not. 1.2759 + mDaysOfHistory = -1; 1.2760 + 1.2761 + return NS_OK; 1.2762 +} 1.2763 + 1.2764 + 1.2765 +// nsNavHistory::RemoveAllPages 1.2766 +// 1.2767 +// This function is used to clear history. 1.2768 + 1.2769 +NS_IMETHODIMP 1.2770 +nsNavHistory::RemoveAllPages() 1.2771 +{ 1.2772 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2773 + 1.2774 + nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(NS_LITERAL_CSTRING( 1.2775 + "DELETE FROM moz_historyvisits" 1.2776 + )); 1.2777 + NS_ENSURE_SUCCESS(rv, rv); 1.2778 + 1.2779 + // Clear the registered embed visits. 1.2780 + clearEmbedVisits(); 1.2781 + 1.2782 + // Update the cached value for whether there's history or not. 1.2783 + mDaysOfHistory = 0; 1.2784 + 1.2785 + // Expiration will take care of orphans. 1.2786 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.2787 + nsINavHistoryObserver, OnClearHistory()); 1.2788 + 1.2789 + // Invalidate frecencies for the remaining places. This must happen 1.2790 + // after the notification to ensure it runs enqueued to expiration. 1.2791 + rv = invalidateFrecencies(EmptyCString()); 1.2792 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to fix invalid frecencies"); 1.2793 + 1.2794 + return NS_OK; 1.2795 +} 1.2796 + 1.2797 + 1.2798 +// Call this method before visiting a URL in order to help determine the 1.2799 +// transition type of the visit. 1.2800 +// 1.2801 +// @see MarkPageAsFollowedBookmark 1.2802 + 1.2803 +NS_IMETHODIMP 1.2804 +nsNavHistory::MarkPageAsTyped(nsIURI *aURI) 1.2805 +{ 1.2806 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2807 + NS_ENSURE_ARG(aURI); 1.2808 + 1.2809 + // don't add when history is disabled 1.2810 + if (IsHistoryDisabled()) 1.2811 + return NS_OK; 1.2812 + 1.2813 + nsAutoCString uriString; 1.2814 + nsresult rv = aURI->GetSpec(uriString); 1.2815 + NS_ENSURE_SUCCESS(rv, rv); 1.2816 + 1.2817 + // if URL is already in the typed queue, then we need to remove the old one 1.2818 + int64_t unusedEventTime; 1.2819 + if (mRecentTyped.Get(uriString, &unusedEventTime)) 1.2820 + mRecentTyped.Remove(uriString); 1.2821 + 1.2822 + if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) 1.2823 + ExpireNonrecentEvents(&mRecentTyped); 1.2824 + 1.2825 + mRecentTyped.Put(uriString, GetNow()); 1.2826 + return NS_OK; 1.2827 +} 1.2828 + 1.2829 + 1.2830 +// Call this method before visiting a URL in order to help determine the 1.2831 +// transition type of the visit. 1.2832 +// 1.2833 +// @see MarkPageAsTyped 1.2834 + 1.2835 +NS_IMETHODIMP 1.2836 +nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI) 1.2837 +{ 1.2838 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2839 + NS_ENSURE_ARG(aURI); 1.2840 + 1.2841 + // don't add when history is disabled 1.2842 + if (IsHistoryDisabled()) 1.2843 + return NS_OK; 1.2844 + 1.2845 + nsAutoCString uriString; 1.2846 + nsresult rv = aURI->GetSpec(uriString); 1.2847 + NS_ENSURE_SUCCESS(rv, rv); 1.2848 + 1.2849 + // if URL is already in the links queue, then we need to remove the old one 1.2850 + int64_t unusedEventTime; 1.2851 + if (mRecentLink.Get(uriString, &unusedEventTime)) 1.2852 + mRecentLink.Remove(uriString); 1.2853 + 1.2854 + if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH) 1.2855 + ExpireNonrecentEvents(&mRecentLink); 1.2856 + 1.2857 + mRecentLink.Put(uriString, GetNow()); 1.2858 + return NS_OK; 1.2859 +} 1.2860 + 1.2861 + 1.2862 +// nsNavHistory::SetCharsetForURI 1.2863 +// 1.2864 +// Sets the character-set for a URI. 1.2865 +// If aCharset is empty remove character-set annotation for aURI. 1.2866 + 1.2867 +NS_IMETHODIMP 1.2868 +nsNavHistory::SetCharsetForURI(nsIURI* aURI, 1.2869 + const nsAString& aCharset) 1.2870 +{ 1.2871 + PLACES_WARN_DEPRECATED(); 1.2872 + 1.2873 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2874 + NS_ENSURE_ARG(aURI); 1.2875 + 1.2876 + nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); 1.2877 + NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); 1.2878 + 1.2879 + if (aCharset.IsEmpty()) { 1.2880 + // remove the current page character-set annotation 1.2881 + nsresult rv = annosvc->RemovePageAnnotation(aURI, CHARSET_ANNO); 1.2882 + NS_ENSURE_SUCCESS(rv, rv); 1.2883 + } 1.2884 + else { 1.2885 + // Set page character-set annotation, silently overwrite if already exists 1.2886 + nsresult rv = annosvc->SetPageAnnotationString(aURI, CHARSET_ANNO, 1.2887 + aCharset, 0, 1.2888 + nsAnnotationService::EXPIRE_NEVER); 1.2889 + if (rv == NS_ERROR_INVALID_ARG) { 1.2890 + // We don't have this page. Silently fail. 1.2891 + return NS_OK; 1.2892 + } 1.2893 + else if (NS_FAILED(rv)) 1.2894 + return rv; 1.2895 + } 1.2896 + 1.2897 + return NS_OK; 1.2898 +} 1.2899 + 1.2900 + 1.2901 +// nsNavHistory::GetCharsetForURI 1.2902 +// 1.2903 +// Get the last saved character-set for a URI. 1.2904 + 1.2905 +NS_IMETHODIMP 1.2906 +nsNavHistory::GetCharsetForURI(nsIURI* aURI, 1.2907 + nsAString& aCharset) 1.2908 +{ 1.2909 + PLACES_WARN_DEPRECATED(); 1.2910 + 1.2911 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2912 + NS_ENSURE_ARG(aURI); 1.2913 + 1.2914 + nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); 1.2915 + NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); 1.2916 + 1.2917 + nsAutoString charset; 1.2918 + nsresult rv = annosvc->GetPageAnnotationString(aURI, CHARSET_ANNO, aCharset); 1.2919 + if (NS_FAILED(rv)) { 1.2920 + // be sure to return an empty string if character-set is not found 1.2921 + aCharset.Truncate(); 1.2922 + } 1.2923 + return NS_OK; 1.2924 +} 1.2925 + 1.2926 + 1.2927 +NS_IMETHODIMP 1.2928 +nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle) 1.2929 +{ 1.2930 + PLACES_WARN_DEPRECATED(); 1.2931 + 1.2932 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.2933 + NS_ENSURE_ARG(aURI); 1.2934 + 1.2935 + aTitle.Truncate(0); 1.2936 + 1.2937 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement( 1.2938 + "SELECT id, url, title, rev_host, visit_count, guid " 1.2939 + "FROM moz_places " 1.2940 + "WHERE url = :page_url " 1.2941 + ); 1.2942 + NS_ENSURE_STATE(stmt); 1.2943 + mozStorageStatementScoper scoper(stmt); 1.2944 + 1.2945 + nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); 1.2946 + NS_ENSURE_SUCCESS(rv, rv); 1.2947 + 1.2948 + bool hasResults = false; 1.2949 + rv = stmt->ExecuteStep(&hasResults); 1.2950 + NS_ENSURE_SUCCESS(rv, rv); 1.2951 + 1.2952 + if (!hasResults) { 1.2953 + aTitle.SetIsVoid(true); 1.2954 + return NS_OK; // Not found, return a void string. 1.2955 + } 1.2956 + 1.2957 + rv = stmt->GetString(2, aTitle); 1.2958 + NS_ENSURE_SUCCESS(rv, rv); 1.2959 + 1.2960 + return NS_OK; 1.2961 +} 1.2962 + 1.2963 + 1.2964 +//////////////////////////////////////////////////////////////////////////////// 1.2965 +//// mozIStorageVacuumParticipant 1.2966 + 1.2967 +NS_IMETHODIMP 1.2968 +nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection) 1.2969 +{ 1.2970 + return GetDBConnection(_DBConnection); 1.2971 +} 1.2972 + 1.2973 + 1.2974 +NS_IMETHODIMP 1.2975 +nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize) 1.2976 +{ 1.2977 + NS_ENSURE_STATE(mDB); 1.2978 + NS_ENSURE_STATE(mDB->MainConn()); 1.2979 + return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize); 1.2980 +} 1.2981 + 1.2982 + 1.2983 +NS_IMETHODIMP 1.2984 +nsNavHistory::OnBeginVacuum(bool* _vacuumGranted) 1.2985 +{ 1.2986 + // TODO: Check if we have to deny the vacuum in some heavy-load case. 1.2987 + // We could maybe want to do that during batches? 1.2988 + *_vacuumGranted = true; 1.2989 + return NS_OK; 1.2990 +} 1.2991 + 1.2992 + 1.2993 +NS_IMETHODIMP 1.2994 +nsNavHistory::OnEndVacuum(bool aSucceeded) 1.2995 +{ 1.2996 + NS_WARN_IF_FALSE(aSucceeded, "Places.sqlite vacuum failed."); 1.2997 + return NS_OK; 1.2998 +} 1.2999 + 1.3000 + 1.3001 +//////////////////////////////////////////////////////////////////////////////// 1.3002 +//// nsPIPlacesDatabase 1.3003 + 1.3004 +NS_IMETHODIMP 1.3005 +nsNavHistory::GetDBConnection(mozIStorageConnection **_DBConnection) 1.3006 +{ 1.3007 + NS_ENSURE_ARG_POINTER(_DBConnection); 1.3008 + nsRefPtr<mozIStorageConnection> connection = mDB->MainConn(); 1.3009 + connection.forget(_DBConnection); 1.3010 + 1.3011 + return NS_OK; 1.3012 +} 1.3013 + 1.3014 +NS_IMETHODIMP 1.3015 +nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries, 1.3016 + uint32_t aQueryCount, 1.3017 + nsINavHistoryQueryOptions* aOptions, 1.3018 + mozIStorageStatementCallback* aCallback, 1.3019 + mozIStoragePendingStatement** _stmt) 1.3020 +{ 1.3021 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.3022 + NS_ENSURE_ARG(aQueries); 1.3023 + NS_ENSURE_ARG(aOptions); 1.3024 + NS_ENSURE_ARG(aCallback); 1.3025 + NS_ENSURE_ARG_POINTER(_stmt); 1.3026 + 1.3027 + nsCOMArray<nsNavHistoryQuery> queries; 1.3028 + for (uint32_t i = 0; i < aQueryCount; i ++) { 1.3029 + nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i]); 1.3030 + NS_ENSURE_STATE(query); 1.3031 + queries.AppendObject(query); 1.3032 + } 1.3033 + NS_ENSURE_ARG_MIN(queries.Count(), 1); 1.3034 + 1.3035 + nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions); 1.3036 + NS_ENSURE_ARG(options); 1.3037 + 1.3038 + nsCString queryString; 1.3039 + bool paramsPresent = false; 1.3040 + nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_MAX); 1.3041 + nsresult rv = ConstructQueryString(queries, options, queryString, 1.3042 + paramsPresent, addParams); 1.3043 + NS_ENSURE_SUCCESS(rv,rv); 1.3044 + 1.3045 + nsCOMPtr<mozIStorageAsyncStatement> statement = 1.3046 + mDB->GetAsyncStatement(queryString); 1.3047 + NS_ENSURE_STATE(statement); 1.3048 + 1.3049 +#ifdef DEBUG 1.3050 + if (NS_FAILED(rv)) { 1.3051 + nsAutoCString lastErrorString; 1.3052 + (void)mDB->MainConn()->GetLastErrorString(lastErrorString); 1.3053 + int32_t lastError = 0; 1.3054 + (void)mDB->MainConn()->GetLastError(&lastError); 1.3055 + printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n", 1.3056 + queryString.get(), lastError, lastErrorString.get()); 1.3057 + } 1.3058 +#endif 1.3059 + NS_ENSURE_SUCCESS(rv, rv); 1.3060 + 1.3061 + if (paramsPresent) { 1.3062 + // bind parameters 1.3063 + int32_t i; 1.3064 + for (i = 0; i < queries.Count(); i++) { 1.3065 + rv = BindQueryClauseParameters(statement, i, queries[i], options); 1.3066 + NS_ENSURE_SUCCESS(rv, rv); 1.3067 + } 1.3068 + } 1.3069 + addParams.EnumerateRead(BindAdditionalParameter, statement.get()); 1.3070 + 1.3071 + rv = statement->ExecuteAsync(aCallback, _stmt); 1.3072 + NS_ENSURE_SUCCESS(rv, rv); 1.3073 + 1.3074 + return NS_OK; 1.3075 +} 1.3076 + 1.3077 + 1.3078 +// nsPIPlacesHistoryListenersNotifier ****************************************** 1.3079 + 1.3080 +NS_IMETHODIMP 1.3081 +nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime, 1.3082 + bool aWholeEntry, const nsACString& aGUID, 1.3083 + uint16_t aReason, uint32_t aTransitionType) 1.3084 +{ 1.3085 + // Invalidate the cached value for whether there's history or not. 1.3086 + mDaysOfHistory = -1; 1.3087 + 1.3088 + MOZ_ASSERT(!aGUID.IsEmpty()); 1.3089 + if (aWholeEntry) { 1.3090 + // Notify our observers that the page has been removed. 1.3091 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.3092 + nsINavHistoryObserver, OnDeleteURI(aURI, aGUID, aReason)); 1.3093 + } 1.3094 + else { 1.3095 + // Notify our observers that some visits for the page have been removed. 1.3096 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.3097 + nsINavHistoryObserver, 1.3098 + OnDeleteVisits(aURI, aVisitTime, aGUID, aReason, 1.3099 + aTransitionType)); 1.3100 + } 1.3101 + 1.3102 + return NS_OK; 1.3103 +} 1.3104 + 1.3105 +//////////////////////////////////////////////////////////////////////////////// 1.3106 +//// nsIObserver 1.3107 + 1.3108 +NS_IMETHODIMP 1.3109 +nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic, 1.3110 + const char16_t *aData) 1.3111 +{ 1.3112 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.3113 + 1.3114 + if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 || 1.3115 + strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0) { 1.3116 + // These notifications are used by tests to simulate a Places shutdown. 1.3117 + // They should just be forwarded to the Database handle. 1.3118 + mDB->Observe(aSubject, aTopic, aData); 1.3119 + } 1.3120 + 1.3121 + else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) { 1.3122 + // Don't even try to notify observers from this point on, the category 1.3123 + // cache would init services that could try to use our APIs. 1.3124 + mCanNotify = false; 1.3125 + } 1.3126 + 1.3127 +#ifdef MOZ_XUL 1.3128 + else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) { 1.3129 + nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject); 1.3130 + if (!input) 1.3131 + return NS_OK; 1.3132 + 1.3133 + // If the source is a private window, don't add any input history. 1.3134 + bool isPrivate; 1.3135 + nsresult rv = input->GetInPrivateContext(&isPrivate); 1.3136 + NS_ENSURE_SUCCESS(rv, rv); 1.3137 + if (isPrivate) 1.3138 + return NS_OK; 1.3139 + 1.3140 + nsCOMPtr<nsIAutoCompletePopup> popup; 1.3141 + input->GetPopup(getter_AddRefs(popup)); 1.3142 + if (!popup) 1.3143 + return NS_OK; 1.3144 + 1.3145 + nsCOMPtr<nsIAutoCompleteController> controller; 1.3146 + input->GetController(getter_AddRefs(controller)); 1.3147 + if (!controller) 1.3148 + return NS_OK; 1.3149 + 1.3150 + // Don't bother if the popup is closed 1.3151 + bool open; 1.3152 + rv = popup->GetPopupOpen(&open); 1.3153 + NS_ENSURE_SUCCESS(rv, rv); 1.3154 + if (!open) 1.3155 + return NS_OK; 1.3156 + 1.3157 + // Ignore if nothing selected from the popup 1.3158 + int32_t selectedIndex; 1.3159 + rv = popup->GetSelectedIndex(&selectedIndex); 1.3160 + NS_ENSURE_SUCCESS(rv, rv); 1.3161 + if (selectedIndex == -1) 1.3162 + return NS_OK; 1.3163 + 1.3164 + rv = AutoCompleteFeedback(selectedIndex, controller); 1.3165 + NS_ENSURE_SUCCESS(rv, rv); 1.3166 + } 1.3167 + 1.3168 +#endif 1.3169 + else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) { 1.3170 + LoadPrefs(); 1.3171 + } 1.3172 + 1.3173 + else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) { 1.3174 + (void)DecayFrecency(); 1.3175 + } 1.3176 + 1.3177 + return NS_OK; 1.3178 +} 1.3179 + 1.3180 + 1.3181 +namespace { 1.3182 + 1.3183 +class DecayFrecencyCallback : public AsyncStatementTelemetryTimer 1.3184 +{ 1.3185 +public: 1.3186 + DecayFrecencyCallback() 1.3187 + : AsyncStatementTelemetryTimer(Telemetry::PLACES_IDLE_FRECENCY_DECAY_TIME_MS) 1.3188 + { 1.3189 + } 1.3190 + 1.3191 + NS_IMETHOD HandleCompletion(uint16_t aReason) 1.3192 + { 1.3193 + (void)AsyncStatementTelemetryTimer::HandleCompletion(aReason); 1.3194 + if (aReason == REASON_FINISHED) { 1.3195 + nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); 1.3196 + NS_ENSURE_STATE(navHistory); 1.3197 + navHistory->NotifyManyFrecenciesChanged(); 1.3198 + } 1.3199 + return NS_OK; 1.3200 + } 1.3201 +}; 1.3202 + 1.3203 +} // anonymous namespace 1.3204 + 1.3205 +nsresult 1.3206 +nsNavHistory::DecayFrecency() 1.3207 +{ 1.3208 + nsresult rv = FixInvalidFrecencies(); 1.3209 + NS_ENSURE_SUCCESS(rv, rv); 1.3210 + 1.3211 + // Globally decay places frecency rankings to estimate reduced frecency 1.3212 + // values of pages that haven't been visited for a while, i.e., they do 1.3213 + // not get an updated frecency. A scaling factor of .975 results in .5 the 1.3214 + // original value after 28 days. 1.3215 + // When changing the scaling factor, ensure that the barrier in 1.3216 + // moz_places_afterupdate_frecency_trigger still ignores these changes. 1.3217 + nsCOMPtr<mozIStorageAsyncStatement> decayFrecency = mDB->GetAsyncStatement( 1.3218 + "UPDATE moz_places SET frecency = ROUND(frecency * .975) " 1.3219 + "WHERE frecency > 0" 1.3220 + ); 1.3221 + NS_ENSURE_STATE(decayFrecency); 1.3222 + 1.3223 + // Decay potentially unused adaptive entries (e.g. those that are at 1) 1.3224 + // to allow better chances for new entries that will start at 1. 1.3225 + nsCOMPtr<mozIStorageAsyncStatement> decayAdaptive = mDB->GetAsyncStatement( 1.3226 + "UPDATE moz_inputhistory SET use_count = use_count * .975" 1.3227 + ); 1.3228 + NS_ENSURE_STATE(decayAdaptive); 1.3229 + 1.3230 + // Delete any adaptive entries that won't help in ordering anymore. 1.3231 + nsCOMPtr<mozIStorageAsyncStatement> deleteAdaptive = mDB->GetAsyncStatement( 1.3232 + "DELETE FROM moz_inputhistory WHERE use_count < .01" 1.3233 + ); 1.3234 + NS_ENSURE_STATE(deleteAdaptive); 1.3235 + 1.3236 + mozIStorageBaseStatement *stmts[] = { 1.3237 + decayFrecency.get(), 1.3238 + decayAdaptive.get(), 1.3239 + deleteAdaptive.get() 1.3240 + }; 1.3241 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.3242 + nsRefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback(); 1.3243 + rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, 1.3244 + getter_AddRefs(ps)); 1.3245 + NS_ENSURE_SUCCESS(rv, rv); 1.3246 + 1.3247 + return NS_OK; 1.3248 +} 1.3249 + 1.3250 + 1.3251 +// Query stuff ***************************************************************** 1.3252 + 1.3253 +// Helper class for QueryToSelectClause 1.3254 +// 1.3255 +// This class helps to build part of the WHERE clause. It supports 1.3256 +// multiple queries by appending the query index to the parameter name. 1.3257 +// For the query with index 0 the parameter name is not altered what 1.3258 +// allows using this parameter in other situations (see SelectAsSite). 1.3259 + 1.3260 +class ConditionBuilder 1.3261 +{ 1.3262 +public: 1.3263 + 1.3264 + ConditionBuilder(int32_t aQueryIndex): mQueryIndex(aQueryIndex) 1.3265 + { } 1.3266 + 1.3267 + ConditionBuilder& Condition(const char* aStr) 1.3268 + { 1.3269 + if (!mClause.IsEmpty()) 1.3270 + mClause.AppendLiteral(" AND "); 1.3271 + Str(aStr); 1.3272 + return *this; 1.3273 + } 1.3274 + 1.3275 + ConditionBuilder& Str(const char* aStr) 1.3276 + { 1.3277 + mClause.Append(' '); 1.3278 + mClause.Append(aStr); 1.3279 + mClause.Append(' '); 1.3280 + return *this; 1.3281 + } 1.3282 + 1.3283 + ConditionBuilder& Param(const char* aParam) 1.3284 + { 1.3285 + mClause.Append(' '); 1.3286 + if (!mQueryIndex) 1.3287 + mClause.Append(aParam); 1.3288 + else 1.3289 + mClause += nsPrintfCString("%s%d", aParam, mQueryIndex); 1.3290 + 1.3291 + mClause.Append(' '); 1.3292 + return *this; 1.3293 + } 1.3294 + 1.3295 + void GetClauseString(nsCString& aResult) 1.3296 + { 1.3297 + aResult = mClause; 1.3298 + } 1.3299 + 1.3300 +private: 1.3301 + 1.3302 + int32_t mQueryIndex; 1.3303 + nsCString mClause; 1.3304 +}; 1.3305 + 1.3306 + 1.3307 +// nsNavHistory::QueryToSelectClause 1.3308 +// 1.3309 +// THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters 1.3310 +// 1.3311 +// I don't check return values from the query object getters because there's 1.3312 +// no way for those to fail. 1.3313 + 1.3314 +nsresult 1.3315 +nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const 1.3316 + nsNavHistoryQueryOptions* aOptions, 1.3317 + int32_t aQueryIndex, 1.3318 + nsCString* aClause) 1.3319 +{ 1.3320 + bool hasIt; 1.3321 + bool excludeQueries = aOptions->ExcludeQueries(); 1.3322 + 1.3323 + ConditionBuilder clause(aQueryIndex); 1.3324 + 1.3325 + if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) || 1.3326 + (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) { 1.3327 + clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits " 1.3328 + "WHERE place_id = h.id"); 1.3329 + // begin time 1.3330 + if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) 1.3331 + clause.Condition("visit_date >=").Param(":begin_time"); 1.3332 + // end time 1.3333 + if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) 1.3334 + clause.Condition("visit_date <=").Param(":end_time"); 1.3335 + clause.Str(" LIMIT 1)"); 1.3336 + } 1.3337 + 1.3338 + // search terms 1.3339 + bool hasSearchTerms; 1.3340 + if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasSearchTerms)) && hasSearchTerms) { 1.3341 + // Re-use the autocomplete_match function. Setting the behavior to 0 1.3342 + // it can match everything and work as a nice case insensitive comparator. 1.3343 + clause.Condition("AUTOCOMPLETE_MATCH(").Param(":search_string") 1.3344 + .Str(", h.url, page_title, tags, ") 1.3345 + .Str(nsPrintfCString("0, 0, 0, 0, %d, 0)", 1.3346 + mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED).get()); 1.3347 + // Serching by terms implicitly exclude queries. 1.3348 + excludeQueries = true; 1.3349 + } 1.3350 + 1.3351 + // min and max visit count 1.3352 + if (aQuery->MinVisits() >= 0) 1.3353 + clause.Condition("h.visit_count >=").Param(":min_visits"); 1.3354 + 1.3355 + if (aQuery->MaxVisits() >= 0) 1.3356 + clause.Condition("h.visit_count <=").Param(":max_visits"); 1.3357 + 1.3358 + // only bookmarked, has no affect on bookmarks-only queries 1.3359 + if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS && 1.3360 + aQuery->OnlyBookmarked()) 1.3361 + clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ") 1.3362 + .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get()) 1.3363 + .Str("AND b.fk = h.id)"); 1.3364 + 1.3365 + // domain 1.3366 + if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { 1.3367 + bool domainIsHost = false; 1.3368 + aQuery->GetDomainIsHost(&domainIsHost); 1.3369 + if (domainIsHost) 1.3370 + clause.Condition("h.rev_host =").Param(":domain_lower"); 1.3371 + else 1.3372 + // see domain setting in BindQueryClauseParameters for why we do this 1.3373 + clause.Condition("h.rev_host >=").Param(":domain_lower") 1.3374 + .Condition("h.rev_host <").Param(":domain_upper"); 1.3375 + } 1.3376 + 1.3377 + // URI 1.3378 + if (NS_SUCCEEDED(aQuery->GetHasUri(&hasIt)) && hasIt) { 1.3379 + if (aQuery->UriIsPrefix()) { 1.3380 + clause.Condition("h.url >= ").Param(":uri") 1.3381 + .Condition("h.url <= ").Param(":uri_upper"); 1.3382 + } 1.3383 + else 1.3384 + clause.Condition("h.url =").Param(":uri"); 1.3385 + } 1.3386 + 1.3387 + // annotation 1.3388 + aQuery->GetHasAnnotation(&hasIt); 1.3389 + if (hasIt) { 1.3390 + clause.Condition(""); 1.3391 + if (aQuery->AnnotationIsNot()) 1.3392 + clause.Str("NOT"); 1.3393 + clause.Str( 1.3394 + "EXISTS " 1.3395 + "(SELECT h.id " 1.3396 + "FROM moz_annos anno " 1.3397 + "JOIN moz_anno_attributes annoname " 1.3398 + "ON anno.anno_attribute_id = annoname.id " 1.3399 + "WHERE anno.place_id = h.id " 1.3400 + "AND annoname.name = ").Param(":anno").Str(")"); 1.3401 + // annotation-based queries don't get the common conditions, so you get 1.3402 + // all URLs with that annotation 1.3403 + } 1.3404 + 1.3405 + // tags 1.3406 + const nsTArray<nsString> &tags = aQuery->Tags(); 1.3407 + if (tags.Length() > 0) { 1.3408 + clause.Condition("h.id"); 1.3409 + if (aQuery->TagsAreNot()) 1.3410 + clause.Str("NOT"); 1.3411 + clause.Str( 1.3412 + "IN " 1.3413 + "(SELECT bms.fk " 1.3414 + "FROM moz_bookmarks bms " 1.3415 + "JOIN moz_bookmarks tags ON bms.parent = tags.id " 1.3416 + "WHERE tags.parent ="). 1.3417 + Param(":tags_folder"). 1.3418 + Str("AND tags.title IN ("); 1.3419 + for (uint32_t i = 0; i < tags.Length(); ++i) { 1.3420 + nsPrintfCString param(":tag%d_", i); 1.3421 + clause.Param(param.get()); 1.3422 + if (i < tags.Length() - 1) 1.3423 + clause.Str(","); 1.3424 + } 1.3425 + clause.Str(")"); 1.3426 + if (!aQuery->TagsAreNot()) 1.3427 + clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count"); 1.3428 + clause.Str(")"); 1.3429 + } 1.3430 + 1.3431 + // transitions 1.3432 + const nsTArray<uint32_t>& transitions = aQuery->Transitions(); 1.3433 + for (uint32_t i = 0; i < transitions.Length(); ++i) { 1.3434 + nsPrintfCString param(":transition%d_", i); 1.3435 + clause.Condition("h.id IN (SELECT place_id FROM moz_historyvisits " 1.3436 + "WHERE visit_type = ") 1.3437 + .Param(param.get()) 1.3438 + .Str(")"); 1.3439 + } 1.3440 + 1.3441 + // folders 1.3442 + const nsTArray<int64_t>& folders = aQuery->Folders(); 1.3443 + if (folders.Length() > 0) { 1.3444 + nsTArray<int64_t> includeFolders; 1.3445 + includeFolders.AppendElements(folders); 1.3446 + 1.3447 + nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService(); 1.3448 + NS_ENSURE_STATE(bookmarks); 1.3449 + 1.3450 + for (nsTArray<int64_t>::size_type i = 0; i < folders.Length(); ++i) { 1.3451 + nsTArray<int64_t> subFolders; 1.3452 + if (NS_FAILED(bookmarks->GetDescendantFolders(folders[i], subFolders))) 1.3453 + continue; 1.3454 + includeFolders.AppendElements(subFolders); 1.3455 + } 1.3456 + 1.3457 + clause.Condition("b.parent IN("); 1.3458 + for (nsTArray<int64_t>::size_type i = 0; i < includeFolders.Length(); ++i) { 1.3459 + clause.Str(nsPrintfCString("%lld", includeFolders[i]).get()); 1.3460 + if (i < includeFolders.Length() - 1) { 1.3461 + clause.Str(","); 1.3462 + } 1.3463 + } 1.3464 + clause.Str(")"); 1.3465 + } 1.3466 + 1.3467 + if (excludeQueries) { 1.3468 + // Serching by terms implicitly exclude queries. 1.3469 + clause.Condition("NOT h.url BETWEEN 'place:' AND 'place;'"); 1.3470 + } 1.3471 + 1.3472 + clause.GetClauseString(*aClause); 1.3473 + return NS_OK; 1.3474 +} 1.3475 + 1.3476 + 1.3477 +// nsNavHistory::BindQueryClauseParameters 1.3478 +// 1.3479 +// THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause 1.3480 + 1.3481 +nsresult 1.3482 +nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement, 1.3483 + int32_t aQueryIndex, 1.3484 + nsNavHistoryQuery* aQuery, // const 1.3485 + nsNavHistoryQueryOptions* aOptions) 1.3486 +{ 1.3487 + nsresult rv; 1.3488 + 1.3489 + bool hasIt; 1.3490 + // Append numbered index to param names, to replace them correctly in 1.3491 + // case of multiple queries. If we have just one query we don't change the 1.3492 + // param name though. 1.3493 + nsAutoCString qIndex; 1.3494 + if (aQueryIndex > 0) 1.3495 + qIndex.AppendInt(aQueryIndex); 1.3496 + 1.3497 + // begin time 1.3498 + if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) { 1.3499 + PRTime time = NormalizeTime(aQuery->BeginTimeReference(), 1.3500 + aQuery->BeginTime()); 1.3501 + rv = statement->BindInt64ByName( 1.3502 + NS_LITERAL_CSTRING("begin_time") + qIndex, time); 1.3503 + NS_ENSURE_SUCCESS(rv, rv); 1.3504 + } 1.3505 + 1.3506 + // end time 1.3507 + if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) { 1.3508 + PRTime time = NormalizeTime(aQuery->EndTimeReference(), 1.3509 + aQuery->EndTime()); 1.3510 + rv = statement->BindInt64ByName( 1.3511 + NS_LITERAL_CSTRING("end_time") + qIndex, time 1.3512 + ); 1.3513 + NS_ENSURE_SUCCESS(rv, rv); 1.3514 + } 1.3515 + 1.3516 + // search terms 1.3517 + if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasIt)) && hasIt) { 1.3518 + rv = statement->BindStringByName( 1.3519 + NS_LITERAL_CSTRING("search_string") + qIndex, 1.3520 + aQuery->SearchTerms() 1.3521 + ); 1.3522 + NS_ENSURE_SUCCESS(rv, rv); 1.3523 + } 1.3524 + 1.3525 + // min and max visit count 1.3526 + int32_t visits = aQuery->MinVisits(); 1.3527 + if (visits >= 0) { 1.3528 + rv = statement->BindInt32ByName( 1.3529 + NS_LITERAL_CSTRING("min_visits") + qIndex, visits 1.3530 + ); 1.3531 + NS_ENSURE_SUCCESS(rv, rv); 1.3532 + } 1.3533 + 1.3534 + visits = aQuery->MaxVisits(); 1.3535 + if (visits >= 0) { 1.3536 + rv = statement->BindInt32ByName( 1.3537 + NS_LITERAL_CSTRING("max_visits") + qIndex, visits 1.3538 + ); 1.3539 + NS_ENSURE_SUCCESS(rv, rv); 1.3540 + } 1.3541 + 1.3542 + // domain (see GetReversedHostname for more info on reversed host names) 1.3543 + if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) { 1.3544 + nsString revDomain; 1.3545 + GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain); 1.3546 + 1.3547 + if (aQuery->DomainIsHost()) { 1.3548 + rv = statement->BindStringByName( 1.3549 + NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain 1.3550 + ); 1.3551 + NS_ENSURE_SUCCESS(rv, rv); 1.3552 + } else { 1.3553 + // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/" 1.3554 + // which will get everything starting with "gro.allizom." while using the 1.3555 + // index (using SUBSTRING() causes indexes to be discarded). 1.3556 + NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.', "Invalid rev. host"); 1.3557 + rv = statement->BindStringByName( 1.3558 + NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain 1.3559 + ); 1.3560 + NS_ENSURE_SUCCESS(rv, rv); 1.3561 + revDomain.Truncate(revDomain.Length() - 1); 1.3562 + revDomain.Append(char16_t('/')); 1.3563 + rv = statement->BindStringByName( 1.3564 + NS_LITERAL_CSTRING("domain_upper") + qIndex, revDomain 1.3565 + ); 1.3566 + NS_ENSURE_SUCCESS(rv, rv); 1.3567 + } 1.3568 + } 1.3569 + 1.3570 + // URI 1.3571 + if (aQuery->Uri()) { 1.3572 + rv = URIBinder::Bind( 1.3573 + statement, NS_LITERAL_CSTRING("uri") + qIndex, aQuery->Uri() 1.3574 + ); 1.3575 + NS_ENSURE_SUCCESS(rv, rv); 1.3576 + if (aQuery->UriIsPrefix()) { 1.3577 + nsAutoCString uriString; 1.3578 + aQuery->Uri()->GetSpec(uriString); 1.3579 + uriString.Append(char(0x7F)); // MAX_UTF8 1.3580 + rv = URIBinder::Bind( 1.3581 + statement, NS_LITERAL_CSTRING("uri_upper") + qIndex, uriString 1.3582 + ); 1.3583 + NS_ENSURE_SUCCESS(rv, rv); 1.3584 + } 1.3585 + } 1.3586 + 1.3587 + // annotation 1.3588 + if (!aQuery->Annotation().IsEmpty()) { 1.3589 + rv = statement->BindUTF8StringByName( 1.3590 + NS_LITERAL_CSTRING("anno") + qIndex, aQuery->Annotation() 1.3591 + ); 1.3592 + NS_ENSURE_SUCCESS(rv, rv); 1.3593 + } 1.3594 + 1.3595 + // tags 1.3596 + const nsTArray<nsString> &tags = aQuery->Tags(); 1.3597 + if (tags.Length() > 0) { 1.3598 + for (uint32_t i = 0; i < tags.Length(); ++i) { 1.3599 + nsPrintfCString paramName("tag%d_", i); 1.3600 + NS_ConvertUTF16toUTF8 tag(tags[i]); 1.3601 + rv = statement->BindUTF8StringByName(paramName + qIndex, tag); 1.3602 + NS_ENSURE_SUCCESS(rv, rv); 1.3603 + } 1.3604 + int64_t tagsFolder = GetTagsFolder(); 1.3605 + rv = statement->BindInt64ByName( 1.3606 + NS_LITERAL_CSTRING("tags_folder") + qIndex, tagsFolder 1.3607 + ); 1.3608 + NS_ENSURE_SUCCESS(rv, rv); 1.3609 + if (!aQuery->TagsAreNot()) { 1.3610 + rv = statement->BindInt32ByName( 1.3611 + NS_LITERAL_CSTRING("tag_count") + qIndex, tags.Length() 1.3612 + ); 1.3613 + NS_ENSURE_SUCCESS(rv, rv); 1.3614 + } 1.3615 + } 1.3616 + 1.3617 + // transitions 1.3618 + const nsTArray<uint32_t>& transitions = aQuery->Transitions(); 1.3619 + if (transitions.Length() > 0) { 1.3620 + for (uint32_t i = 0; i < transitions.Length(); ++i) { 1.3621 + nsPrintfCString paramName("transition%d_", i); 1.3622 + rv = statement->BindInt64ByName(paramName + qIndex, transitions[i]); 1.3623 + NS_ENSURE_SUCCESS(rv, rv); 1.3624 + } 1.3625 + } 1.3626 + 1.3627 + return NS_OK; 1.3628 +} 1.3629 + 1.3630 + 1.3631 +// nsNavHistory::ResultsAsList 1.3632 +// 1.3633 + 1.3634 +nsresult 1.3635 +nsNavHistory::ResultsAsList(mozIStorageStatement* statement, 1.3636 + nsNavHistoryQueryOptions* aOptions, 1.3637 + nsCOMArray<nsNavHistoryResultNode>* aResults) 1.3638 +{ 1.3639 + nsresult rv; 1.3640 + nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); 1.3641 + NS_ENSURE_SUCCESS(rv, rv); 1.3642 + 1.3643 + bool hasMore = false; 1.3644 + while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) { 1.3645 + nsRefPtr<nsNavHistoryResultNode> result; 1.3646 + rv = RowToResult(row, aOptions, getter_AddRefs(result)); 1.3647 + NS_ENSURE_SUCCESS(rv, rv); 1.3648 + aResults->AppendObject(result); 1.3649 + } 1.3650 + return NS_OK; 1.3651 +} 1.3652 + 1.3653 +const int64_t UNDEFINED_URN_VALUE = -1; 1.3654 + 1.3655 +// Create a urn (like 1.3656 +// urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29) 1.3657 +// to be used to persist the open state of this container in localstore.rdf 1.3658 +nsresult 1.3659 +CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode, 1.3660 + int64_t aValue, const nsCString& aTitle, nsCString& aURN) 1.3661 +{ 1.3662 + nsAutoCString uri; 1.3663 + nsresult rv = aResultNode->GetUri(uri); 1.3664 + NS_ENSURE_SUCCESS(rv, rv); 1.3665 + 1.3666 + aURN.Assign(NS_LITERAL_CSTRING("urn:places-persist:")); 1.3667 + aURN.Append(uri); 1.3668 + 1.3669 + aURN.Append(NS_LITERAL_CSTRING(",")); 1.3670 + if (aValue != UNDEFINED_URN_VALUE) 1.3671 + aURN.AppendInt(aValue); 1.3672 + 1.3673 + aURN.Append(NS_LITERAL_CSTRING(",")); 1.3674 + if (!aTitle.IsEmpty()) { 1.3675 + nsAutoCString escapedTitle; 1.3676 + bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas); 1.3677 + NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY); 1.3678 + aURN.Append(escapedTitle); 1.3679 + } 1.3680 + 1.3681 + return NS_OK; 1.3682 +} 1.3683 + 1.3684 +int64_t 1.3685 +nsNavHistory::GetTagsFolder() 1.3686 +{ 1.3687 + // cache our tags folder 1.3688 + // note, we can't do this in nsNavHistory::Init(), 1.3689 + // as getting the bookmarks service would initialize it. 1.3690 + if (mTagsFolder == -1) { 1.3691 + nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); 1.3692 + NS_ENSURE_TRUE(bookmarks, -1); 1.3693 + 1.3694 + nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder); 1.3695 + NS_ENSURE_SUCCESS(rv, -1); 1.3696 + } 1.3697 + return mTagsFolder; 1.3698 +} 1.3699 + 1.3700 +// nsNavHistory::FilterResultSet 1.3701 +// 1.3702 +// This does some post-query-execution filtering: 1.3703 +// - searching on title, url and tags 1.3704 +// - limit count 1.3705 +// 1.3706 +// Note: changes to filtering in FilterResultSet() 1.3707 +// may require changes to NeedToFilterResultSet() 1.3708 + 1.3709 +nsresult 1.3710 +nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode, 1.3711 + const nsCOMArray<nsNavHistoryResultNode>& aSet, 1.3712 + nsCOMArray<nsNavHistoryResultNode>* aFiltered, 1.3713 + const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.3714 + nsNavHistoryQueryOptions *aOptions) 1.3715 +{ 1.3716 + // get the bookmarks service 1.3717 + nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); 1.3718 + NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); 1.3719 + 1.3720 + // parse the search terms 1.3721 + nsTArray<nsTArray<nsString>*> terms; 1.3722 + ParseSearchTermsFromQueries(aQueries, &terms); 1.3723 + 1.3724 + uint16_t resultType = aOptions->ResultType(); 1.3725 + for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) { 1.3726 + // exclude-queries is implicit when searching, we're only looking at 1.3727 + // plan URI nodes 1.3728 + if (!aSet[nodeIndex]->IsURI()) 1.3729 + continue; 1.3730 + 1.3731 + // RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and 1.3732 + // lastModified. So, to remove duplicates, we can retain the first result 1.3733 + // for each uri. 1.3734 + if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS && 1.3735 + nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI) 1.3736 + continue; 1.3737 + 1.3738 + if (aSet[nodeIndex]->mItemId != -1 && aQueryNode && 1.3739 + aQueryNode->mItemId == aSet[nodeIndex]->mItemId) { 1.3740 + continue; 1.3741 + } 1.3742 + 1.3743 + // Append the node only if it matches one of the queries. 1.3744 + bool appendNode = false; 1.3745 + for (int32_t queryIndex = 0; 1.3746 + queryIndex < aQueries.Count() && !appendNode; queryIndex++) { 1.3747 + 1.3748 + if (terms[queryIndex]->Length()) { 1.3749 + // Filter based on search terms. 1.3750 + // Convert title and url for the current node to UTF16 strings. 1.3751 + NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle); 1.3752 + // Unescape the URL for search terms matching. 1.3753 + nsAutoCString cNodeURL(aSet[nodeIndex]->mURI); 1.3754 + NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL)); 1.3755 + 1.3756 + // Determine if every search term matches anywhere in the title, url or 1.3757 + // tag. 1.3758 + bool matchAll = true; 1.3759 + for (int32_t termIndex = terms[queryIndex]->Length() - 1; 1.3760 + termIndex >= 0 && matchAll; 1.3761 + termIndex--) { 1.3762 + nsString& term = terms[queryIndex]->ElementAt(termIndex); 1.3763 + 1.3764 + // True if any of them match; false makes us quit the loop 1.3765 + matchAll = CaseInsensitiveFindInReadable(term, nodeTitle) || 1.3766 + CaseInsensitiveFindInReadable(term, nodeURL) || 1.3767 + CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags); 1.3768 + } 1.3769 + 1.3770 + // Skip the node if we don't match all terms in the title, url or tag 1.3771 + if (!matchAll) 1.3772 + continue; 1.3773 + } 1.3774 + 1.3775 + // We passed all filters, so we can append the node to filtered results. 1.3776 + appendNode = true; 1.3777 + } 1.3778 + 1.3779 + if (appendNode) 1.3780 + aFiltered->AppendObject(aSet[nodeIndex]); 1.3781 + 1.3782 + // Stop once we have reached max results. 1.3783 + if (aOptions->MaxResults() > 0 && 1.3784 + (uint32_t)aFiltered->Count() >= aOptions->MaxResults()) 1.3785 + break; 1.3786 + } 1.3787 + 1.3788 + // De-allocate the temporary matrixes. 1.3789 + for (int32_t i = 0; i < aQueries.Count(); i++) { 1.3790 + delete terms[i]; 1.3791 + } 1.3792 + 1.3793 + return NS_OK; 1.3794 +} 1.3795 + 1.3796 +void 1.3797 +nsNavHistory::registerEmbedVisit(nsIURI* aURI, 1.3798 + int64_t aTime) 1.3799 +{ 1.3800 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.3801 + 1.3802 + VisitHashKey* visit = mEmbedVisits.PutEntry(aURI); 1.3803 + if (!visit) { 1.3804 + NS_WARNING("Unable to register a EMBED visit."); 1.3805 + return; 1.3806 + } 1.3807 + visit->visitTime = aTime; 1.3808 +} 1.3809 + 1.3810 +bool 1.3811 +nsNavHistory::hasEmbedVisit(nsIURI* aURI) { 1.3812 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.3813 + 1.3814 + return !!mEmbedVisits.GetEntry(aURI); 1.3815 +} 1.3816 + 1.3817 +void 1.3818 +nsNavHistory::clearEmbedVisits() { 1.3819 + NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread"); 1.3820 + 1.3821 + mEmbedVisits.Clear(); 1.3822 +} 1.3823 + 1.3824 +// nsNavHistory::CheckIsRecentEvent 1.3825 +// 1.3826 +// Sees if this URL happened "recently." 1.3827 +// 1.3828 +// It is always removed from our recent list no matter what. It only counts 1.3829 +// as "recent" if the event happened more recently than our event 1.3830 +// threshold ago. 1.3831 + 1.3832 +bool 1.3833 +nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable, 1.3834 + const nsACString& url) 1.3835 +{ 1.3836 + PRTime eventTime; 1.3837 + if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) { 1.3838 + hashTable->Remove(url); 1.3839 + if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD) 1.3840 + return true; 1.3841 + return false; 1.3842 + } 1.3843 + return false; 1.3844 +} 1.3845 + 1.3846 + 1.3847 +// nsNavHistory::ExpireNonrecentEvents 1.3848 +// 1.3849 +// This goes through our 1.3850 + 1.3851 +static PLDHashOperator 1.3852 +ExpireNonrecentEventsCallback(nsCStringHashKey::KeyType aKey, 1.3853 + int64_t& aData, 1.3854 + void* userArg) 1.3855 +{ 1.3856 + int64_t* threshold = reinterpret_cast<int64_t*>(userArg); 1.3857 + if (aData < *threshold) 1.3858 + return PL_DHASH_REMOVE; 1.3859 + return PL_DHASH_NEXT; 1.3860 +} 1.3861 +void 1.3862 +nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable) 1.3863 +{ 1.3864 + int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD; 1.3865 + hashTable->Enumerate(ExpireNonrecentEventsCallback, 1.3866 + reinterpret_cast<void*>(&threshold)); 1.3867 +} 1.3868 + 1.3869 + 1.3870 +// nsNavHistory::RowToResult 1.3871 +// 1.3872 +// Here, we just have a generic row. It could be a query, URL, visit, 1.3873 +// or full visit. 1.3874 + 1.3875 +nsresult 1.3876 +nsNavHistory::RowToResult(mozIStorageValueArray* aRow, 1.3877 + nsNavHistoryQueryOptions* aOptions, 1.3878 + nsNavHistoryResultNode** aResult) 1.3879 +{ 1.3880 + NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult"); 1.3881 + 1.3882 + // URL 1.3883 + nsAutoCString url; 1.3884 + nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url); 1.3885 + NS_ENSURE_SUCCESS(rv, rv); 1.3886 + 1.3887 + // title 1.3888 + nsAutoCString title; 1.3889 + rv = aRow->GetUTF8String(kGetInfoIndex_Title, title); 1.3890 + NS_ENSURE_SUCCESS(rv, rv); 1.3891 + 1.3892 + uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount); 1.3893 + PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate); 1.3894 + 1.3895 + // favicon 1.3896 + nsAutoCString favicon; 1.3897 + rv = aRow->GetUTF8String(kGetInfoIndex_FaviconURL, favicon); 1.3898 + NS_ENSURE_SUCCESS(rv, rv); 1.3899 + 1.3900 + // itemId 1.3901 + int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId); 1.3902 + int64_t parentId = -1; 1.3903 + if (itemId == 0) { 1.3904 + // This is not a bookmark. For non-bookmarks we use a -1 itemId value. 1.3905 + // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0. 1.3906 + itemId = -1; 1.3907 + } 1.3908 + else { 1.3909 + // This is a bookmark, so it has a parent. 1.3910 + int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId); 1.3911 + if (itemParentId > 0) { 1.3912 + // The Places root has parent == 0, but that item id does not really 1.3913 + // exist. We want to set the parent only if it's a real one. 1.3914 + parentId = itemParentId; 1.3915 + } 1.3916 + } 1.3917 + 1.3918 + if (IsQueryURI(url)) { 1.3919 + // special case "place:" URIs: turn them into containers 1.3920 + 1.3921 + // We should never expose the history title for query nodes if the 1.3922 + // bookmark-item's title is set to null (the history title may be the 1.3923 + // query string without the place: prefix). Thus we call getItemTitle 1.3924 + // explicitly. Doing this in the SQL query would be less performant since 1.3925 + // it should be done for all results rather than only for queries. 1.3926 + if (itemId != -1) { 1.3927 + nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); 1.3928 + NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); 1.3929 + 1.3930 + rv = bookmarks->GetItemTitle(itemId, title); 1.3931 + NS_ENSURE_SUCCESS(rv, rv); 1.3932 + } 1.3933 + 1.3934 + nsRefPtr<nsNavHistoryResultNode> resultNode; 1.3935 + rv = QueryRowToResult(itemId, url, title, accessCount, time, favicon, 1.3936 + getter_AddRefs(resultNode)); 1.3937 + NS_ENSURE_SUCCESS(rv,rv); 1.3938 + 1.3939 + if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) { 1.3940 + // RESULTS_AS_TAG_QUERY has date columns 1.3941 + resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); 1.3942 + resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); 1.3943 + } 1.3944 + else if (resultNode->IsFolder()) { 1.3945 + // If it's a simple folder node (i.e. a shortcut to another folder), apply 1.3946 + // our options for it. However, if the parent type was tag query, we do not 1.3947 + // apply them, because it would not yield any results. 1.3948 + resultNode->GetAsContainer()->mOptions = aOptions; 1.3949 + } 1.3950 + 1.3951 + resultNode.forget(aResult); 1.3952 + return rv; 1.3953 + } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI || 1.3954 + aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) { 1.3955 + nsRefPtr<nsNavHistoryResultNode> resultNode = 1.3956 + new nsNavHistoryResultNode(url, title, accessCount, time, favicon); 1.3957 + 1.3958 + if (itemId != -1) { 1.3959 + resultNode->mItemId = itemId; 1.3960 + resultNode->mFolderId = parentId; 1.3961 + resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded); 1.3962 + resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified); 1.3963 + } 1.3964 + 1.3965 + resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency); 1.3966 + resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden); 1.3967 + 1.3968 + nsAutoString tags; 1.3969 + rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); 1.3970 + NS_ENSURE_SUCCESS(rv, rv); 1.3971 + if (!tags.IsVoid()) { 1.3972 + resultNode->mTags.Assign(tags); 1.3973 + } 1.3974 + 1.3975 + rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); 1.3976 + NS_ENSURE_SUCCESS(rv, rv); 1.3977 + 1.3978 + resultNode.forget(aResult); 1.3979 + return NS_OK; 1.3980 + } 1.3981 + 1.3982 + if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) { 1.3983 + nsRefPtr<nsNavHistoryResultNode> resultNode = 1.3984 + new nsNavHistoryResultNode(url, title, accessCount, time, favicon); 1.3985 + 1.3986 + nsAutoString tags; 1.3987 + rv = aRow->GetString(kGetInfoIndex_ItemTags, tags); 1.3988 + if (!tags.IsVoid()) 1.3989 + resultNode->mTags.Assign(tags); 1.3990 + 1.3991 + rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid); 1.3992 + NS_ENSURE_SUCCESS(rv, rv); 1.3993 + 1.3994 + resultNode.forget(aResult); 1.3995 + return NS_OK; 1.3996 + } 1.3997 + 1.3998 + return NS_ERROR_FAILURE; 1.3999 +} 1.4000 + 1.4001 + 1.4002 +// nsNavHistory::QueryRowToResult 1.4003 +// 1.4004 +// Called by RowToResult when the URI is a place: URI to generate the proper 1.4005 +// folder or query node. 1.4006 + 1.4007 +nsresult 1.4008 +nsNavHistory::QueryRowToResult(int64_t itemId, const nsACString& aURI, 1.4009 + const nsACString& aTitle, 1.4010 + uint32_t aAccessCount, PRTime aTime, 1.4011 + const nsACString& aFavicon, 1.4012 + nsNavHistoryResultNode** aNode) 1.4013 +{ 1.4014 + nsCOMArray<nsNavHistoryQuery> queries; 1.4015 + nsCOMPtr<nsNavHistoryQueryOptions> options; 1.4016 + nsresult rv = QueryStringToQueryArray(aURI, &queries, 1.4017 + getter_AddRefs(options)); 1.4018 + 1.4019 + nsRefPtr<nsNavHistoryResultNode> resultNode; 1.4020 + // If this failed the query does not parse correctly, let the error pass and 1.4021 + // handle it later. 1.4022 + if (NS_SUCCEEDED(rv)) { 1.4023 + // Check if this is a folder shortcut, so we can take a faster path. 1.4024 + int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options); 1.4025 + if (folderId) { 1.4026 + nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService(); 1.4027 + NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY); 1.4028 + 1.4029 + rv = bookmarks->ResultNodeForContainer(folderId, options, 1.4030 + getter_AddRefs(resultNode)); 1.4031 + // If this failed the shortcut is pointing to nowhere, let the error pass 1.4032 + // and handle it later. 1.4033 + if (NS_SUCCEEDED(rv)) { 1.4034 + // This is the query itemId, and is what is exposed by node.itemId. 1.4035 + resultNode->GetAsFolder()->mQueryItemId = itemId; 1.4036 + 1.4037 + // Use the query item title, unless it's void (in that case use the 1.4038 + // concrete folder title). 1.4039 + if (!aTitle.IsVoid()) { 1.4040 + resultNode->mTitle = aTitle; 1.4041 + } 1.4042 + } 1.4043 + } 1.4044 + else { 1.4045 + // This is a regular query. 1.4046 + resultNode = new nsNavHistoryQueryResultNode(aTitle, EmptyCString(), 1.4047 + aTime, queries, options); 1.4048 + resultNode->mItemId = itemId; 1.4049 + } 1.4050 + } 1.4051 + 1.4052 + if (NS_FAILED(rv)) { 1.4053 + NS_WARNING("Generating a generic empty node for a broken query!"); 1.4054 + // This is a broken query, that either did not parse or points to not 1.4055 + // existing data. We don't want to return failure since that will kill the 1.4056 + // whole result. Instead make a generic empty query node. 1.4057 + resultNode = new nsNavHistoryQueryResultNode(aTitle, aFavicon, aURI); 1.4058 + resultNode->mItemId = itemId; 1.4059 + // This is a perf hack to generate an empty query that skips filtering. 1.4060 + resultNode->GetAsQuery()->Options()->SetExcludeItems(true); 1.4061 + } 1.4062 + 1.4063 + resultNode.forget(aNode); 1.4064 + return NS_OK; 1.4065 +} 1.4066 + 1.4067 + 1.4068 +// nsNavHistory::VisitIdToResultNode 1.4069 +// 1.4070 +// Used by the query results to create new nodes on the fly when 1.4071 +// notifications come in. This just creates a node for the given visit ID. 1.4072 + 1.4073 +nsresult 1.4074 +nsNavHistory::VisitIdToResultNode(int64_t visitId, 1.4075 + nsNavHistoryQueryOptions* aOptions, 1.4076 + nsNavHistoryResultNode** aResult) 1.4077 +{ 1.4078 + nsAutoCString tagsFragment; 1.4079 + GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), 1.4080 + true, tagsFragment); 1.4081 + 1.4082 + nsCOMPtr<mozIStorageStatement> statement; 1.4083 + switch (aOptions->ResultType()) 1.4084 + { 1.4085 + case nsNavHistoryQueryOptions::RESULTS_AS_VISIT: 1.4086 + case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT: 1.4087 + // visit query - want exact visit time 1.4088 + // Should match kGetInfoIndex_* (see GetQueryResults) 1.4089 + statement = mDB->GetStatement(NS_LITERAL_CSTRING( 1.4090 + "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " 1.4091 + "v.visit_date, f.url, null, null, null, null, " 1.4092 + ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.4093 + "FROM moz_places h " 1.4094 + "JOIN moz_historyvisits v ON h.id = v.place_id " 1.4095 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.4096 + "WHERE v.id = :visit_id ") 1.4097 + ); 1.4098 + break; 1.4099 + 1.4100 + case nsNavHistoryQueryOptions::RESULTS_AS_URI: 1.4101 + // URL results - want last visit time 1.4102 + // Should match kGetInfoIndex_* (see GetQueryResults) 1.4103 + statement = mDB->GetStatement(NS_LITERAL_CSTRING( 1.4104 + "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, " 1.4105 + "h.last_visit_date, f.url, null, null, null, null, " 1.4106 + ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.4107 + "FROM moz_places h " 1.4108 + "JOIN moz_historyvisits v ON h.id = v.place_id " 1.4109 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.4110 + "WHERE v.id = :visit_id ") 1.4111 + ); 1.4112 + break; 1.4113 + 1.4114 + default: 1.4115 + // Query base types like RESULTS_AS_*_QUERY handle additions 1.4116 + // by registering their own observers when they are expanded. 1.4117 + return NS_OK; 1.4118 + } 1.4119 + NS_ENSURE_STATE(statement); 1.4120 + mozStorageStatementScoper scoper(statement); 1.4121 + 1.4122 + nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"), 1.4123 + visitId); 1.4124 + NS_ENSURE_SUCCESS(rv, rv); 1.4125 + 1.4126 + bool hasMore = false; 1.4127 + rv = statement->ExecuteStep(&hasMore); 1.4128 + NS_ENSURE_SUCCESS(rv, rv); 1.4129 + if (! hasMore) { 1.4130 + NS_NOTREACHED("Trying to get a result node for an invalid visit"); 1.4131 + return NS_ERROR_INVALID_ARG; 1.4132 + } 1.4133 + 1.4134 + nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv); 1.4135 + NS_ENSURE_SUCCESS(rv, rv); 1.4136 + 1.4137 + return RowToResult(row, aOptions, aResult); 1.4138 +} 1.4139 + 1.4140 +nsresult 1.4141 +nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions, 1.4142 + nsNavHistoryResultNode** aResult) 1.4143 +{ 1.4144 + nsAutoCString tagsFragment; 1.4145 + GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), 1.4146 + true, tagsFragment); 1.4147 + // Should match kGetInfoIndex_* 1.4148 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( 1.4149 + "SELECT b.fk, h.url, COALESCE(b.title, h.title), " 1.4150 + "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, " 1.4151 + "b.dateAdded, b.lastModified, b.parent, " 1.4152 + ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.4153 + "FROM moz_bookmarks b " 1.4154 + "JOIN moz_places h ON b.fk = h.id " 1.4155 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.4156 + "WHERE b.id = :item_id ") 1.4157 + ); 1.4158 + NS_ENSURE_STATE(stmt); 1.4159 + mozStorageStatementScoper scoper(stmt); 1.4160 + 1.4161 + nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), 1.4162 + aBookmarkId); 1.4163 + NS_ENSURE_SUCCESS(rv, rv); 1.4164 + 1.4165 + bool hasMore = false; 1.4166 + rv = stmt->ExecuteStep(&hasMore); 1.4167 + NS_ENSURE_SUCCESS(rv, rv); 1.4168 + if (!hasMore) { 1.4169 + NS_NOTREACHED("Trying to get a result node for an invalid bookmark identifier"); 1.4170 + return NS_ERROR_INVALID_ARG; 1.4171 + } 1.4172 + 1.4173 + nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); 1.4174 + NS_ENSURE_SUCCESS(rv, rv); 1.4175 + 1.4176 + return RowToResult(row, aOptions, aResult); 1.4177 +} 1.4178 + 1.4179 +nsresult 1.4180 +nsNavHistory::URIToResultNode(nsIURI* aURI, 1.4181 + nsNavHistoryQueryOptions* aOptions, 1.4182 + nsNavHistoryResultNode** aResult) 1.4183 +{ 1.4184 + nsAutoCString tagsFragment; 1.4185 + GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"), 1.4186 + true, tagsFragment); 1.4187 + // Should match kGetInfoIndex_* 1.4188 + nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING( 1.4189 + "SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, " 1.4190 + "h.last_visit_date, f.url, null, null, null, null, " 1.4191 + ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid " 1.4192 + "FROM moz_places h " 1.4193 + "LEFT JOIN moz_favicons f ON h.favicon_id = f.id " 1.4194 + "WHERE h.url = :page_url ") 1.4195 + ); 1.4196 + NS_ENSURE_STATE(stmt); 1.4197 + mozStorageStatementScoper scoper(stmt); 1.4198 + 1.4199 + nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI); 1.4200 + NS_ENSURE_SUCCESS(rv, rv); 1.4201 + 1.4202 + bool hasMore = false; 1.4203 + rv = stmt->ExecuteStep(&hasMore); 1.4204 + NS_ENSURE_SUCCESS(rv, rv); 1.4205 + if (!hasMore) { 1.4206 + NS_NOTREACHED("Trying to get a result node for an invalid url"); 1.4207 + return NS_ERROR_INVALID_ARG; 1.4208 + } 1.4209 + 1.4210 + nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv); 1.4211 + NS_ENSURE_SUCCESS(rv, rv); 1.4212 + 1.4213 + return RowToResult(row, aOptions, aResult); 1.4214 +} 1.4215 + 1.4216 +void 1.4217 +nsNavHistory::SendPageChangedNotification(nsIURI* aURI, 1.4218 + uint32_t aChangedAttribute, 1.4219 + const nsAString& aNewValue, 1.4220 + const nsACString& aGUID) 1.4221 +{ 1.4222 + MOZ_ASSERT(!aGUID.IsEmpty()); 1.4223 + NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers, 1.4224 + nsINavHistoryObserver, 1.4225 + OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID)); 1.4226 +} 1.4227 + 1.4228 +// nsNavHistory::TitleForDomain 1.4229 +// 1.4230 +// This computes the title for a given domain. Normally, this is just the 1.4231 +// domain name, but we specially handle empty cases to give you a nice 1.4232 +// localized string. 1.4233 + 1.4234 +void 1.4235 +nsNavHistory::TitleForDomain(const nsCString& domain, nsACString& aTitle) 1.4236 +{ 1.4237 + if (! domain.IsEmpty()) { 1.4238 + aTitle = domain; 1.4239 + return; 1.4240 + } 1.4241 + 1.4242 + // use the localized one instead 1.4243 + GetStringFromName(MOZ_UTF16("localhost"), aTitle); 1.4244 +} 1.4245 + 1.4246 +void 1.4247 +nsNavHistory::GetAgeInDaysString(int32_t aInt, const char16_t *aName, 1.4248 + nsACString& aResult) 1.4249 +{ 1.4250 + nsIStringBundle *bundle = GetBundle(); 1.4251 + if (bundle) { 1.4252 + nsAutoString intString; 1.4253 + intString.AppendInt(aInt); 1.4254 + const char16_t* strings[1] = { intString.get() }; 1.4255 + nsXPIDLString value; 1.4256 + nsresult rv = bundle->FormatStringFromName(aName, strings, 1.4257 + 1, getter_Copies(value)); 1.4258 + if (NS_SUCCEEDED(rv)) { 1.4259 + CopyUTF16toUTF8(value, aResult); 1.4260 + return; 1.4261 + } 1.4262 + } 1.4263 + CopyUTF16toUTF8(nsDependentString(aName), aResult); 1.4264 +} 1.4265 + 1.4266 +void 1.4267 +nsNavHistory::GetStringFromName(const char16_t *aName, nsACString& aResult) 1.4268 +{ 1.4269 + nsIStringBundle *bundle = GetBundle(); 1.4270 + if (bundle) { 1.4271 + nsXPIDLString value; 1.4272 + nsresult rv = bundle->GetStringFromName(aName, getter_Copies(value)); 1.4273 + if (NS_SUCCEEDED(rv)) { 1.4274 + CopyUTF16toUTF8(value, aResult); 1.4275 + return; 1.4276 + } 1.4277 + } 1.4278 + CopyUTF16toUTF8(nsDependentString(aName), aResult); 1.4279 +} 1.4280 + 1.4281 +void 1.4282 +nsNavHistory::GetMonthName(int32_t aIndex, nsACString& aResult) 1.4283 +{ 1.4284 + nsIStringBundle *bundle = GetDateFormatBundle(); 1.4285 + if (bundle) { 1.4286 + nsCString name = nsPrintfCString("month.%d.name", aIndex); 1.4287 + nsXPIDLString value; 1.4288 + nsresult rv = bundle->GetStringFromName(NS_ConvertUTF8toUTF16(name).get(), 1.4289 + getter_Copies(value)); 1.4290 + if (NS_SUCCEEDED(rv)) { 1.4291 + CopyUTF16toUTF8(value, aResult); 1.4292 + return; 1.4293 + } 1.4294 + } 1.4295 + aResult = nsPrintfCString("[%d]", aIndex); 1.4296 +} 1.4297 + 1.4298 +void 1.4299 +nsNavHistory::GetMonthYear(int32_t aMonth, int32_t aYear, nsACString& aResult) 1.4300 +{ 1.4301 + nsIStringBundle *bundle = GetBundle(); 1.4302 + if (bundle) { 1.4303 + nsAutoCString monthName; 1.4304 + GetMonthName(aMonth, monthName); 1.4305 + nsAutoString yearString; 1.4306 + yearString.AppendInt(aYear); 1.4307 + const char16_t* strings[2] = { 1.4308 + NS_ConvertUTF8toUTF16(monthName).get() 1.4309 + , yearString.get() 1.4310 + }; 1.4311 + nsXPIDLString value; 1.4312 + if (NS_SUCCEEDED(bundle->FormatStringFromName( 1.4313 + MOZ_UTF16("finduri-MonthYear"), strings, 2, 1.4314 + getter_Copies(value) 1.4315 + ))) { 1.4316 + CopyUTF16toUTF8(value, aResult); 1.4317 + return; 1.4318 + } 1.4319 + } 1.4320 + aResult.AppendLiteral("finduri-MonthYear"); 1.4321 +} 1.4322 + 1.4323 + 1.4324 +namespace { 1.4325 + 1.4326 +// GetSimpleBookmarksQueryFolder 1.4327 +// 1.4328 +// Determines if this set of queries is a simple bookmarks query for a 1.4329 +// folder with no other constraints. In these common cases, we can more 1.4330 +// efficiently compute the results. 1.4331 +// 1.4332 +// A simple bookmarks query will result in a hierarchical tree of 1.4333 +// bookmark items, folders and separators. 1.4334 +// 1.4335 +// Returns the folder ID if it is a simple folder query, 0 if not. 1.4336 +static int64_t 1.4337 +GetSimpleBookmarksQueryFolder(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.4338 + nsNavHistoryQueryOptions* aOptions) 1.4339 +{ 1.4340 + if (aQueries.Count() != 1) 1.4341 + return 0; 1.4342 + 1.4343 + nsNavHistoryQuery* query = aQueries[0]; 1.4344 + if (query->Folders().Length() != 1) 1.4345 + return 0; 1.4346 + 1.4347 + bool hasIt; 1.4348 + query->GetHasBeginTime(&hasIt); 1.4349 + if (hasIt) 1.4350 + return 0; 1.4351 + query->GetHasEndTime(&hasIt); 1.4352 + if (hasIt) 1.4353 + return 0; 1.4354 + query->GetHasDomain(&hasIt); 1.4355 + if (hasIt) 1.4356 + return 0; 1.4357 + query->GetHasUri(&hasIt); 1.4358 + if (hasIt) 1.4359 + return 0; 1.4360 + (void)query->GetHasSearchTerms(&hasIt); 1.4361 + if (hasIt) 1.4362 + return 0; 1.4363 + if (query->Tags().Length() > 0) 1.4364 + return 0; 1.4365 + if (aOptions->MaxResults() > 0) 1.4366 + return 0; 1.4367 + 1.4368 + // RESULTS_AS_TAG_CONTENTS is quite similar to a folder shortcut, but it must 1.4369 + // not be treated like that, since it needs all query options. 1.4370 + if(aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) 1.4371 + return 0; 1.4372 + 1.4373 + // Don't care about onlyBookmarked flag, since specifying a bookmark 1.4374 + // folder is inferring onlyBookmarked. 1.4375 + 1.4376 + return query->Folders()[0]; 1.4377 +} 1.4378 + 1.4379 + 1.4380 +// ParseSearchTermsFromQueries 1.4381 +// 1.4382 +// Construct a matrix of search terms from the given queries array. 1.4383 +// All of the query objects are ORed together. Within a query, all the terms 1.4384 +// are ANDed together. See nsINavHistoryService.idl. 1.4385 +// 1.4386 +// This just breaks the query up into words. We don't do anything fancy, 1.4387 +// not even quoting. We do, however, strip quotes, because people might 1.4388 +// try to input quotes expecting them to do something and get no results 1.4389 +// back. 1.4390 + 1.4391 +inline bool isQueryWhitespace(char16_t ch) 1.4392 +{ 1.4393 + return ch == ' '; 1.4394 +} 1.4395 + 1.4396 +void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries, 1.4397 + nsTArray<nsTArray<nsString>*>* aTerms) 1.4398 +{ 1.4399 + int32_t lastBegin = -1; 1.4400 + for (int32_t i = 0; i < aQueries.Count(); i++) { 1.4401 + nsTArray<nsString> *queryTerms = new nsTArray<nsString>(); 1.4402 + bool hasSearchTerms; 1.4403 + if (NS_SUCCEEDED(aQueries[i]->GetHasSearchTerms(&hasSearchTerms)) && 1.4404 + hasSearchTerms) { 1.4405 + const nsString& searchTerms = aQueries[i]->SearchTerms(); 1.4406 + for (uint32_t j = 0; j < searchTerms.Length(); j++) { 1.4407 + if (isQueryWhitespace(searchTerms[j]) || 1.4408 + searchTerms[j] == '"') { 1.4409 + if (lastBegin >= 0) { 1.4410 + // found the end of a word 1.4411 + queryTerms->AppendElement(Substring(searchTerms, lastBegin, 1.4412 + j - lastBegin)); 1.4413 + lastBegin = -1; 1.4414 + } 1.4415 + } else { 1.4416 + if (lastBegin < 0) { 1.4417 + // found the beginning of a word 1.4418 + lastBegin = j; 1.4419 + } 1.4420 + } 1.4421 + } 1.4422 + // last word 1.4423 + if (lastBegin >= 0) 1.4424 + queryTerms->AppendElement(Substring(searchTerms, lastBegin)); 1.4425 + } 1.4426 + aTerms->AppendElement(queryTerms); 1.4427 + } 1.4428 +} 1.4429 + 1.4430 +} // anonymous namespace 1.4431 + 1.4432 + 1.4433 +nsresult 1.4434 +nsNavHistory::UpdateFrecency(int64_t aPlaceId) 1.4435 +{ 1.4436 + nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement( 1.4437 + "UPDATE moz_places " 1.4438 + "SET frecency = NOTIFY_FRECENCY(" 1.4439 + "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date" 1.4440 + ") " 1.4441 + "WHERE id = :page_id" 1.4442 + ); 1.4443 + NS_ENSURE_STATE(updateFrecencyStmt); 1.4444 + nsresult rv = updateFrecencyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), 1.4445 + aPlaceId); 1.4446 + NS_ENSURE_SUCCESS(rv, rv); 1.4447 + nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement( 1.4448 + "UPDATE moz_places " 1.4449 + "SET hidden = 0 " 1.4450 + "WHERE id = :page_id AND frecency <> 0" 1.4451 + ); 1.4452 + NS_ENSURE_STATE(updateHiddenStmt); 1.4453 + rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), 1.4454 + aPlaceId); 1.4455 + NS_ENSURE_SUCCESS(rv, rv); 1.4456 + 1.4457 + mozIStorageBaseStatement *stmts[] = { 1.4458 + updateFrecencyStmt.get() 1.4459 + , updateHiddenStmt.get() 1.4460 + }; 1.4461 + 1.4462 + nsRefPtr<AsyncStatementCallbackNotifier> cb = 1.4463 + new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED); 1.4464 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.4465 + rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb, 1.4466 + getter_AddRefs(ps)); 1.4467 + NS_ENSURE_SUCCESS(rv, rv); 1.4468 + 1.4469 + return NS_OK; 1.4470 +} 1.4471 + 1.4472 + 1.4473 +namespace { 1.4474 + 1.4475 +class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier 1.4476 +{ 1.4477 +public: 1.4478 + FixInvalidFrecenciesCallback() 1.4479 + : AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED) 1.4480 + { 1.4481 + } 1.4482 + 1.4483 + NS_IMETHOD HandleCompletion(uint16_t aReason) 1.4484 + { 1.4485 + nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason); 1.4486 + NS_ENSURE_SUCCESS(rv, rv); 1.4487 + if (aReason == REASON_FINISHED) { 1.4488 + nsNavHistory *navHistory = nsNavHistory::GetHistoryService(); 1.4489 + NS_ENSURE_STATE(navHistory); 1.4490 + navHistory->NotifyManyFrecenciesChanged(); 1.4491 + } 1.4492 + return NS_OK; 1.4493 + } 1.4494 +}; 1.4495 + 1.4496 +} // anonymous namespace 1.4497 + 1.4498 +nsresult 1.4499 +nsNavHistory::FixInvalidFrecencies() 1.4500 +{ 1.4501 + nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( 1.4502 + "UPDATE moz_places " 1.4503 + "SET frecency = CALCULATE_FRECENCY(id) " 1.4504 + "WHERE frecency < 0" 1.4505 + ); 1.4506 + NS_ENSURE_STATE(stmt); 1.4507 + 1.4508 + nsRefPtr<FixInvalidFrecenciesCallback> callback = 1.4509 + new FixInvalidFrecenciesCallback(); 1.4510 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.4511 + (void)stmt->ExecuteAsync(callback, getter_AddRefs(ps)); 1.4512 + 1.4513 + return NS_OK; 1.4514 +} 1.4515 + 1.4516 + 1.4517 +#ifdef MOZ_XUL 1.4518 + 1.4519 +nsresult 1.4520 +nsNavHistory::AutoCompleteFeedback(int32_t aIndex, 1.4521 + nsIAutoCompleteController *aController) 1.4522 +{ 1.4523 + nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement( 1.4524 + "INSERT OR REPLACE INTO moz_inputhistory " 1.4525 + // use_count will asymptotically approach the max of 10. 1.4526 + "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 " 1.4527 + "FROM moz_places h " 1.4528 + "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text " 1.4529 + "WHERE url = :page_url " 1.4530 + ); 1.4531 + NS_ENSURE_STATE(stmt); 1.4532 + 1.4533 + nsAutoString input; 1.4534 + nsresult rv = aController->GetSearchString(input); 1.4535 + NS_ENSURE_SUCCESS(rv, rv); 1.4536 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input); 1.4537 + NS_ENSURE_SUCCESS(rv, rv); 1.4538 + 1.4539 + nsAutoString url; 1.4540 + rv = aController->GetValueAt(aIndex, url); 1.4541 + NS_ENSURE_SUCCESS(rv, rv); 1.4542 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), 1.4543 + NS_ConvertUTF16toUTF8(url)); 1.4544 + NS_ENSURE_SUCCESS(rv, rv); 1.4545 + 1.4546 + // We do the update asynchronously and we do not care about failures. 1.4547 + nsRefPtr<AsyncStatementCallbackNotifier> callback = 1.4548 + new AsyncStatementCallbackNotifier(TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED); 1.4549 + nsCOMPtr<mozIStoragePendingStatement> canceler; 1.4550 + rv = stmt->ExecuteAsync(callback, getter_AddRefs(canceler)); 1.4551 + NS_ENSURE_SUCCESS(rv, rv); 1.4552 + 1.4553 + return NS_OK; 1.4554 +} 1.4555 + 1.4556 +#endif 1.4557 + 1.4558 + 1.4559 +nsICollation * 1.4560 +nsNavHistory::GetCollation() 1.4561 +{ 1.4562 + if (mCollation) 1.4563 + return mCollation; 1.4564 + 1.4565 + // locale 1.4566 + nsCOMPtr<nsILocale> locale; 1.4567 + nsCOMPtr<nsILocaleService> ls(do_GetService(NS_LOCALESERVICE_CONTRACTID)); 1.4568 + NS_ENSURE_TRUE(ls, nullptr); 1.4569 + nsresult rv = ls->GetApplicationLocale(getter_AddRefs(locale)); 1.4570 + NS_ENSURE_SUCCESS(rv, nullptr); 1.4571 + 1.4572 + // collation 1.4573 + nsCOMPtr<nsICollationFactory> cfact = 1.4574 + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); 1.4575 + NS_ENSURE_TRUE(cfact, nullptr); 1.4576 + rv = cfact->CreateCollation(locale, getter_AddRefs(mCollation)); 1.4577 + NS_ENSURE_SUCCESS(rv, nullptr); 1.4578 + 1.4579 + return mCollation; 1.4580 +} 1.4581 + 1.4582 +nsIStringBundle * 1.4583 +nsNavHistory::GetBundle() 1.4584 +{ 1.4585 + if (!mBundle) { 1.4586 + nsCOMPtr<nsIStringBundleService> bundleService = 1.4587 + services::GetStringBundleService(); 1.4588 + NS_ENSURE_TRUE(bundleService, nullptr); 1.4589 + nsresult rv = bundleService->CreateBundle( 1.4590 + "chrome://places/locale/places.properties", 1.4591 + getter_AddRefs(mBundle)); 1.4592 + NS_ENSURE_SUCCESS(rv, nullptr); 1.4593 + } 1.4594 + return mBundle; 1.4595 +} 1.4596 + 1.4597 +nsIStringBundle * 1.4598 +nsNavHistory::GetDateFormatBundle() 1.4599 +{ 1.4600 + if (!mDateFormatBundle) { 1.4601 + nsCOMPtr<nsIStringBundleService> bundleService = 1.4602 + services::GetStringBundleService(); 1.4603 + NS_ENSURE_TRUE(bundleService, nullptr); 1.4604 + nsresult rv = bundleService->CreateBundle( 1.4605 + "chrome://global/locale/dateFormat.properties", 1.4606 + getter_AddRefs(mDateFormatBundle)); 1.4607 + NS_ENSURE_SUCCESS(rv, nullptr); 1.4608 + } 1.4609 + return mDateFormatBundle; 1.4610 +}