1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/History.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2943 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/ArrayUtils.h" 1.11 +#include "mozilla/Attributes.h" 1.12 +#include "mozilla/DebugOnly.h" 1.13 +#include "mozilla/MemoryReporting.h" 1.14 + 1.15 +#include "mozilla/dom/ContentChild.h" 1.16 +#include "mozilla/dom/ContentParent.h" 1.17 +#include "nsXULAppAPI.h" 1.18 + 1.19 +#include "History.h" 1.20 +#include "nsNavHistory.h" 1.21 +#include "nsNavBookmarks.h" 1.22 +#include "nsAnnotationService.h" 1.23 +#include "Helpers.h" 1.24 +#include "PlaceInfo.h" 1.25 +#include "VisitInfo.h" 1.26 +#include "nsPlacesMacros.h" 1.27 + 1.28 +#include "mozilla/storage.h" 1.29 +#include "mozilla/dom/Link.h" 1.30 +#include "nsDocShellCID.h" 1.31 +#include "mozilla/Services.h" 1.32 +#include "nsThreadUtils.h" 1.33 +#include "nsNetUtil.h" 1.34 +#include "nsIXPConnect.h" 1.35 +#include "mozilla/unused.h" 1.36 +#include "nsContentUtils.h" // for nsAutoScriptBlocker 1.37 +#include "mozilla/ipc/URIUtils.h" 1.38 +#include "nsPrintfCString.h" 1.39 +#include "nsTHashtable.h" 1.40 +#include "jsapi.h" 1.41 + 1.42 +// Initial size for the cache holding visited status observers. 1.43 +#define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128 1.44 + 1.45 +// Initial size for the visits removal hash. 1.46 +#define VISITS_REMOVAL_INITIAL_HASH_SIZE 128 1.47 + 1.48 +using namespace mozilla::dom; 1.49 +using namespace mozilla::ipc; 1.50 +using mozilla::unused; 1.51 + 1.52 +namespace mozilla { 1.53 +namespace places { 1.54 + 1.55 +//////////////////////////////////////////////////////////////////////////////// 1.56 +//// Global Defines 1.57 + 1.58 +#define URI_VISITED "visited" 1.59 +#define URI_NOT_VISITED "not visited" 1.60 +#define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution" 1.61 +// Observer event fired after a visit has been registered in the DB. 1.62 +#define URI_VISIT_SAVED "uri-visit-saved" 1.63 + 1.64 +#define DESTINATIONFILEURI_ANNO \ 1.65 + NS_LITERAL_CSTRING("downloads/destinationFileURI") 1.66 +#define DESTINATIONFILENAME_ANNO \ 1.67 + NS_LITERAL_CSTRING("downloads/destinationFileName") 1.68 + 1.69 +//////////////////////////////////////////////////////////////////////////////// 1.70 +//// VisitData 1.71 + 1.72 +struct VisitData { 1.73 + VisitData() 1.74 + : placeId(0) 1.75 + , visitId(0) 1.76 + , hidden(true) 1.77 + , typed(false) 1.78 + , transitionType(UINT32_MAX) 1.79 + , visitTime(0) 1.80 + , frecency(-1) 1.81 + , titleChanged(false) 1.82 + , shouldUpdateFrecency(true) 1.83 + { 1.84 + guid.SetIsVoid(true); 1.85 + title.SetIsVoid(true); 1.86 + } 1.87 + 1.88 + VisitData(nsIURI* aURI, 1.89 + nsIURI* aReferrer = nullptr) 1.90 + : placeId(0) 1.91 + , visitId(0) 1.92 + , hidden(true) 1.93 + , typed(false) 1.94 + , transitionType(UINT32_MAX) 1.95 + , visitTime(0) 1.96 + , frecency(-1) 1.97 + , titleChanged(false) 1.98 + , shouldUpdateFrecency(true) 1.99 + { 1.100 + (void)aURI->GetSpec(spec); 1.101 + (void)GetReversedHostname(aURI, revHost); 1.102 + if (aReferrer) { 1.103 + (void)aReferrer->GetSpec(referrerSpec); 1.104 + } 1.105 + guid.SetIsVoid(true); 1.106 + title.SetIsVoid(true); 1.107 + } 1.108 + 1.109 + /** 1.110 + * Sets the transition type of the visit, as well as if it was typed. 1.111 + * 1.112 + * @param aTransitionType 1.113 + * The transition type constant to set. Must be one of the 1.114 + * TRANSITION_ constants on nsINavHistoryService. 1.115 + */ 1.116 + void SetTransitionType(uint32_t aTransitionType) 1.117 + { 1.118 + typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED; 1.119 + transitionType = aTransitionType; 1.120 + } 1.121 + 1.122 + /** 1.123 + * Determines if this refers to the same url as aOther, and updates aOther 1.124 + * with missing information if so. 1.125 + * 1.126 + * @param aOther 1.127 + * The other place to check against. 1.128 + * @return true if this is a visit for the same place as aOther, false 1.129 + * otherwise. 1.130 + */ 1.131 + bool IsSamePlaceAs(VisitData& aOther) 1.132 + { 1.133 + if (!spec.Equals(aOther.spec)) { 1.134 + return false; 1.135 + } 1.136 + 1.137 + aOther.placeId = placeId; 1.138 + aOther.guid = guid; 1.139 + return true; 1.140 + } 1.141 + 1.142 + int64_t placeId; 1.143 + nsCString guid; 1.144 + int64_t visitId; 1.145 + nsCString spec; 1.146 + nsString revHost; 1.147 + bool hidden; 1.148 + bool typed; 1.149 + uint32_t transitionType; 1.150 + PRTime visitTime; 1.151 + int32_t frecency; 1.152 + 1.153 + /** 1.154 + * Stores the title. If this is empty (IsEmpty() returns true), then the 1.155 + * title should be removed from the Place. If the title is void (IsVoid() 1.156 + * returns true), then no title has been set on this object, and titleChanged 1.157 + * should remain false. 1.158 + */ 1.159 + nsString title; 1.160 + 1.161 + nsCString referrerSpec; 1.162 + 1.163 + // TODO bug 626836 hook up hidden and typed change tracking too! 1.164 + bool titleChanged; 1.165 + 1.166 + // Indicates whether frecency should be updated for this visit. 1.167 + bool shouldUpdateFrecency; 1.168 +}; 1.169 + 1.170 +//////////////////////////////////////////////////////////////////////////////// 1.171 +//// RemoveVisitsFilter 1.172 + 1.173 +/** 1.174 + * Used to store visit filters for RemoveVisits. 1.175 + */ 1.176 +struct RemoveVisitsFilter { 1.177 + RemoveVisitsFilter() 1.178 + : transitionType(UINT32_MAX) 1.179 + { 1.180 + } 1.181 + 1.182 + uint32_t transitionType; 1.183 +}; 1.184 + 1.185 +//////////////////////////////////////////////////////////////////////////////// 1.186 +//// PlaceHashKey 1.187 + 1.188 +class PlaceHashKey : public nsCStringHashKey 1.189 +{ 1.190 + public: 1.191 + PlaceHashKey(const nsACString& aSpec) 1.192 + : nsCStringHashKey(&aSpec) 1.193 + , visitCount(-1) 1.194 + , bookmarked(-1) 1.195 + { 1.196 + } 1.197 + 1.198 + PlaceHashKey(const nsACString* aSpec) 1.199 + : nsCStringHashKey(aSpec) 1.200 + , visitCount(-1) 1.201 + , bookmarked(-1) 1.202 + { 1.203 + } 1.204 + 1.205 + PlaceHashKey(const PlaceHashKey& aOther) 1.206 + : nsCStringHashKey(&aOther.GetKey()) 1.207 + { 1.208 + MOZ_ASSERT(false, "Do not call me!"); 1.209 + } 1.210 + 1.211 + // Visit count for this place. 1.212 + int32_t visitCount; 1.213 + // Whether this place is bookmarked. 1.214 + int32_t bookmarked; 1.215 + // Array of VisitData objects. 1.216 + nsTArray<VisitData> visits; 1.217 +}; 1.218 + 1.219 +//////////////////////////////////////////////////////////////////////////////// 1.220 +//// Anonymous Helpers 1.221 + 1.222 +namespace { 1.223 + 1.224 +/** 1.225 + * Convert the given js value to a js array. 1.226 + * 1.227 + * @param [in] aValue 1.228 + * the JS value to convert. 1.229 + * @param [in] aCtx 1.230 + * The JSContext for aValue. 1.231 + * @param [out] _array 1.232 + * the JS array. 1.233 + * @param [out] _arrayLength 1.234 + * _array's length. 1.235 + */ 1.236 +nsresult 1.237 +GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue, 1.238 + JSContext* aCtx, 1.239 + JS::MutableHandle<JSObject*> _array, 1.240 + uint32_t* _arrayLength) { 1.241 + if (aValue.isObjectOrNull()) { 1.242 + JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull()); 1.243 + if (JS_IsArrayObject(aCtx, val)) { 1.244 + _array.set(val); 1.245 + (void)JS_GetArrayLength(aCtx, _array, _arrayLength); 1.246 + NS_ENSURE_ARG(*_arrayLength > 0); 1.247 + return NS_OK; 1.248 + } 1.249 + } 1.250 + 1.251 + // Build a temporary array to store this one item so the code below can 1.252 + // just loop. 1.253 + *_arrayLength = 1; 1.254 + _array.set(JS_NewArrayObject(aCtx, 0)); 1.255 + NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY); 1.256 + 1.257 + bool rc = JS_DefineElement(aCtx, _array, 0, aValue, nullptr, nullptr, 0); 1.258 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.259 + return NS_OK; 1.260 +} 1.261 + 1.262 +/** 1.263 + * Attemps to convert a given js value to a nsIURI object. 1.264 + * @param aCtx 1.265 + * The JSContext for aValue. 1.266 + * @param aValue 1.267 + * The JS value to convert. 1.268 + * @return the nsIURI object, or null if aValue is not a nsIURI object. 1.269 + */ 1.270 +already_AddRefed<nsIURI> 1.271 +GetJSValueAsURI(JSContext* aCtx, 1.272 + const JS::Value& aValue) { 1.273 + if (!JSVAL_IS_PRIMITIVE(aValue)) { 1.274 + nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect(); 1.275 + 1.276 + nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj; 1.277 + nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(aValue), 1.278 + getter_AddRefs(wrappedObj)); 1.279 + NS_ENSURE_SUCCESS(rv, nullptr); 1.280 + nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj); 1.281 + return uri.forget(); 1.282 + } 1.283 + return nullptr; 1.284 +} 1.285 + 1.286 +/** 1.287 + * Obtains an nsIURI from the "uri" property of a JSObject. 1.288 + * 1.289 + * @param aCtx 1.290 + * The JSContext for aObject. 1.291 + * @param aObject 1.292 + * The JSObject to get the URI from. 1.293 + * @param aProperty 1.294 + * The name of the property to get the URI from. 1.295 + * @return the URI if it exists. 1.296 + */ 1.297 +already_AddRefed<nsIURI> 1.298 +GetURIFromJSObject(JSContext* aCtx, 1.299 + JS::Handle<JSObject *> aObject, 1.300 + const char* aProperty) 1.301 +{ 1.302 + JS::Rooted<JS::Value> uriVal(aCtx); 1.303 + bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal); 1.304 + NS_ENSURE_TRUE(rc, nullptr); 1.305 + return GetJSValueAsURI(aCtx, uriVal); 1.306 +} 1.307 + 1.308 +/** 1.309 + * Attemps to convert a JS value to a string. 1.310 + * @param aCtx 1.311 + * The JSContext for aObject. 1.312 + * @param aValue 1.313 + * The JS value to convert. 1.314 + * @param _string 1.315 + * The string to populate with the value, or set it to void. 1.316 + */ 1.317 +void 1.318 +GetJSValueAsString(JSContext* aCtx, 1.319 + const JS::Value& aValue, 1.320 + nsString& _string) { 1.321 + if (JSVAL_IS_VOID(aValue) || 1.322 + !(JSVAL_IS_NULL(aValue) || JSVAL_IS_STRING(aValue))) { 1.323 + _string.SetIsVoid(true); 1.324 + return; 1.325 + } 1.326 + 1.327 + // |null| in JS maps to the empty string. 1.328 + if (JSVAL_IS_NULL(aValue)) { 1.329 + _string.Truncate(); 1.330 + return; 1.331 + } 1.332 + size_t length; 1.333 + const jschar* chars = 1.334 + JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(aValue), &length); 1.335 + if (!chars) { 1.336 + _string.SetIsVoid(true); 1.337 + return; 1.338 + } 1.339 + _string.Assign(static_cast<const char16_t*>(chars), length); 1.340 +} 1.341 + 1.342 +/** 1.343 + * Obtains the specified property of a JSObject. 1.344 + * 1.345 + * @param aCtx 1.346 + * The JSContext for aObject. 1.347 + * @param aObject 1.348 + * The JSObject to get the string from. 1.349 + * @param aProperty 1.350 + * The property to get the value from. 1.351 + * @param _string 1.352 + * The string to populate with the value, or set it to void. 1.353 + */ 1.354 +void 1.355 +GetStringFromJSObject(JSContext* aCtx, 1.356 + JS::Handle<JSObject *> aObject, 1.357 + const char* aProperty, 1.358 + nsString& _string) 1.359 +{ 1.360 + JS::Rooted<JS::Value> val(aCtx); 1.361 + bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val); 1.362 + if (!rc) { 1.363 + _string.SetIsVoid(true); 1.364 + return; 1.365 + } 1.366 + else { 1.367 + GetJSValueAsString(aCtx, val, _string); 1.368 + } 1.369 +} 1.370 + 1.371 +/** 1.372 + * Obtains the specified property of a JSObject. 1.373 + * 1.374 + * @param aCtx 1.375 + * The JSContext for aObject. 1.376 + * @param aObject 1.377 + * The JSObject to get the int from. 1.378 + * @param aProperty 1.379 + * The property to get the value from. 1.380 + * @param _int 1.381 + * The integer to populate with the value on success. 1.382 + */ 1.383 +template <typename IntType> 1.384 +nsresult 1.385 +GetIntFromJSObject(JSContext* aCtx, 1.386 + JS::Handle<JSObject *> aObject, 1.387 + const char* aProperty, 1.388 + IntType* _int) 1.389 +{ 1.390 + JS::Rooted<JS::Value> value(aCtx); 1.391 + bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value); 1.392 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.393 + if (JSVAL_IS_VOID(value)) { 1.394 + return NS_ERROR_INVALID_ARG; 1.395 + } 1.396 + NS_ENSURE_ARG(JSVAL_IS_PRIMITIVE(value)); 1.397 + NS_ENSURE_ARG(JSVAL_IS_NUMBER(value)); 1.398 + 1.399 + double num; 1.400 + rc = JS::ToNumber(aCtx, value, &num); 1.401 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.402 + NS_ENSURE_ARG(IntType(num) == num); 1.403 + 1.404 + *_int = IntType(num); 1.405 + return NS_OK; 1.406 +} 1.407 + 1.408 +/** 1.409 + * Obtains the specified property of a JSObject. 1.410 + * 1.411 + * @pre aArray must be an Array object. 1.412 + * 1.413 + * @param aCtx 1.414 + * The JSContext for aArray. 1.415 + * @param aArray 1.416 + * The JSObject to get the object from. 1.417 + * @param aIndex 1.418 + * The index to get the object from. 1.419 + * @param objOut 1.420 + * Set to the JSObject pointer on success. 1.421 + */ 1.422 +nsresult 1.423 +GetJSObjectFromArray(JSContext* aCtx, 1.424 + JS::Handle<JSObject*> aArray, 1.425 + uint32_t aIndex, 1.426 + JS::MutableHandle<JSObject*> objOut) 1.427 +{ 1.428 + NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray), 1.429 + "Must provide an object that is an array!"); 1.430 + 1.431 + JS::Rooted<JS::Value> value(aCtx); 1.432 + bool rc = JS_GetElement(aCtx, aArray, aIndex, &value); 1.433 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.434 + NS_ENSURE_ARG(!value.isPrimitive()); 1.435 + objOut.set(&value.toObject()); 1.436 + return NS_OK; 1.437 +} 1.438 + 1.439 +class VisitedQuery : public AsyncStatementCallback 1.440 +{ 1.441 +public: 1.442 + static nsresult Start(nsIURI* aURI, 1.443 + mozIVisitedStatusCallback* aCallback=nullptr) 1.444 + { 1.445 + NS_PRECONDITION(aURI, "Null URI"); 1.446 + 1.447 + // If we are a content process, always remote the request to the 1.448 + // parent process. 1.449 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.450 + URIParams uri; 1.451 + SerializeURI(aURI, uri); 1.452 + 1.453 + mozilla::dom::ContentChild* cpc = 1.454 + mozilla::dom::ContentChild::GetSingleton(); 1.455 + NS_ASSERTION(cpc, "Content Protocol is NULL!"); 1.456 + (void)cpc->SendStartVisitedQuery(uri); 1.457 + return NS_OK; 1.458 + } 1.459 + 1.460 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.461 + NS_ENSURE_STATE(navHistory); 1.462 + if (navHistory->hasEmbedVisit(aURI)) { 1.463 + nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true); 1.464 + NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY); 1.465 + // As per IHistory contract, we must notify asynchronously. 1.466 + nsCOMPtr<nsIRunnable> event = 1.467 + NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus); 1.468 + NS_DispatchToMainThread(event); 1.469 + 1.470 + return NS_OK; 1.471 + } 1.472 + 1.473 + History* history = History::GetService(); 1.474 + NS_ENSURE_STATE(history); 1.475 + mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement(); 1.476 + NS_ENSURE_STATE(stmt); 1.477 + 1.478 + // Bind by index for performance. 1.479 + nsresult rv = URIBinder::Bind(stmt, 0, aURI); 1.480 + NS_ENSURE_SUCCESS(rv, rv); 1.481 + 1.482 + nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback); 1.483 + NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY); 1.484 + 1.485 + nsCOMPtr<mozIStoragePendingStatement> handle; 1.486 + return stmt->ExecuteAsync(callback, getter_AddRefs(handle)); 1.487 + } 1.488 + 1.489 + NS_IMETHOD HandleResult(mozIStorageResultSet* aResults) 1.490 + { 1.491 + // If this method is called, we've gotten results, which means we have a 1.492 + // visit. 1.493 + mIsVisited = true; 1.494 + return NS_OK; 1.495 + } 1.496 + 1.497 + NS_IMETHOD HandleError(mozIStorageError* aError) 1.498 + { 1.499 + // mIsVisited is already set to false, and that's the assumption we will 1.500 + // make if an error occurred. 1.501 + return NS_OK; 1.502 + } 1.503 + 1.504 + NS_IMETHOD HandleCompletion(uint16_t aReason) 1.505 + { 1.506 + if (aReason != mozIStorageStatementCallback::REASON_FINISHED) { 1.507 + return NS_OK; 1.508 + } 1.509 + 1.510 + nsresult rv = NotifyVisitedStatus(); 1.511 + NS_ENSURE_SUCCESS(rv, rv); 1.512 + return NS_OK; 1.513 + } 1.514 + 1.515 + nsresult NotifyVisitedStatus() 1.516 + { 1.517 + // If an external handling callback is provided, just notify through it. 1.518 + if (mCallback) { 1.519 + mCallback->IsVisited(mURI, mIsVisited); 1.520 + return NS_OK; 1.521 + } 1.522 + 1.523 + if (mIsVisited) { 1.524 + History* history = History::GetService(); 1.525 + NS_ENSURE_STATE(history); 1.526 + history->NotifyVisited(mURI); 1.527 + } 1.528 + 1.529 + nsCOMPtr<nsIObserverService> observerService = 1.530 + mozilla::services::GetObserverService(); 1.531 + if (observerService) { 1.532 + nsAutoString status; 1.533 + if (mIsVisited) { 1.534 + status.AssignLiteral(URI_VISITED); 1.535 + } 1.536 + else { 1.537 + status.AssignLiteral(URI_NOT_VISITED); 1.538 + } 1.539 + (void)observerService->NotifyObservers(mURI, 1.540 + URI_VISITED_RESOLUTION_TOPIC, 1.541 + status.get()); 1.542 + } 1.543 + 1.544 + return NS_OK; 1.545 + } 1.546 + 1.547 +private: 1.548 + VisitedQuery(nsIURI* aURI, 1.549 + mozIVisitedStatusCallback *aCallback=nullptr, 1.550 + bool aIsVisited=false) 1.551 + : mURI(aURI) 1.552 + , mCallback(aCallback) 1.553 + , mIsVisited(aIsVisited) 1.554 + { 1.555 + } 1.556 + 1.557 + nsCOMPtr<nsIURI> mURI; 1.558 + nsCOMPtr<mozIVisitedStatusCallback> mCallback; 1.559 + bool mIsVisited; 1.560 +}; 1.561 + 1.562 +/** 1.563 + * Notifies observers about a visit. 1.564 + */ 1.565 +class NotifyVisitObservers : public nsRunnable 1.566 +{ 1.567 +public: 1.568 + NotifyVisitObservers(VisitData& aPlace, 1.569 + VisitData& aReferrer) 1.570 + : mPlace(aPlace) 1.571 + , mReferrer(aReferrer) 1.572 + , mHistory(History::GetService()) 1.573 + { 1.574 + } 1.575 + 1.576 + NS_IMETHOD Run() 1.577 + { 1.578 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.579 + 1.580 + // We are in the main thread, no need to lock. 1.581 + if (mHistory->IsShuttingDown()) { 1.582 + // If we are shutting down, we cannot notify the observers. 1.583 + return NS_OK; 1.584 + } 1.585 + 1.586 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.587 + if (!navHistory) { 1.588 + NS_WARNING("Trying to notify about a visit but cannot get the history service!"); 1.589 + return NS_OK; 1.590 + } 1.591 + 1.592 + nsCOMPtr<nsIURI> uri; 1.593 + (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec); 1.594 + 1.595 + // Notify the visit. Note that TRANSITION_EMBED visits are never added 1.596 + // to the database, thus cannot be queried and we don't notify them. 1.597 + if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) { 1.598 + navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime, 1.599 + mReferrer.visitId, mPlace.transitionType, 1.600 + mPlace.guid, mPlace.hidden); 1.601 + } 1.602 + 1.603 + nsCOMPtr<nsIObserverService> obsService = 1.604 + mozilla::services::GetObserverService(); 1.605 + if (obsService) { 1.606 + DebugOnly<nsresult> rv = 1.607 + obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr); 1.608 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers"); 1.609 + } 1.610 + 1.611 + History* history = History::GetService(); 1.612 + NS_ENSURE_STATE(history); 1.613 + history->AppendToRecentlyVisitedURIs(uri); 1.614 + history->NotifyVisited(uri); 1.615 + 1.616 + return NS_OK; 1.617 + } 1.618 +private: 1.619 + VisitData mPlace; 1.620 + VisitData mReferrer; 1.621 + nsRefPtr<History> mHistory; 1.622 +}; 1.623 + 1.624 +/** 1.625 + * Notifies observers about a pages title changing. 1.626 + */ 1.627 +class NotifyTitleObservers : public nsRunnable 1.628 +{ 1.629 +public: 1.630 + /** 1.631 + * Notifies observers on the main thread. 1.632 + * 1.633 + * @param aSpec 1.634 + * The spec of the URI to notify about. 1.635 + * @param aTitle 1.636 + * The new title to notify about. 1.637 + */ 1.638 + NotifyTitleObservers(const nsCString& aSpec, 1.639 + const nsString& aTitle, 1.640 + const nsCString& aGUID) 1.641 + : mSpec(aSpec) 1.642 + , mTitle(aTitle) 1.643 + , mGUID(aGUID) 1.644 + { 1.645 + } 1.646 + 1.647 + NS_IMETHOD Run() 1.648 + { 1.649 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.650 + 1.651 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.652 + NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY); 1.653 + nsCOMPtr<nsIURI> uri; 1.654 + (void)NS_NewURI(getter_AddRefs(uri), mSpec); 1.655 + navHistory->NotifyTitleChange(uri, mTitle, mGUID); 1.656 + 1.657 + return NS_OK; 1.658 + } 1.659 +private: 1.660 + const nsCString mSpec; 1.661 + const nsString mTitle; 1.662 + const nsCString mGUID; 1.663 +}; 1.664 + 1.665 +/** 1.666 + * Helper class for methods which notify their callers through the 1.667 + * mozIVisitInfoCallback interface. 1.668 + */ 1.669 +class NotifyPlaceInfoCallback : public nsRunnable 1.670 +{ 1.671 +public: 1.672 + NotifyPlaceInfoCallback(mozIVisitInfoCallback* aCallback, 1.673 + const VisitData& aPlace, 1.674 + bool aIsSingleVisit, 1.675 + nsresult aResult) 1.676 + : mCallback(aCallback) 1.677 + , mPlace(aPlace) 1.678 + , mResult(aResult) 1.679 + , mIsSingleVisit(aIsSingleVisit) 1.680 + { 1.681 + MOZ_ASSERT(aCallback, "Must pass a non-null callback!"); 1.682 + } 1.683 + 1.684 + NS_IMETHOD Run() 1.685 + { 1.686 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.687 + 1.688 + nsCOMPtr<nsIURI> referrerURI; 1.689 + if (!mPlace.referrerSpec.IsEmpty()) { 1.690 + (void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec); 1.691 + } 1.692 + 1.693 + nsCOMPtr<nsIURI> uri; 1.694 + (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec); 1.695 + 1.696 + nsCOMPtr<mozIPlaceInfo> place; 1.697 + if (mIsSingleVisit) { 1.698 + nsCOMPtr<mozIVisitInfo> visit = 1.699 + new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType, 1.700 + referrerURI.forget()); 1.701 + PlaceInfo::VisitsArray visits; 1.702 + (void)visits.AppendElement(visit); 1.703 + 1.704 + // The frecency isn't exposed because it may not reflect the updated value 1.705 + // in the case of InsertVisitedURIs. 1.706 + place = 1.707 + new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title, 1.708 + -1, visits); 1.709 + } 1.710 + else { 1.711 + // Same as above. 1.712 + place = 1.713 + new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title, 1.714 + -1); 1.715 + } 1.716 + 1.717 + if (NS_SUCCEEDED(mResult)) { 1.718 + (void)mCallback->HandleResult(place); 1.719 + } 1.720 + else { 1.721 + (void)mCallback->HandleError(mResult, place); 1.722 + } 1.723 + 1.724 + return NS_OK; 1.725 + } 1.726 + 1.727 +private: 1.728 + /** 1.729 + * Callers MUST hold a strong reference to this that outlives us because we 1.730 + * may be created off of the main thread, and therefore cannot call AddRef on 1.731 + * this object (and therefore cannot hold a strong reference to it). 1.732 + */ 1.733 + mozIVisitInfoCallback* mCallback; 1.734 + VisitData mPlace; 1.735 + const nsresult mResult; 1.736 + bool mIsSingleVisit; 1.737 +}; 1.738 + 1.739 +/** 1.740 + * Notifies a callback object when the operation is complete. 1.741 + */ 1.742 +class NotifyCompletion : public nsRunnable 1.743 +{ 1.744 +public: 1.745 + NotifyCompletion(mozIVisitInfoCallback* aCallback) 1.746 + : mCallback(aCallback) 1.747 + { 1.748 + MOZ_ASSERT(aCallback, "Must pass a non-null callback!"); 1.749 + } 1.750 + 1.751 + NS_IMETHOD Run() 1.752 + { 1.753 + if (NS_IsMainThread()) { 1.754 + (void)mCallback->HandleCompletion(); 1.755 + } 1.756 + else { 1.757 + (void)NS_DispatchToMainThread(this); 1.758 + 1.759 + // Also dispatch an event to release the reference to the callback after 1.760 + // we have run. 1.761 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.762 + (void)NS_ProxyRelease(mainThread, mCallback, true); 1.763 + } 1.764 + return NS_OK; 1.765 + } 1.766 + 1.767 +private: 1.768 + /** 1.769 + * Callers MUST hold a strong reference to this because we may be created 1.770 + * off of the main thread, and therefore cannot call AddRef on this object 1.771 + * (and therefore cannot hold a strong reference to it). If invoked from a 1.772 + * background thread, NotifyCompletion will release the reference to this. 1.773 + */ 1.774 + mozIVisitInfoCallback* mCallback; 1.775 +}; 1.776 + 1.777 +/** 1.778 + * Checks to see if we can add aURI to history, and dispatches an error to 1.779 + * aCallback (if provided) if we cannot. 1.780 + * 1.781 + * @param aURI 1.782 + * The URI to check. 1.783 + * @param [optional] aGUID 1.784 + * The guid of the URI to check. This is passed back to the callback. 1.785 + * @param [optional] aCallback 1.786 + * The callback to notify if the URI cannot be added to history. 1.787 + * @return true if the URI can be added to history, false otherwise. 1.788 + */ 1.789 +bool 1.790 +CanAddURI(nsIURI* aURI, 1.791 + const nsCString& aGUID = EmptyCString(), 1.792 + mozIVisitInfoCallback* aCallback = nullptr) 1.793 +{ 1.794 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.795 + NS_ENSURE_TRUE(navHistory, false); 1.796 + 1.797 + bool canAdd; 1.798 + nsresult rv = navHistory->CanAddURI(aURI, &canAdd); 1.799 + if (NS_SUCCEEDED(rv) && canAdd) { 1.800 + return true; 1.801 + }; 1.802 + 1.803 + // We cannot add the URI. Notify the callback, if we were given one. 1.804 + if (aCallback) { 1.805 + // NotifyPlaceInfoCallback does not hold a strong reference to the callback, so we 1.806 + // have to manage it by AddRefing now and then releasing it after the event 1.807 + // has run. 1.808 + NS_ADDREF(aCallback); 1.809 + 1.810 + VisitData place(aURI); 1.811 + place.guid = aGUID; 1.812 + nsCOMPtr<nsIRunnable> event = 1.813 + new NotifyPlaceInfoCallback(aCallback, place, true, NS_ERROR_INVALID_ARG); 1.814 + (void)NS_DispatchToMainThread(event); 1.815 + 1.816 + // Also dispatch an event to release our reference to the callback after 1.817 + // NotifyPlaceInfoCallback has run. 1.818 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.819 + (void)NS_ProxyRelease(mainThread, aCallback, true); 1.820 + } 1.821 + 1.822 + return false; 1.823 +} 1.824 + 1.825 +/** 1.826 + * Adds a visit to the database. 1.827 + */ 1.828 +class InsertVisitedURIs : public nsRunnable 1.829 +{ 1.830 +public: 1.831 + /** 1.832 + * Adds a visit to the database asynchronously. 1.833 + * 1.834 + * @param aConnection 1.835 + * The database connection to use for these operations. 1.836 + * @param aPlaces 1.837 + * The locations to record visits. 1.838 + * @param [optional] aCallback 1.839 + * The callback to notify about the visit. 1.840 + */ 1.841 + static nsresult Start(mozIStorageConnection* aConnection, 1.842 + nsTArray<VisitData>& aPlaces, 1.843 + mozIVisitInfoCallback* aCallback = nullptr) 1.844 + { 1.845 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.846 + MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!"); 1.847 + 1.848 + nsRefPtr<InsertVisitedURIs> event = 1.849 + new InsertVisitedURIs(aConnection, aPlaces, aCallback); 1.850 + 1.851 + // Get the target thread, and then start the work! 1.852 + nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection); 1.853 + NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1.854 + nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); 1.855 + NS_ENSURE_SUCCESS(rv, rv); 1.856 + 1.857 + return NS_OK; 1.858 + } 1.859 + 1.860 + NS_IMETHOD Run() 1.861 + { 1.862 + MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread"); 1.863 + 1.864 + // Prevent the main thread from shutting down while this is running. 1.865 + MutexAutoLock lockedScope(mHistory->GetShutdownMutex()); 1.866 + if (mHistory->IsShuttingDown()) { 1.867 + // If we were already shutting down, we cannot insert the URIs. 1.868 + return NS_OK; 1.869 + } 1.870 + 1.871 + mozStorageTransaction transaction(mDBConn, false, 1.872 + mozIStorageConnection::TRANSACTION_IMMEDIATE); 1.873 + 1.874 + VisitData* lastPlace = nullptr; 1.875 + for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) { 1.876 + VisitData& place = mPlaces.ElementAt(i); 1.877 + VisitData& referrer = mReferrers.ElementAt(i); 1.878 + 1.879 + // We can avoid a database lookup if it's the same place as the last 1.880 + // visit we added. 1.881 + bool known = lastPlace && lastPlace->IsSamePlaceAs(place); 1.882 + if (!known) { 1.883 + nsresult rv = mHistory->FetchPageInfo(place, &known); 1.884 + if (NS_FAILED(rv)) { 1.885 + if (mCallback) { 1.886 + nsCOMPtr<nsIRunnable> event = 1.887 + new NotifyPlaceInfoCallback(mCallback, place, true, rv); 1.888 + return NS_DispatchToMainThread(event); 1.889 + } 1.890 + return NS_OK; 1.891 + } 1.892 + } 1.893 + 1.894 + FetchReferrerInfo(referrer, place); 1.895 + 1.896 + nsresult rv = DoDatabaseInserts(known, place, referrer); 1.897 + if (mCallback) { 1.898 + nsCOMPtr<nsIRunnable> event = 1.899 + new NotifyPlaceInfoCallback(mCallback, place, true, rv); 1.900 + nsresult rv2 = NS_DispatchToMainThread(event); 1.901 + NS_ENSURE_SUCCESS(rv2, rv2); 1.902 + } 1.903 + NS_ENSURE_SUCCESS(rv, rv); 1.904 + 1.905 + nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer); 1.906 + rv = NS_DispatchToMainThread(event); 1.907 + NS_ENSURE_SUCCESS(rv, rv); 1.908 + 1.909 + // Notify about title change if needed. 1.910 + if ((!known && !place.title.IsVoid()) || place.titleChanged) { 1.911 + event = new NotifyTitleObservers(place.spec, place.title, place.guid); 1.912 + rv = NS_DispatchToMainThread(event); 1.913 + NS_ENSURE_SUCCESS(rv, rv); 1.914 + } 1.915 + 1.916 + lastPlace = &mPlaces.ElementAt(i); 1.917 + } 1.918 + 1.919 + nsresult rv = transaction.Commit(); 1.920 + NS_ENSURE_SUCCESS(rv, rv); 1.921 + 1.922 + return NS_OK; 1.923 + } 1.924 +private: 1.925 + InsertVisitedURIs(mozIStorageConnection* aConnection, 1.926 + nsTArray<VisitData>& aPlaces, 1.927 + mozIVisitInfoCallback* aCallback) 1.928 + : mDBConn(aConnection) 1.929 + , mCallback(aCallback) 1.930 + , mHistory(History::GetService()) 1.931 + { 1.932 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.933 + 1.934 + (void)mPlaces.SwapElements(aPlaces); 1.935 + (void)mReferrers.SetLength(mPlaces.Length()); 1.936 + 1.937 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.938 + NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!"); 1.939 + 1.940 + for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) { 1.941 + mReferrers[i].spec = mPlaces[i].referrerSpec; 1.942 + 1.943 +#ifdef DEBUG 1.944 + nsCOMPtr<nsIURI> uri; 1.945 + (void)NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec); 1.946 + NS_ASSERTION(CanAddURI(uri), 1.947 + "Passed a VisitData with a URI we cannot add to history!"); 1.948 +#endif 1.949 + } 1.950 + } 1.951 + 1.952 + virtual ~InsertVisitedURIs() 1.953 + { 1.954 + if (mCallback) { 1.955 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.956 + (void)NS_ProxyRelease(mainThread, mCallback, true); 1.957 + } 1.958 + } 1.959 + 1.960 + /** 1.961 + * Inserts or updates the entry in moz_places for this visit, adds the visit, 1.962 + * and updates the frecency of the place. 1.963 + * 1.964 + * @param aKnown 1.965 + * True if we already have an entry for this place in moz_places, false 1.966 + * otherwise. 1.967 + * @param aPlace 1.968 + * The place we are adding a visit for. 1.969 + * @param aReferrer 1.970 + * The referrer for aPlace. 1.971 + */ 1.972 + nsresult DoDatabaseInserts(bool aKnown, 1.973 + VisitData& aPlace, 1.974 + VisitData& aReferrer) 1.975 + { 1.976 + MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread"); 1.977 + 1.978 + // If the page was in moz_places, we need to update the entry. 1.979 + nsresult rv; 1.980 + if (aKnown) { 1.981 + rv = mHistory->UpdatePlace(aPlace); 1.982 + NS_ENSURE_SUCCESS(rv, rv); 1.983 + } 1.984 + // Otherwise, the page was not in moz_places, so now we have to add it. 1.985 + else { 1.986 + rv = mHistory->InsertPlace(aPlace); 1.987 + NS_ENSURE_SUCCESS(rv, rv); 1.988 + 1.989 + // We need the place id and guid of the page we just inserted when we 1.990 + // have a callback or when the GUID isn't known. No point in doing the 1.991 + // disk I/O if we do not need it. 1.992 + if (mCallback || aPlace.guid.IsEmpty()) { 1.993 + bool exists; 1.994 + rv = mHistory->FetchPageInfo(aPlace, &exists); 1.995 + NS_ENSURE_SUCCESS(rv, rv); 1.996 + 1.997 + if (!exists) { 1.998 + NS_NOTREACHED("should have an entry in moz_places"); 1.999 + } 1.1000 + } 1.1001 + } 1.1002 + 1.1003 + rv = AddVisit(aPlace, aReferrer); 1.1004 + NS_ENSURE_SUCCESS(rv, rv); 1.1005 + 1.1006 + // TODO (bug 623969) we shouldn't update this after each visit, but 1.1007 + // rather only for each unique place to save disk I/O. 1.1008 + 1.1009 + // Don't update frecency if the page should not appear in autocomplete. 1.1010 + if (aPlace.shouldUpdateFrecency) { 1.1011 + rv = UpdateFrecency(aPlace); 1.1012 + NS_ENSURE_SUCCESS(rv, rv); 1.1013 + } 1.1014 + 1.1015 + return NS_OK; 1.1016 + } 1.1017 + 1.1018 + /** 1.1019 + * Loads visit information about the page into _place. 1.1020 + * 1.1021 + * @param _place 1.1022 + * The VisitData for the place we need to know visit information about. 1.1023 + * @param [optional] aThresholdStart 1.1024 + * The timestamp of a new visit (not represented by _place) used to 1.1025 + * determine if the page was recently visited or not. 1.1026 + * @return true if the page was recently (determined with aThresholdStart) 1.1027 + * visited, false otherwise. 1.1028 + */ 1.1029 + bool FetchVisitInfo(VisitData& _place, 1.1030 + PRTime aThresholdStart = 0) 1.1031 + { 1.1032 + NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!"); 1.1033 + 1.1034 + nsCOMPtr<mozIStorageStatement> stmt; 1.1035 + // If we have a visitTime, we want information on that specific visit. 1.1036 + if (_place.visitTime) { 1.1037 + stmt = mHistory->GetStatement( 1.1038 + "SELECT id, visit_date " 1.1039 + "FROM moz_historyvisits " 1.1040 + "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " 1.1041 + "AND visit_date = :visit_date " 1.1042 + ); 1.1043 + NS_ENSURE_TRUE(stmt, false); 1.1044 + 1.1045 + mozStorageStatementScoper scoper(stmt); 1.1046 + nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"), 1.1047 + _place.visitTime); 1.1048 + NS_ENSURE_SUCCESS(rv, false); 1.1049 + 1.1050 + scoper.Abandon(); 1.1051 + } 1.1052 + // Otherwise, we want information about the most recent visit. 1.1053 + else { 1.1054 + stmt = mHistory->GetStatement( 1.1055 + "SELECT id, visit_date " 1.1056 + "FROM moz_historyvisits " 1.1057 + "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) " 1.1058 + "ORDER BY visit_date DESC " 1.1059 + ); 1.1060 + NS_ENSURE_TRUE(stmt, false); 1.1061 + } 1.1062 + mozStorageStatementScoper scoper(stmt); 1.1063 + 1.1064 + nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), 1.1065 + _place.spec); 1.1066 + NS_ENSURE_SUCCESS(rv, false); 1.1067 + 1.1068 + bool hasResult; 1.1069 + rv = stmt->ExecuteStep(&hasResult); 1.1070 + NS_ENSURE_SUCCESS(rv, false); 1.1071 + if (!hasResult) { 1.1072 + return false; 1.1073 + } 1.1074 + 1.1075 + rv = stmt->GetInt64(0, &_place.visitId); 1.1076 + NS_ENSURE_SUCCESS(rv, false); 1.1077 + rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime)); 1.1078 + NS_ENSURE_SUCCESS(rv, false); 1.1079 + 1.1080 + // If we have been given a visit threshold start time, go ahead and 1.1081 + // calculate if we have been recently visited. 1.1082 + if (aThresholdStart && 1.1083 + aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) { 1.1084 + return true; 1.1085 + } 1.1086 + 1.1087 + return false; 1.1088 + } 1.1089 + 1.1090 + /** 1.1091 + * Fetches information about a referrer for aPlace if it was a recent 1.1092 + * visit or not. 1.1093 + * 1.1094 + * @param aReferrer 1.1095 + * The VisitData for the referrer. This will be populated with 1.1096 + * FetchVisitInfo. 1.1097 + * @param aPlace 1.1098 + * The VisitData for the visit we will eventually add. 1.1099 + * 1.1100 + */ 1.1101 + void FetchReferrerInfo(VisitData& aReferrer, 1.1102 + VisitData& aPlace) 1.1103 + { 1.1104 + if (aReferrer.spec.IsEmpty()) { 1.1105 + return; 1.1106 + } 1.1107 + 1.1108 + if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) { 1.1109 + // We must change both the place and referrer to indicate that we will 1.1110 + // not be using the referrer's data. This behavior has test coverage, so 1.1111 + // if this invariant changes, we'll know. 1.1112 + aPlace.referrerSpec.Truncate(); 1.1113 + aReferrer.visitId = 0; 1.1114 + } 1.1115 + } 1.1116 + 1.1117 + /** 1.1118 + * Adds a visit for _place and updates it with the right visit id. 1.1119 + * 1.1120 + * @param _place 1.1121 + * The VisitData for the place we need to know visit information about. 1.1122 + * @param aReferrer 1.1123 + * A reference to the referrer's visit data. 1.1124 + */ 1.1125 + nsresult AddVisit(VisitData& _place, 1.1126 + const VisitData& aReferrer) 1.1127 + { 1.1128 + nsresult rv; 1.1129 + nsCOMPtr<mozIStorageStatement> stmt; 1.1130 + if (_place.placeId) { 1.1131 + stmt = mHistory->GetStatement( 1.1132 + "INSERT INTO moz_historyvisits " 1.1133 + "(from_visit, place_id, visit_date, visit_type, session) " 1.1134 + "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) " 1.1135 + ); 1.1136 + NS_ENSURE_STATE(stmt); 1.1137 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId); 1.1138 + NS_ENSURE_SUCCESS(rv, rv); 1.1139 + } 1.1140 + else { 1.1141 + stmt = mHistory->GetStatement( 1.1142 + "INSERT INTO moz_historyvisits " 1.1143 + "(from_visit, place_id, visit_date, visit_type, session) " 1.1144 + "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) " 1.1145 + ); 1.1146 + NS_ENSURE_STATE(stmt); 1.1147 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec); 1.1148 + NS_ENSURE_SUCCESS(rv, rv); 1.1149 + } 1.1150 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"), 1.1151 + aReferrer.visitId); 1.1152 + NS_ENSURE_SUCCESS(rv, rv); 1.1153 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"), 1.1154 + _place.visitTime); 1.1155 + NS_ENSURE_SUCCESS(rv, rv); 1.1156 + uint32_t transitionType = _place.transitionType; 1.1157 + NS_ASSERTION(transitionType >= nsINavHistoryService::TRANSITION_LINK && 1.1158 + transitionType <= nsINavHistoryService::TRANSITION_FRAMED_LINK, 1.1159 + "Invalid transition type!"); 1.1160 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"), 1.1161 + transitionType); 1.1162 + NS_ENSURE_SUCCESS(rv, rv); 1.1163 + 1.1164 + mozStorageStatementScoper scoper(stmt); 1.1165 + rv = stmt->Execute(); 1.1166 + NS_ENSURE_SUCCESS(rv, rv); 1.1167 + 1.1168 + // Now that it should be in the database, we need to obtain the id of the 1.1169 + // visit we just added. 1.1170 + (void)FetchVisitInfo(_place); 1.1171 + 1.1172 + return NS_OK; 1.1173 + } 1.1174 + 1.1175 + /** 1.1176 + * Updates the frecency, and possibly the hidden-ness of aPlace. 1.1177 + * 1.1178 + * @param aPlace 1.1179 + * The VisitData for the place we want to update. 1.1180 + */ 1.1181 + nsresult UpdateFrecency(const VisitData& aPlace) 1.1182 + { 1.1183 + MOZ_ASSERT(aPlace.shouldUpdateFrecency); 1.1184 + 1.1185 + nsresult rv; 1.1186 + { // First, set our frecency to the proper value. 1.1187 + nsCOMPtr<mozIStorageStatement> stmt; 1.1188 + if (aPlace.placeId) { 1.1189 + stmt = mHistory->GetStatement( 1.1190 + "UPDATE moz_places " 1.1191 + "SET frecency = NOTIFY_FRECENCY(" 1.1192 + "CALCULATE_FRECENCY(:page_id), " 1.1193 + "url, guid, hidden, last_visit_date" 1.1194 + ") " 1.1195 + "WHERE id = :page_id" 1.1196 + ); 1.1197 + NS_ENSURE_STATE(stmt); 1.1198 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId); 1.1199 + NS_ENSURE_SUCCESS(rv, rv); 1.1200 + } 1.1201 + else { 1.1202 + stmt = mHistory->GetStatement( 1.1203 + "UPDATE moz_places " 1.1204 + "SET frecency = NOTIFY_FRECENCY(" 1.1205 + "CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date" 1.1206 + ") " 1.1207 + "WHERE url = :page_url" 1.1208 + ); 1.1209 + NS_ENSURE_STATE(stmt); 1.1210 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec); 1.1211 + NS_ENSURE_SUCCESS(rv, rv); 1.1212 + } 1.1213 + mozStorageStatementScoper scoper(stmt); 1.1214 + 1.1215 + rv = stmt->Execute(); 1.1216 + NS_ENSURE_SUCCESS(rv, rv); 1.1217 + } 1.1218 + 1.1219 + if (!aPlace.hidden) { 1.1220 + // Mark the page as not hidden if the frecency is now nonzero. 1.1221 + nsCOMPtr<mozIStorageStatement> stmt; 1.1222 + if (aPlace.placeId) { 1.1223 + stmt = mHistory->GetStatement( 1.1224 + "UPDATE moz_places " 1.1225 + "SET hidden = 0 " 1.1226 + "WHERE id = :page_id AND frecency <> 0" 1.1227 + ); 1.1228 + NS_ENSURE_STATE(stmt); 1.1229 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId); 1.1230 + NS_ENSURE_SUCCESS(rv, rv); 1.1231 + } 1.1232 + else { 1.1233 + stmt = mHistory->GetStatement( 1.1234 + "UPDATE moz_places " 1.1235 + "SET hidden = 0 " 1.1236 + "WHERE url = :page_url AND frecency <> 0" 1.1237 + ); 1.1238 + NS_ENSURE_STATE(stmt); 1.1239 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec); 1.1240 + NS_ENSURE_SUCCESS(rv, rv); 1.1241 + } 1.1242 + 1.1243 + mozStorageStatementScoper scoper(stmt); 1.1244 + rv = stmt->Execute(); 1.1245 + NS_ENSURE_SUCCESS(rv, rv); 1.1246 + } 1.1247 + 1.1248 + return NS_OK; 1.1249 + } 1.1250 + 1.1251 + mozIStorageConnection* mDBConn; 1.1252 + 1.1253 + nsTArray<VisitData> mPlaces; 1.1254 + nsTArray<VisitData> mReferrers; 1.1255 + 1.1256 + nsCOMPtr<mozIVisitInfoCallback> mCallback; 1.1257 + 1.1258 + /** 1.1259 + * Strong reference to the History object because we do not want it to 1.1260 + * disappear out from under us. 1.1261 + */ 1.1262 + nsRefPtr<History> mHistory; 1.1263 +}; 1.1264 + 1.1265 +class GetPlaceInfo MOZ_FINAL : public nsRunnable { 1.1266 +public: 1.1267 + /** 1.1268 + * Get the place info for a given place (by GUID or URI) asynchronously. 1.1269 + */ 1.1270 + static nsresult Start(mozIStorageConnection* aConnection, 1.1271 + VisitData& aPlace, 1.1272 + mozIVisitInfoCallback* aCallback) { 1.1273 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1274 + 1.1275 + nsRefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, aCallback); 1.1276 + 1.1277 + // Get the target thread, and then start the work! 1.1278 + nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection); 1.1279 + NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1.1280 + nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); 1.1281 + NS_ENSURE_SUCCESS(rv, rv); 1.1282 + 1.1283 + return NS_OK; 1.1284 + } 1.1285 + 1.1286 + NS_IMETHOD Run() 1.1287 + { 1.1288 + MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread"); 1.1289 + 1.1290 + bool exists; 1.1291 + nsresult rv = mHistory->FetchPageInfo(mPlace, &exists); 1.1292 + NS_ENSURE_SUCCESS(rv, rv); 1.1293 + 1.1294 + if (!exists) 1.1295 + rv = NS_ERROR_NOT_AVAILABLE; 1.1296 + 1.1297 + nsCOMPtr<nsIRunnable> event = 1.1298 + new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv); 1.1299 + 1.1300 + rv = NS_DispatchToMainThread(event); 1.1301 + NS_ENSURE_SUCCESS(rv, rv); 1.1302 + 1.1303 + return NS_OK; 1.1304 + } 1.1305 +private: 1.1306 + GetPlaceInfo(VisitData& aPlace, 1.1307 + mozIVisitInfoCallback* aCallback) 1.1308 + : mPlace(aPlace) 1.1309 + , mCallback(aCallback) 1.1310 + , mHistory(History::GetService()) 1.1311 + { 1.1312 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1313 + } 1.1314 + 1.1315 + virtual ~GetPlaceInfo() 1.1316 + { 1.1317 + if (mCallback) { 1.1318 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.1319 + (void)NS_ProxyRelease(mainThread, mCallback, true); 1.1320 + } 1.1321 + } 1.1322 + 1.1323 + VisitData mPlace; 1.1324 + nsCOMPtr<mozIVisitInfoCallback> mCallback; 1.1325 + nsRefPtr<History> mHistory; 1.1326 +}; 1.1327 + 1.1328 +/** 1.1329 + * Sets the page title for a page in moz_places (if necessary). 1.1330 + */ 1.1331 +class SetPageTitle : public nsRunnable 1.1332 +{ 1.1333 +public: 1.1334 + /** 1.1335 + * Sets a pages title in the database asynchronously. 1.1336 + * 1.1337 + * @param aConnection 1.1338 + * The database connection to use for this operation. 1.1339 + * @param aURI 1.1340 + * The URI to set the page title on. 1.1341 + * @param aTitle 1.1342 + * The title to set for the page, if the page exists. 1.1343 + */ 1.1344 + static nsresult Start(mozIStorageConnection* aConnection, 1.1345 + nsIURI* aURI, 1.1346 + const nsAString& aTitle) 1.1347 + { 1.1348 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1349 + MOZ_ASSERT(aURI, "Must pass a non-null URI object!"); 1.1350 + 1.1351 + nsCString spec; 1.1352 + nsresult rv = aURI->GetSpec(spec); 1.1353 + NS_ENSURE_SUCCESS(rv, rv); 1.1354 + 1.1355 + nsRefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle); 1.1356 + 1.1357 + // Get the target thread, and then start the work! 1.1358 + nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection); 1.1359 + NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1.1360 + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); 1.1361 + NS_ENSURE_SUCCESS(rv, rv); 1.1362 + 1.1363 + return NS_OK; 1.1364 + } 1.1365 + 1.1366 + NS_IMETHOD Run() 1.1367 + { 1.1368 + MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread"); 1.1369 + 1.1370 + // First, see if the page exists in the database (we'll need its id later). 1.1371 + bool exists; 1.1372 + nsresult rv = mHistory->FetchPageInfo(mPlace, &exists); 1.1373 + NS_ENSURE_SUCCESS(rv, rv); 1.1374 + 1.1375 + if (!exists || !mPlace.titleChanged) { 1.1376 + // We have no record of this page, or we have no title change, so there 1.1377 + // is no need to do any further work. 1.1378 + return NS_OK; 1.1379 + } 1.1380 + 1.1381 + NS_ASSERTION(mPlace.placeId > 0, 1.1382 + "We somehow have an invalid place id here!"); 1.1383 + 1.1384 + // Now we can update our database record. 1.1385 + nsCOMPtr<mozIStorageStatement> stmt = 1.1386 + mHistory->GetStatement( 1.1387 + "UPDATE moz_places " 1.1388 + "SET title = :page_title " 1.1389 + "WHERE id = :page_id " 1.1390 + ); 1.1391 + NS_ENSURE_STATE(stmt); 1.1392 + 1.1393 + { 1.1394 + mozStorageStatementScoper scoper(stmt); 1.1395 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId); 1.1396 + NS_ENSURE_SUCCESS(rv, rv); 1.1397 + // Empty strings should clear the title, just like 1.1398 + // nsNavHistory::SetPageTitle. 1.1399 + if (mPlace.title.IsEmpty()) { 1.1400 + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title")); 1.1401 + } 1.1402 + else { 1.1403 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"), 1.1404 + StringHead(mPlace.title, TITLE_LENGTH_MAX)); 1.1405 + } 1.1406 + NS_ENSURE_SUCCESS(rv, rv); 1.1407 + rv = stmt->Execute(); 1.1408 + NS_ENSURE_SUCCESS(rv, rv); 1.1409 + } 1.1410 + 1.1411 + nsCOMPtr<nsIRunnable> event = 1.1412 + new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid); 1.1413 + rv = NS_DispatchToMainThread(event); 1.1414 + NS_ENSURE_SUCCESS(rv, rv); 1.1415 + 1.1416 + return NS_OK; 1.1417 + } 1.1418 + 1.1419 +private: 1.1420 + SetPageTitle(const nsCString& aSpec, 1.1421 + const nsAString& aTitle) 1.1422 + : mHistory(History::GetService()) 1.1423 + { 1.1424 + mPlace.spec = aSpec; 1.1425 + mPlace.title = aTitle; 1.1426 + } 1.1427 + 1.1428 + VisitData mPlace; 1.1429 + 1.1430 + /** 1.1431 + * Strong reference to the History object because we do not want it to 1.1432 + * disappear out from under us. 1.1433 + */ 1.1434 + nsRefPtr<History> mHistory; 1.1435 +}; 1.1436 + 1.1437 +/** 1.1438 + * Adds download-specific annotations to a download page. 1.1439 + */ 1.1440 +class SetDownloadAnnotations MOZ_FINAL : public mozIVisitInfoCallback 1.1441 +{ 1.1442 +public: 1.1443 + NS_DECL_ISUPPORTS 1.1444 + 1.1445 + SetDownloadAnnotations(nsIURI* aDestination) 1.1446 + : mDestination(aDestination) 1.1447 + , mHistory(History::GetService()) 1.1448 + { 1.1449 + MOZ_ASSERT(mDestination); 1.1450 + MOZ_ASSERT(NS_IsMainThread()); 1.1451 + } 1.1452 + 1.1453 + NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo) 1.1454 + { 1.1455 + // Just don't add the annotations in case the visit isn't added. 1.1456 + return NS_OK; 1.1457 + } 1.1458 + 1.1459 + NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo) 1.1460 + { 1.1461 + // Exit silently if the download destination is not a local file. 1.1462 + nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination); 1.1463 + if (!destinationFileURL) { 1.1464 + return NS_OK; 1.1465 + } 1.1466 + 1.1467 + nsCOMPtr<nsIURI> source; 1.1468 + nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source)); 1.1469 + NS_ENSURE_SUCCESS(rv, rv); 1.1470 + 1.1471 + nsCOMPtr<nsIFile> destinationFile; 1.1472 + rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile)); 1.1473 + NS_ENSURE_SUCCESS(rv, rv); 1.1474 + 1.1475 + nsAutoString destinationFileName; 1.1476 + rv = destinationFile->GetLeafName(destinationFileName); 1.1477 + NS_ENSURE_SUCCESS(rv, rv); 1.1478 + 1.1479 + nsAutoCString destinationURISpec; 1.1480 + rv = destinationFileURL->GetSpec(destinationURISpec); 1.1481 + NS_ENSURE_SUCCESS(rv, rv); 1.1482 + 1.1483 + // Use annotations for storing the additional download metadata. 1.1484 + nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService(); 1.1485 + NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY); 1.1486 + 1.1487 + rv = annosvc->SetPageAnnotationString( 1.1488 + source, 1.1489 + DESTINATIONFILEURI_ANNO, 1.1490 + NS_ConvertUTF8toUTF16(destinationURISpec), 1.1491 + 0, 1.1492 + nsIAnnotationService::EXPIRE_WITH_HISTORY 1.1493 + ); 1.1494 + NS_ENSURE_SUCCESS(rv, rv); 1.1495 + 1.1496 + rv = annosvc->SetPageAnnotationString( 1.1497 + source, 1.1498 + DESTINATIONFILENAME_ANNO, 1.1499 + destinationFileName, 1.1500 + 0, 1.1501 + nsIAnnotationService::EXPIRE_WITH_HISTORY 1.1502 + ); 1.1503 + NS_ENSURE_SUCCESS(rv, rv); 1.1504 + 1.1505 + nsAutoString title; 1.1506 + rv = aPlaceInfo->GetTitle(title); 1.1507 + NS_ENSURE_SUCCESS(rv, rv); 1.1508 + 1.1509 + // In case we are downloading a file that does not correspond to a web 1.1510 + // page for which the title is present, we populate the otherwise empty 1.1511 + // history title with the name of the destination file, to allow it to be 1.1512 + // visible and searchable in history results. 1.1513 + if (title.IsEmpty()) { 1.1514 + rv = mHistory->SetURITitle(source, destinationFileName); 1.1515 + NS_ENSURE_SUCCESS(rv, rv); 1.1516 + } 1.1517 + 1.1518 + return NS_OK; 1.1519 + } 1.1520 + 1.1521 + NS_IMETHOD HandleCompletion() 1.1522 + { 1.1523 + return NS_OK; 1.1524 + } 1.1525 + 1.1526 +private: 1.1527 + nsCOMPtr<nsIURI> mDestination; 1.1528 + 1.1529 + /** 1.1530 + * Strong reference to the History object because we do not want it to 1.1531 + * disappear out from under us. 1.1532 + */ 1.1533 + nsRefPtr<History> mHistory; 1.1534 +}; 1.1535 +NS_IMPL_ISUPPORTS( 1.1536 + SetDownloadAnnotations, 1.1537 + mozIVisitInfoCallback 1.1538 +) 1.1539 + 1.1540 +/** 1.1541 + * Enumerator used by NotifyRemoveVisits to transfer the hash entries. 1.1542 + */ 1.1543 +static PLDHashOperator TransferHashEntries(PlaceHashKey* aEntry, 1.1544 + void* aHash) 1.1545 +{ 1.1546 + nsTHashtable<PlaceHashKey>* hash = 1.1547 + static_cast<nsTHashtable<PlaceHashKey> *>(aHash); 1.1548 + PlaceHashKey* copy = hash->PutEntry(aEntry->GetKey()); 1.1549 + copy->visitCount = aEntry->visitCount; 1.1550 + copy->bookmarked = aEntry->bookmarked; 1.1551 + aEntry->visits.SwapElements(copy->visits); 1.1552 + return PL_DHASH_NEXT; 1.1553 +} 1.1554 + 1.1555 +/** 1.1556 + * Enumerator used by NotifyRemoveVisits to notify removals. 1.1557 + */ 1.1558 +static PLDHashOperator NotifyVisitRemoval(PlaceHashKey* aEntry, 1.1559 + void* aHistory) 1.1560 +{ 1.1561 + nsNavHistory* history = static_cast<nsNavHistory *>(aHistory); 1.1562 + const nsTArray<VisitData>& visits = aEntry->visits; 1.1563 + nsCOMPtr<nsIURI> uri; 1.1564 + (void)NS_NewURI(getter_AddRefs(uri), visits[0].spec); 1.1565 + bool removingPage = visits.Length() == aEntry->visitCount && 1.1566 + !aEntry->bookmarked; 1.1567 + // FindRemovableVisits only sets the transition type on the VisitData objects 1.1568 + // it collects if the visits were filtered by transition type. 1.1569 + // RemoveVisitsFilter currently only supports filtering by transition type, so 1.1570 + // FindRemovableVisits will either find all visits, or all visits of a given 1.1571 + // type. Therefore, if transitionType is set on this visit, we pass the 1.1572 + // transition type to NotifyOnPageExpired which in turns passes it to 1.1573 + // OnDeleteVisits to indicate that all visits of a given type were removed. 1.1574 + uint32_t transition = visits[0].transitionType < UINT32_MAX ? 1.1575 + visits[0].transitionType : 0; 1.1576 + history->NotifyOnPageExpired(uri, visits[0].visitTime, removingPage, 1.1577 + visits[0].guid, 1.1578 + nsINavHistoryObserver::REASON_DELETED, 1.1579 + transition); 1.1580 + return PL_DHASH_NEXT; 1.1581 +} 1.1582 + 1.1583 +/** 1.1584 + * Notify removed visits to observers. 1.1585 + */ 1.1586 +class NotifyRemoveVisits : public nsRunnable 1.1587 +{ 1.1588 +public: 1.1589 + 1.1590 + NotifyRemoveVisits(nsTHashtable<PlaceHashKey>& aPlaces) 1.1591 + : mPlaces(VISITS_REMOVAL_INITIAL_HASH_SIZE) 1.1592 + , mHistory(History::GetService()) 1.1593 + { 1.1594 + MOZ_ASSERT(!NS_IsMainThread(), 1.1595 + "This should not be called on the main thread"); 1.1596 + aPlaces.EnumerateEntries(TransferHashEntries, &mPlaces); 1.1597 + } 1.1598 + 1.1599 + NS_IMETHOD Run() 1.1600 + { 1.1601 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1602 + 1.1603 + // We are in the main thread, no need to lock. 1.1604 + if (mHistory->IsShuttingDown()) { 1.1605 + // If we are shutting down, we cannot notify the observers. 1.1606 + return NS_OK; 1.1607 + } 1.1608 + 1.1609 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.1610 + if (!navHistory) { 1.1611 + NS_WARNING("Cannot notify without the history service!"); 1.1612 + return NS_OK; 1.1613 + } 1.1614 + 1.1615 + // Wrap all notifications in a batch, so the view can handle changes in a 1.1616 + // more performant way, by initiating a refresh after a limited number of 1.1617 + // single changes. 1.1618 + (void)navHistory->BeginUpdateBatch(); 1.1619 + mPlaces.EnumerateEntries(NotifyVisitRemoval, navHistory); 1.1620 + (void)navHistory->EndUpdateBatch(); 1.1621 + 1.1622 + return NS_OK; 1.1623 + } 1.1624 + 1.1625 +private: 1.1626 + nsTHashtable<PlaceHashKey> mPlaces; 1.1627 + 1.1628 + /** 1.1629 + * Strong reference to the History object because we do not want it to 1.1630 + * disappear out from under us. 1.1631 + */ 1.1632 + nsRefPtr<History> mHistory; 1.1633 +}; 1.1634 + 1.1635 +/** 1.1636 + * Enumerator used by RemoveVisits to populate list of removed place ids. 1.1637 + */ 1.1638 +static PLDHashOperator ListToBeRemovedPlaceIds(PlaceHashKey* aEntry, 1.1639 + void* aIdsList) 1.1640 +{ 1.1641 + const nsTArray<VisitData>& visits = aEntry->visits; 1.1642 + // Only orphan ids should be listed. 1.1643 + if (visits.Length() == aEntry->visitCount && !aEntry->bookmarked) { 1.1644 + nsCString* list = static_cast<nsCString*>(aIdsList); 1.1645 + if (!list->IsEmpty()) 1.1646 + list->AppendLiteral(","); 1.1647 + list->AppendInt(visits[0].placeId); 1.1648 + } 1.1649 + return PL_DHASH_NEXT; 1.1650 +} 1.1651 + 1.1652 +/** 1.1653 + * Remove visits from history. 1.1654 + */ 1.1655 +class RemoveVisits : public nsRunnable 1.1656 +{ 1.1657 +public: 1.1658 + /** 1.1659 + * Asynchronously removes visits from history. 1.1660 + * 1.1661 + * @param aConnection 1.1662 + * The database connection to use for these operations. 1.1663 + * @param aFilter 1.1664 + * Filter to remove visits. 1.1665 + */ 1.1666 + static nsresult Start(mozIStorageConnection* aConnection, 1.1667 + RemoveVisitsFilter& aFilter) 1.1668 + { 1.1669 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1670 + 1.1671 + nsRefPtr<RemoveVisits> event = new RemoveVisits(aConnection, aFilter); 1.1672 + 1.1673 + // Get the target thread, and then start the work! 1.1674 + nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection); 1.1675 + NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED); 1.1676 + nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); 1.1677 + NS_ENSURE_SUCCESS(rv, rv); 1.1678 + 1.1679 + return NS_OK; 1.1680 + } 1.1681 + 1.1682 + NS_IMETHOD Run() 1.1683 + { 1.1684 + MOZ_ASSERT(!NS_IsMainThread(), 1.1685 + "This should not be called on the main thread"); 1.1686 + 1.1687 + // Prevent the main thread from shutting down while this is running. 1.1688 + MutexAutoLock lockedScope(mHistory->GetShutdownMutex()); 1.1689 + if (mHistory->IsShuttingDown()) { 1.1690 + // If we were already shutting down, we cannot remove the visits. 1.1691 + return NS_OK; 1.1692 + } 1.1693 + 1.1694 + // Find all the visits relative to the current filters and whether their 1.1695 + // pages will be removed or not. 1.1696 + nsTHashtable<PlaceHashKey> places(VISITS_REMOVAL_INITIAL_HASH_SIZE); 1.1697 + nsresult rv = FindRemovableVisits(places); 1.1698 + NS_ENSURE_SUCCESS(rv, rv); 1.1699 + 1.1700 + if (places.Count() == 0) 1.1701 + return NS_OK; 1.1702 + 1.1703 + mozStorageTransaction transaction(mDBConn, false, 1.1704 + mozIStorageConnection::TRANSACTION_IMMEDIATE); 1.1705 + 1.1706 + rv = RemoveVisitsFromDatabase(); 1.1707 + NS_ENSURE_SUCCESS(rv, rv); 1.1708 + rv = RemovePagesFromDatabase(places); 1.1709 + NS_ENSURE_SUCCESS(rv, rv); 1.1710 + 1.1711 + rv = transaction.Commit(); 1.1712 + NS_ENSURE_SUCCESS(rv, rv); 1.1713 + 1.1714 + nsCOMPtr<nsIRunnable> event = new NotifyRemoveVisits(places); 1.1715 + rv = NS_DispatchToMainThread(event); 1.1716 + NS_ENSURE_SUCCESS(rv, rv); 1.1717 + 1.1718 + return NS_OK; 1.1719 + } 1.1720 + 1.1721 +private: 1.1722 + RemoveVisits(mozIStorageConnection* aConnection, 1.1723 + RemoveVisitsFilter& aFilter) 1.1724 + : mDBConn(aConnection) 1.1725 + , mHasTransitionType(false) 1.1726 + , mHistory(History::GetService()) 1.1727 + { 1.1728 + MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread"); 1.1729 + 1.1730 + // Build query conditions. 1.1731 + nsTArray<nsCString> conditions; 1.1732 + // TODO: add support for binding params when adding further stuff here. 1.1733 + if (aFilter.transitionType < UINT32_MAX) { 1.1734 + conditions.AppendElement(nsPrintfCString("visit_type = %d", aFilter.transitionType)); 1.1735 + mHasTransitionType = true; 1.1736 + } 1.1737 + if (conditions.Length() > 0) { 1.1738 + mWhereClause.AppendLiteral (" WHERE "); 1.1739 + for (uint32_t i = 0; i < conditions.Length(); ++i) { 1.1740 + if (i > 0) 1.1741 + mWhereClause.AppendLiteral(" AND "); 1.1742 + mWhereClause.Append(conditions[i]); 1.1743 + } 1.1744 + } 1.1745 + } 1.1746 + 1.1747 + nsresult 1.1748 + FindRemovableVisits(nsTHashtable<PlaceHashKey>& aPlaces) 1.1749 + { 1.1750 + MOZ_ASSERT(!NS_IsMainThread(), 1.1751 + "This should not be called on the main thread"); 1.1752 + 1.1753 + nsCString query("SELECT h.id, url, guid, visit_date, visit_type, " 1.1754 + "(SELECT count(*) FROM moz_historyvisits WHERE place_id = h.id) as full_visit_count, " 1.1755 + "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) as bookmarked " 1.1756 + "FROM moz_historyvisits " 1.1757 + "JOIN moz_places h ON place_id = h.id"); 1.1758 + query.Append(mWhereClause); 1.1759 + 1.1760 + nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query); 1.1761 + NS_ENSURE_STATE(stmt); 1.1762 + mozStorageStatementScoper scoper(stmt); 1.1763 + 1.1764 + bool hasResult; 1.1765 + nsresult rv; 1.1766 + while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { 1.1767 + VisitData visit; 1.1768 + rv = stmt->GetInt64(0, &visit.placeId); 1.1769 + NS_ENSURE_SUCCESS(rv, rv); 1.1770 + rv = stmt->GetUTF8String(1, visit.spec); 1.1771 + NS_ENSURE_SUCCESS(rv, rv); 1.1772 + rv = stmt->GetUTF8String(2, visit.guid); 1.1773 + NS_ENSURE_SUCCESS(rv, rv); 1.1774 + rv = stmt->GetInt64(3, &visit.visitTime); 1.1775 + NS_ENSURE_SUCCESS(rv, rv); 1.1776 + if (mHasTransitionType) { 1.1777 + int32_t transition; 1.1778 + rv = stmt->GetInt32(4, &transition); 1.1779 + NS_ENSURE_SUCCESS(rv, rv); 1.1780 + visit.transitionType = static_cast<uint32_t>(transition); 1.1781 + } 1.1782 + int32_t visitCount, bookmarked; 1.1783 + rv = stmt->GetInt32(5, &visitCount); 1.1784 + NS_ENSURE_SUCCESS(rv, rv); 1.1785 + rv = stmt->GetInt32(6, &bookmarked); 1.1786 + NS_ENSURE_SUCCESS(rv, rv); 1.1787 + 1.1788 + PlaceHashKey* entry = aPlaces.GetEntry(visit.spec); 1.1789 + if (!entry) { 1.1790 + entry = aPlaces.PutEntry(visit.spec); 1.1791 + } 1.1792 + entry->visitCount = visitCount; 1.1793 + entry->bookmarked = bookmarked; 1.1794 + entry->visits.AppendElement(visit); 1.1795 + } 1.1796 + NS_ENSURE_SUCCESS(rv, rv); 1.1797 + 1.1798 + return NS_OK; 1.1799 + } 1.1800 + 1.1801 + nsresult 1.1802 + RemoveVisitsFromDatabase() 1.1803 + { 1.1804 + MOZ_ASSERT(!NS_IsMainThread(), 1.1805 + "This should not be called on the main thread"); 1.1806 + 1.1807 + nsCString query("DELETE FROM moz_historyvisits"); 1.1808 + query.Append(mWhereClause); 1.1809 + 1.1810 + nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query); 1.1811 + NS_ENSURE_STATE(stmt); 1.1812 + mozStorageStatementScoper scoper(stmt); 1.1813 + nsresult rv = stmt->Execute(); 1.1814 + NS_ENSURE_SUCCESS(rv, rv); 1.1815 + 1.1816 + return NS_OK; 1.1817 + } 1.1818 + 1.1819 + nsresult 1.1820 + RemovePagesFromDatabase(nsTHashtable<PlaceHashKey>& aPlaces) 1.1821 + { 1.1822 + MOZ_ASSERT(!NS_IsMainThread(), 1.1823 + "This should not be called on the main thread"); 1.1824 + 1.1825 + nsCString placeIdsToRemove; 1.1826 + aPlaces.EnumerateEntries(ListToBeRemovedPlaceIds, &placeIdsToRemove); 1.1827 + 1.1828 +#ifdef DEBUG 1.1829 + { 1.1830 + // Ensure that we are not removing any problematic entry. 1.1831 + nsCString query("SELECT id FROM moz_places h WHERE id IN ("); 1.1832 + query.Append(placeIdsToRemove); 1.1833 + query.AppendLiteral(") AND (" 1.1834 + "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) OR " 1.1835 + "EXISTS(SELECT 1 FROM moz_historyvisits WHERE place_id = h.id) OR " 1.1836 + "SUBSTR(h.url, 1, 6) = 'place:' " 1.1837 + ")"); 1.1838 + nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query); 1.1839 + NS_ENSURE_STATE(stmt); 1.1840 + mozStorageStatementScoper scoper(stmt); 1.1841 + bool hasResult; 1.1842 + MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult, 1.1843 + "Trying to remove a non-oprhan place from the database"); 1.1844 + } 1.1845 +#endif 1.1846 + 1.1847 + nsCString query("DELETE FROM moz_places " 1.1848 + "WHERE id IN ("); 1.1849 + query.Append(placeIdsToRemove); 1.1850 + query.AppendLiteral(")"); 1.1851 + 1.1852 + nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query); 1.1853 + NS_ENSURE_STATE(stmt); 1.1854 + mozStorageStatementScoper scoper(stmt); 1.1855 + nsresult rv = stmt->Execute(); 1.1856 + NS_ENSURE_SUCCESS(rv, rv); 1.1857 + 1.1858 + return NS_OK; 1.1859 + } 1.1860 + 1.1861 + mozIStorageConnection* mDBConn; 1.1862 + bool mHasTransitionType; 1.1863 + nsCString mWhereClause; 1.1864 + 1.1865 + /** 1.1866 + * Strong reference to the History object because we do not want it to 1.1867 + * disappear out from under us. 1.1868 + */ 1.1869 + nsRefPtr<History> mHistory; 1.1870 +}; 1.1871 + 1.1872 +/** 1.1873 + * Stores an embed visit, and notifies observers. 1.1874 + * 1.1875 + * @param aPlace 1.1876 + * The VisitData of the visit to store as an embed visit. 1.1877 + * @param [optional] aCallback 1.1878 + * The mozIVisitInfoCallback to notify, if provided. 1.1879 + */ 1.1880 +void 1.1881 +StoreAndNotifyEmbedVisit(VisitData& aPlace, 1.1882 + mozIVisitInfoCallback* aCallback = nullptr) 1.1883 +{ 1.1884 + MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED, 1.1885 + "Must only pass TRANSITION_EMBED visits to this!"); 1.1886 + MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!"); 1.1887 + 1.1888 + nsCOMPtr<nsIURI> uri; 1.1889 + (void)NS_NewURI(getter_AddRefs(uri), aPlace.spec); 1.1890 + 1.1891 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.1892 + if (!navHistory || !uri) { 1.1893 + return; 1.1894 + } 1.1895 + 1.1896 + navHistory->registerEmbedVisit(uri, aPlace.visitTime); 1.1897 + 1.1898 + if (aCallback) { 1.1899 + // NotifyPlaceInfoCallback does not hold a strong reference to the callback, 1.1900 + // so we have to manage it by AddRefing now and then releasing it after the 1.1901 + // event has run. 1.1902 + NS_ADDREF(aCallback); 1.1903 + nsCOMPtr<nsIRunnable> event = 1.1904 + new NotifyPlaceInfoCallback(aCallback, aPlace, true, NS_OK); 1.1905 + (void)NS_DispatchToMainThread(event); 1.1906 + 1.1907 + // Also dispatch an event to release our reference to the callback after 1.1908 + // NotifyPlaceInfoCallback has run. 1.1909 + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); 1.1910 + (void)NS_ProxyRelease(mainThread, aCallback, true); 1.1911 + } 1.1912 + 1.1913 + VisitData noReferrer; 1.1914 + nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer); 1.1915 + (void)NS_DispatchToMainThread(event); 1.1916 +} 1.1917 + 1.1918 +} // anonymous namespace 1.1919 + 1.1920 +//////////////////////////////////////////////////////////////////////////////// 1.1921 +//// History 1.1922 + 1.1923 +History* History::gService = nullptr; 1.1924 + 1.1925 +History::History() 1.1926 + : mShuttingDown(false) 1.1927 + , mShutdownMutex("History::mShutdownMutex") 1.1928 + , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_SIZE) 1.1929 + , mRecentlyVisitedURIsNextIndex(0) 1.1930 +{ 1.1931 + NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!"); 1.1932 + gService = this; 1.1933 + 1.1934 + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 1.1935 + NS_WARN_IF_FALSE(os, "Observer service was not found!"); 1.1936 + if (os) { 1.1937 + (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false); 1.1938 + } 1.1939 +} 1.1940 + 1.1941 +History::~History() 1.1942 +{ 1.1943 + UnregisterWeakMemoryReporter(this); 1.1944 + 1.1945 + gService = nullptr; 1.1946 + 1.1947 + NS_ASSERTION(mObservers.Count() == 0, 1.1948 + "Not all Links were removed before we disappear!"); 1.1949 +} 1.1950 + 1.1951 +void 1.1952 +History::InitMemoryReporter() 1.1953 +{ 1.1954 + RegisterWeakMemoryReporter(this); 1.1955 +} 1.1956 + 1.1957 +NS_IMETHODIMP 1.1958 +History::NotifyVisited(nsIURI* aURI) 1.1959 +{ 1.1960 + NS_ENSURE_ARG(aURI); 1.1961 + 1.1962 + nsAutoScriptBlocker scriptBlocker; 1.1963 + 1.1964 + if (XRE_GetProcessType() == GeckoProcessType_Default) { 1.1965 + nsTArray<ContentParent*> cplist; 1.1966 + ContentParent::GetAll(cplist); 1.1967 + 1.1968 + if (!cplist.IsEmpty()) { 1.1969 + URIParams uri; 1.1970 + SerializeURI(aURI, uri); 1.1971 + for (uint32_t i = 0; i < cplist.Length(); ++i) { 1.1972 + unused << cplist[i]->SendNotifyVisited(uri); 1.1973 + } 1.1974 + } 1.1975 + } 1.1976 + 1.1977 + // If we have no observers for this URI, we have nothing to notify about. 1.1978 + KeyClass* key = mObservers.GetEntry(aURI); 1.1979 + if (!key) { 1.1980 + return NS_OK; 1.1981 + } 1.1982 + 1.1983 + // Update status of each Link node. 1.1984 + { 1.1985 + // RemoveEntry will destroy the array, this iterator should not survive it. 1.1986 + ObserverArray::ForwardIterator iter(key->array); 1.1987 + while (iter.HasMore()) { 1.1988 + Link* link = iter.GetNext(); 1.1989 + link->SetLinkState(eLinkState_Visited); 1.1990 + // Verify that the observers hash doesn't mutate while looping through 1.1991 + // the links associated with this URI. 1.1992 + NS_ABORT_IF_FALSE(key == mObservers.GetEntry(aURI), 1.1993 + "The URIs hash mutated!"); 1.1994 + } 1.1995 + } 1.1996 + 1.1997 + // All the registered nodes can now be removed for this URI. 1.1998 + mObservers.RemoveEntry(aURI); 1.1999 + return NS_OK; 1.2000 +} 1.2001 + 1.2002 +mozIStorageAsyncStatement* 1.2003 +History::GetIsVisitedStatement() 1.2004 +{ 1.2005 + if (mIsVisitedStatement) { 1.2006 + return mIsVisitedStatement; 1.2007 + } 1.2008 + 1.2009 + // If we don't yet have a database connection, go ahead and clone it now. 1.2010 + if (!mReadOnlyDBConn) { 1.2011 + mozIStorageConnection* dbConn = GetDBConn(); 1.2012 + NS_ENSURE_TRUE(dbConn, nullptr); 1.2013 + 1.2014 + (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn)); 1.2015 + NS_ENSURE_TRUE(mReadOnlyDBConn, nullptr); 1.2016 + } 1.2017 + 1.2018 + // Now we can create our cached statement. 1.2019 + nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( 1.2020 + "SELECT 1 " 1.2021 + "FROM moz_places h " 1.2022 + "WHERE url = ?1 " 1.2023 + "AND last_visit_date NOTNULL " 1.2024 + ), getter_AddRefs(mIsVisitedStatement)); 1.2025 + NS_ENSURE_SUCCESS(rv, nullptr); 1.2026 + return mIsVisitedStatement; 1.2027 +} 1.2028 + 1.2029 +nsresult 1.2030 +History::InsertPlace(const VisitData& aPlace) 1.2031 +{ 1.2032 + NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!"); 1.2033 + NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!"); 1.2034 + 1.2035 + nsCOMPtr<mozIStorageStatement> stmt = GetStatement( 1.2036 + "INSERT INTO moz_places " 1.2037 + "(url, title, rev_host, hidden, typed, frecency, guid) " 1.2038 + "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) " 1.2039 + ); 1.2040 + NS_ENSURE_STATE(stmt); 1.2041 + mozStorageStatementScoper scoper(stmt); 1.2042 + 1.2043 + nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), 1.2044 + aPlace.revHost); 1.2045 + NS_ENSURE_SUCCESS(rv, rv); 1.2046 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec); 1.2047 + NS_ENSURE_SUCCESS(rv, rv); 1.2048 + nsString title = aPlace.title; 1.2049 + // Empty strings should have no title, just like nsNavHistory::SetPageTitle. 1.2050 + if (title.IsEmpty()) { 1.2051 + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title")); 1.2052 + } 1.2053 + else { 1.2054 + title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX)); 1.2055 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title); 1.2056 + } 1.2057 + NS_ENSURE_SUCCESS(rv, rv); 1.2058 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed); 1.2059 + NS_ENSURE_SUCCESS(rv, rv); 1.2060 + // When inserting a page for a first visit that should not appear in 1.2061 + // autocomplete, for example an error page, use a zero frecency. 1.2062 + int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0; 1.2063 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency); 1.2064 + NS_ENSURE_SUCCESS(rv, rv); 1.2065 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden); 1.2066 + NS_ENSURE_SUCCESS(rv, rv); 1.2067 + nsAutoCString guid(aPlace.guid); 1.2068 + if (aPlace.guid.IsVoid()) { 1.2069 + rv = GenerateGUID(guid); 1.2070 + NS_ENSURE_SUCCESS(rv, rv); 1.2071 + } 1.2072 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid); 1.2073 + NS_ENSURE_SUCCESS(rv, rv); 1.2074 + rv = stmt->Execute(); 1.2075 + NS_ENSURE_SUCCESS(rv, rv); 1.2076 + 1.2077 + // Post an onFrecencyChanged observer notification. 1.2078 + const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService(); 1.2079 + NS_ENSURE_STATE(navHistory); 1.2080 + navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid, 1.2081 + aPlace.hidden, 1.2082 + aPlace.visitTime); 1.2083 + 1.2084 + return NS_OK; 1.2085 +} 1.2086 + 1.2087 +nsresult 1.2088 +History::UpdatePlace(const VisitData& aPlace) 1.2089 +{ 1.2090 + NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!"); 1.2091 + NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!"); 1.2092 + NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!"); 1.2093 + 1.2094 + nsCOMPtr<mozIStorageStatement> stmt = GetStatement( 1.2095 + "UPDATE moz_places " 1.2096 + "SET title = :title, " 1.2097 + "hidden = :hidden, " 1.2098 + "typed = :typed, " 1.2099 + "guid = :guid " 1.2100 + "WHERE id = :page_id " 1.2101 + ); 1.2102 + NS_ENSURE_STATE(stmt); 1.2103 + mozStorageStatementScoper scoper(stmt); 1.2104 + 1.2105 + nsresult rv; 1.2106 + // Empty strings should clear the title, just like nsNavHistory::SetPageTitle. 1.2107 + if (aPlace.title.IsEmpty()) { 1.2108 + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title")); 1.2109 + } 1.2110 + else { 1.2111 + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), 1.2112 + StringHead(aPlace.title, TITLE_LENGTH_MAX)); 1.2113 + } 1.2114 + NS_ENSURE_SUCCESS(rv, rv); 1.2115 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed); 1.2116 + NS_ENSURE_SUCCESS(rv, rv); 1.2117 + rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden); 1.2118 + NS_ENSURE_SUCCESS(rv, rv); 1.2119 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid); 1.2120 + NS_ENSURE_SUCCESS(rv, rv); 1.2121 + rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), 1.2122 + aPlace.placeId); 1.2123 + NS_ENSURE_SUCCESS(rv, rv); 1.2124 + rv = stmt->Execute(); 1.2125 + NS_ENSURE_SUCCESS(rv, rv); 1.2126 + 1.2127 + return NS_OK; 1.2128 +} 1.2129 + 1.2130 +nsresult 1.2131 +History::FetchPageInfo(VisitData& _place, bool* _exists) 1.2132 +{ 1.2133 + NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!"); 1.2134 + NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!"); 1.2135 + 1.2136 + nsresult rv; 1.2137 + 1.2138 + // URI takes precedence. 1.2139 + nsCOMPtr<mozIStorageStatement> stmt; 1.2140 + bool selectByURI = !_place.spec.IsEmpty(); 1.2141 + if (selectByURI) { 1.2142 + stmt = GetStatement( 1.2143 + "SELECT guid, id, title, hidden, typed, frecency " 1.2144 + "FROM moz_places " 1.2145 + "WHERE url = :page_url " 1.2146 + ); 1.2147 + NS_ENSURE_STATE(stmt); 1.2148 + 1.2149 + rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec); 1.2150 + NS_ENSURE_SUCCESS(rv, rv); 1.2151 + } 1.2152 + else { 1.2153 + stmt = GetStatement( 1.2154 + "SELECT url, id, title, hidden, typed, frecency " 1.2155 + "FROM moz_places " 1.2156 + "WHERE guid = :guid " 1.2157 + ); 1.2158 + NS_ENSURE_STATE(stmt); 1.2159 + 1.2160 + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid); 1.2161 + NS_ENSURE_SUCCESS(rv, rv); 1.2162 + } 1.2163 + 1.2164 + mozStorageStatementScoper scoper(stmt); 1.2165 + 1.2166 + rv = stmt->ExecuteStep(_exists); 1.2167 + NS_ENSURE_SUCCESS(rv, rv); 1.2168 + 1.2169 + if (!*_exists) { 1.2170 + return NS_OK; 1.2171 + } 1.2172 + 1.2173 + if (selectByURI) { 1.2174 + if (_place.guid.IsEmpty()) { 1.2175 + rv = stmt->GetUTF8String(0, _place.guid); 1.2176 + NS_ENSURE_SUCCESS(rv, rv); 1.2177 + } 1.2178 + } 1.2179 + else { 1.2180 + nsAutoCString spec; 1.2181 + rv = stmt->GetUTF8String(0, spec); 1.2182 + NS_ENSURE_SUCCESS(rv, rv); 1.2183 + _place.spec = spec; 1.2184 + } 1.2185 + 1.2186 + rv = stmt->GetInt64(1, &_place.placeId); 1.2187 + NS_ENSURE_SUCCESS(rv, rv); 1.2188 + 1.2189 + nsAutoString title; 1.2190 + rv = stmt->GetString(2, title); 1.2191 + NS_ENSURE_SUCCESS(rv, rv); 1.2192 + 1.2193 + // If the title we were given was void, that means we did not bother to set 1.2194 + // it to anything. As a result, ignore the fact that we may have changed the 1.2195 + // title (because we don't want to, that would be empty), and set the title 1.2196 + // to what is currently stored in the datbase. 1.2197 + if (_place.title.IsVoid()) { 1.2198 + _place.title = title; 1.2199 + } 1.2200 + // Otherwise, just indicate if the title has changed. 1.2201 + else { 1.2202 + _place.titleChanged = !(_place.title.Equals(title) || 1.2203 + (_place.title.IsEmpty() && title.IsVoid())); 1.2204 + } 1.2205 + 1.2206 + if (_place.hidden) { 1.2207 + // If this transition was hidden, it is possible that others were not. 1.2208 + // Any one visible transition makes this location visible. If database 1.2209 + // has location as visible, reflect that in our data structure. 1.2210 + int32_t hidden; 1.2211 + rv = stmt->GetInt32(3, &hidden); 1.2212 + NS_ENSURE_SUCCESS(rv, rv); 1.2213 + _place.hidden = !!hidden; 1.2214 + } 1.2215 + 1.2216 + if (!_place.typed) { 1.2217 + // If this transition wasn't typed, others might have been. If database 1.2218 + // has location as typed, reflect that in our data structure. 1.2219 + int32_t typed; 1.2220 + rv = stmt->GetInt32(4, &typed); 1.2221 + NS_ENSURE_SUCCESS(rv, rv); 1.2222 + _place.typed = !!typed; 1.2223 + } 1.2224 + 1.2225 + rv = stmt->GetInt32(5, &_place.frecency); 1.2226 + NS_ENSURE_SUCCESS(rv, rv); 1.2227 + return NS_OK; 1.2228 +} 1.2229 + 1.2230 +/* static */ size_t 1.2231 +History::SizeOfEntryExcludingThis(KeyClass* aEntry, mozilla::MallocSizeOf aMallocSizeOf, void *) 1.2232 +{ 1.2233 + return aEntry->array.SizeOfExcludingThis(aMallocSizeOf); 1.2234 +} 1.2235 + 1.2236 +MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf) 1.2237 + 1.2238 +NS_IMETHODIMP 1.2239 +History::CollectReports(nsIHandleReportCallback* aHandleReport, 1.2240 + nsISupports* aData) 1.2241 +{ 1.2242 + return MOZ_COLLECT_REPORT( 1.2243 + "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES, 1.2244 + SizeOfIncludingThis(HistoryMallocSizeOf), 1.2245 + "Memory used by the hashtable that records changes to the visited state " 1.2246 + "of links."); 1.2247 +} 1.2248 + 1.2249 +size_t 1.2250 +History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis) 1.2251 +{ 1.2252 + return aMallocSizeOfThis(this) + 1.2253 + mObservers.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOfThis); 1.2254 +} 1.2255 + 1.2256 +/* static */ 1.2257 +History* 1.2258 +History::GetService() 1.2259 +{ 1.2260 + if (gService) { 1.2261 + return gService; 1.2262 + } 1.2263 + 1.2264 + nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID)); 1.2265 + NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!"); 1.2266 + NS_ASSERTION(gService, "Our constructor was not run?!"); 1.2267 + 1.2268 + return gService; 1.2269 +} 1.2270 + 1.2271 +/* static */ 1.2272 +History* 1.2273 +History::GetSingleton() 1.2274 +{ 1.2275 + if (!gService) { 1.2276 + gService = new History(); 1.2277 + NS_ENSURE_TRUE(gService, nullptr); 1.2278 + gService->InitMemoryReporter(); 1.2279 + } 1.2280 + 1.2281 + NS_ADDREF(gService); 1.2282 + return gService; 1.2283 +} 1.2284 + 1.2285 +mozIStorageConnection* 1.2286 +History::GetDBConn() 1.2287 +{ 1.2288 + if (!mDB) { 1.2289 + mDB = Database::GetDatabase(); 1.2290 + NS_ENSURE_TRUE(mDB, nullptr); 1.2291 + } 1.2292 + return mDB->MainConn(); 1.2293 +} 1.2294 + 1.2295 +void 1.2296 +History::Shutdown() 1.2297 +{ 1.2298 + MOZ_ASSERT(NS_IsMainThread()); 1.2299 + 1.2300 + // Prevent other threads from scheduling uses of the DB while we mark 1.2301 + // ourselves as shutting down. 1.2302 + MutexAutoLock lockedScope(mShutdownMutex); 1.2303 + MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!"); 1.2304 + 1.2305 + mShuttingDown = true; 1.2306 + 1.2307 + if (mReadOnlyDBConn) { 1.2308 + if (mIsVisitedStatement) { 1.2309 + (void)mIsVisitedStatement->Finalize(); 1.2310 + } 1.2311 + (void)mReadOnlyDBConn->AsyncClose(nullptr); 1.2312 + } 1.2313 +} 1.2314 + 1.2315 +void 1.2316 +History::AppendToRecentlyVisitedURIs(nsIURI* aURI) { 1.2317 + if (mRecentlyVisitedURIs.Length() < RECENTLY_VISITED_URI_SIZE) { 1.2318 + // Append a new element while the array is not full. 1.2319 + mRecentlyVisitedURIs.AppendElement(aURI); 1.2320 + } else { 1.2321 + // Otherwise, replace the oldest member. 1.2322 + mRecentlyVisitedURIsNextIndex %= RECENTLY_VISITED_URI_SIZE; 1.2323 + mRecentlyVisitedURIs.ElementAt(mRecentlyVisitedURIsNextIndex) = aURI; 1.2324 + mRecentlyVisitedURIsNextIndex++; 1.2325 + } 1.2326 +} 1.2327 + 1.2328 +inline bool 1.2329 +History::IsRecentlyVisitedURI(nsIURI* aURI) { 1.2330 + bool equals = false; 1.2331 + RecentlyVisitedArray::index_type i; 1.2332 + for (i = 0; i < mRecentlyVisitedURIs.Length() && !equals; ++i) { 1.2333 + aURI->Equals(mRecentlyVisitedURIs.ElementAt(i), &equals); 1.2334 + } 1.2335 + return equals; 1.2336 +} 1.2337 + 1.2338 +//////////////////////////////////////////////////////////////////////////////// 1.2339 +//// IHistory 1.2340 + 1.2341 +NS_IMETHODIMP 1.2342 +History::VisitURI(nsIURI* aURI, 1.2343 + nsIURI* aLastVisitedURI, 1.2344 + uint32_t aFlags) 1.2345 +{ 1.2346 + NS_PRECONDITION(aURI, "URI should not be NULL."); 1.2347 + if (mShuttingDown) { 1.2348 + return NS_OK; 1.2349 + } 1.2350 + 1.2351 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.2352 + URIParams uri; 1.2353 + SerializeURI(aURI, uri); 1.2354 + 1.2355 + OptionalURIParams lastVisitedURI; 1.2356 + SerializeURI(aLastVisitedURI, lastVisitedURI); 1.2357 + 1.2358 + mozilla::dom::ContentChild* cpc = 1.2359 + mozilla::dom::ContentChild::GetSingleton(); 1.2360 + NS_ASSERTION(cpc, "Content Protocol is NULL!"); 1.2361 + (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags); 1.2362 + return NS_OK; 1.2363 + } 1.2364 + 1.2365 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.2366 + NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY); 1.2367 + 1.2368 + // Silently return if URI is something we shouldn't add to DB. 1.2369 + bool canAdd; 1.2370 + nsresult rv = navHistory->CanAddURI(aURI, &canAdd); 1.2371 + NS_ENSURE_SUCCESS(rv, rv); 1.2372 + if (!canAdd) { 1.2373 + return NS_OK; 1.2374 + } 1.2375 + 1.2376 + if (aLastVisitedURI) { 1.2377 + bool same; 1.2378 + rv = aURI->Equals(aLastVisitedURI, &same); 1.2379 + NS_ENSURE_SUCCESS(rv, rv); 1.2380 + if (same && IsRecentlyVisitedURI(aURI)) { 1.2381 + // Do not save refresh visits if we have visited this URI recently. 1.2382 + return NS_OK; 1.2383 + } 1.2384 + } 1.2385 + 1.2386 + nsTArray<VisitData> placeArray(1); 1.2387 + NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)), 1.2388 + NS_ERROR_OUT_OF_MEMORY); 1.2389 + VisitData& place = placeArray.ElementAt(0); 1.2390 + NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG); 1.2391 + 1.2392 + place.visitTime = PR_Now(); 1.2393 + 1.2394 + // Assigns a type to the edge in the visit linked list. Each type will be 1.2395 + // considered differently when weighting the frecency of a location. 1.2396 + uint32_t recentFlags = navHistory->GetRecentFlags(aURI); 1.2397 + bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED; 1.2398 + 1.2399 + // Embed visits should never be added to the database, and the same is valid 1.2400 + // for redirects across frames. 1.2401 + // For the above reasoning non-toplevel transitions are handled at first. 1.2402 + // if the visit is toplevel or a non-toplevel followed link, then it can be 1.2403 + // handled as usual and stored on disk. 1.2404 + 1.2405 + uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK; 1.2406 + 1.2407 + if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) { 1.2408 + // A frame redirected to a new site without user interaction. 1.2409 + transitionType = nsINavHistoryService::TRANSITION_EMBED; 1.2410 + } 1.2411 + else if (aFlags & IHistory::REDIRECT_TEMPORARY) { 1.2412 + transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY; 1.2413 + } 1.2414 + else if (aFlags & IHistory::REDIRECT_PERMANENT) { 1.2415 + transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT; 1.2416 + } 1.2417 + else if ((recentFlags & nsNavHistory::RECENT_TYPED) && 1.2418 + !(aFlags & IHistory::UNRECOVERABLE_ERROR)) { 1.2419 + // Don't mark error pages as typed, even if they were actually typed by 1.2420 + // the user. This is useful to limit their score in autocomplete. 1.2421 + transitionType = nsINavHistoryService::TRANSITION_TYPED; 1.2422 + } 1.2423 + else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) { 1.2424 + transitionType = nsINavHistoryService::TRANSITION_BOOKMARK; 1.2425 + } 1.2426 + else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) { 1.2427 + // User activated a link in a frame. 1.2428 + transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK; 1.2429 + } 1.2430 + 1.2431 + place.SetTransitionType(transitionType); 1.2432 + place.hidden = GetHiddenState(aFlags & IHistory::REDIRECT_SOURCE, 1.2433 + transitionType); 1.2434 + 1.2435 + // Error pages should never be autocompleted. 1.2436 + if (aFlags & IHistory::UNRECOVERABLE_ERROR) { 1.2437 + place.shouldUpdateFrecency = false; 1.2438 + } 1.2439 + 1.2440 + // EMBED visits are session-persistent and should not go through the database. 1.2441 + // They exist only to keep track of isVisited status during the session. 1.2442 + if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) { 1.2443 + StoreAndNotifyEmbedVisit(place); 1.2444 + } 1.2445 + else { 1.2446 + mozIStorageConnection* dbConn = GetDBConn(); 1.2447 + NS_ENSURE_STATE(dbConn); 1.2448 + 1.2449 + rv = InsertVisitedURIs::Start(dbConn, placeArray); 1.2450 + NS_ENSURE_SUCCESS(rv, rv); 1.2451 + } 1.2452 + 1.2453 + // Finally, notify that we've been visited. 1.2454 + nsCOMPtr<nsIObserverService> obsService = 1.2455 + mozilla::services::GetObserverService(); 1.2456 + if (obsService) { 1.2457 + obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nullptr); 1.2458 + } 1.2459 + 1.2460 + return NS_OK; 1.2461 +} 1.2462 + 1.2463 +NS_IMETHODIMP 1.2464 +History::RegisterVisitedCallback(nsIURI* aURI, 1.2465 + Link* aLink) 1.2466 +{ 1.2467 + NS_ASSERTION(aURI, "Must pass a non-null URI!"); 1.2468 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.2469 + NS_PRECONDITION(aLink, "Must pass a non-null Link!"); 1.2470 + } 1.2471 + 1.2472 + // Obtain our array of observers for this URI. 1.2473 +#ifdef DEBUG 1.2474 + bool keyAlreadyExists = !!mObservers.GetEntry(aURI); 1.2475 +#endif 1.2476 + KeyClass* key = mObservers.PutEntry(aURI); 1.2477 + NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY); 1.2478 + ObserverArray& observers = key->array; 1.2479 + 1.2480 + if (observers.IsEmpty()) { 1.2481 + NS_ASSERTION(!keyAlreadyExists, 1.2482 + "An empty key was kept around in our hashtable!"); 1.2483 + 1.2484 + // We are the first Link node to ask about this URI, or there are no pending 1.2485 + // Links wanting to know about this URI. Therefore, we should query the 1.2486 + // database now. 1.2487 + nsresult rv = VisitedQuery::Start(aURI); 1.2488 + 1.2489 + // In IPC builds, we are passed a nullptr Link from 1.2490 + // ContentParent::RecvStartVisitedQuery. Since we won't be adding a 1.2491 + // nullptr entry to our list of observers, and the code after this point 1.2492 + // assumes that aLink is non-nullptr, we will need to return now. 1.2493 + if (NS_FAILED(rv) || !aLink) { 1.2494 + // Remove our array from the hashtable so we don't keep it around. 1.2495 + mObservers.RemoveEntry(aURI); 1.2496 + return rv; 1.2497 + } 1.2498 + } 1.2499 + // In IPC builds, we are passed a nullptr Link from 1.2500 + // ContentParent::RecvStartVisitedQuery. All of our code after this point 1.2501 + // assumes aLink is non-nullptr, so we have to return now. 1.2502 + else if (!aLink) { 1.2503 + NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Default, 1.2504 + "We should only ever get a null Link in the default process!"); 1.2505 + return NS_OK; 1.2506 + } 1.2507 + 1.2508 + // Sanity check that Links are not registered more than once for a given URI. 1.2509 + // This will not catch a case where it is registered for two different URIs. 1.2510 + NS_ASSERTION(!observers.Contains(aLink), 1.2511 + "Already tracking this Link object!"); 1.2512 + 1.2513 + // Start tracking our Link. 1.2514 + if (!observers.AppendElement(aLink)) { 1.2515 + // Curses - unregister and return failure. 1.2516 + (void)UnregisterVisitedCallback(aURI, aLink); 1.2517 + return NS_ERROR_OUT_OF_MEMORY; 1.2518 + } 1.2519 + 1.2520 + return NS_OK; 1.2521 +} 1.2522 + 1.2523 +NS_IMETHODIMP 1.2524 +History::UnregisterVisitedCallback(nsIURI* aURI, 1.2525 + Link* aLink) 1.2526 +{ 1.2527 + NS_ASSERTION(aURI, "Must pass a non-null URI!"); 1.2528 + NS_ASSERTION(aLink, "Must pass a non-null Link object!"); 1.2529 + 1.2530 + // Get the array, and remove the item from it. 1.2531 + KeyClass* key = mObservers.GetEntry(aURI); 1.2532 + if (!key) { 1.2533 + NS_ERROR("Trying to unregister for a URI that wasn't registered!"); 1.2534 + return NS_ERROR_UNEXPECTED; 1.2535 + } 1.2536 + ObserverArray& observers = key->array; 1.2537 + if (!observers.RemoveElement(aLink)) { 1.2538 + NS_ERROR("Trying to unregister a node that wasn't registered!"); 1.2539 + return NS_ERROR_UNEXPECTED; 1.2540 + } 1.2541 + 1.2542 + // If the array is now empty, we should remove it from the hashtable. 1.2543 + if (observers.IsEmpty()) { 1.2544 + mObservers.RemoveEntry(aURI); 1.2545 + } 1.2546 + 1.2547 + return NS_OK; 1.2548 +} 1.2549 + 1.2550 +NS_IMETHODIMP 1.2551 +History::SetURITitle(nsIURI* aURI, const nsAString& aTitle) 1.2552 +{ 1.2553 + NS_PRECONDITION(aURI, "Must pass a non-null URI!"); 1.2554 + if (mShuttingDown) { 1.2555 + return NS_OK; 1.2556 + } 1.2557 + 1.2558 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.2559 + URIParams uri; 1.2560 + SerializeURI(aURI, uri); 1.2561 + 1.2562 + mozilla::dom::ContentChild * cpc = 1.2563 + mozilla::dom::ContentChild::GetSingleton(); 1.2564 + NS_ASSERTION(cpc, "Content Protocol is NULL!"); 1.2565 + (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle)); 1.2566 + return NS_OK; 1.2567 + } 1.2568 + 1.2569 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.2570 + 1.2571 + // At first, it seems like nav history should always be available here, no 1.2572 + // matter what. 1.2573 + // 1.2574 + // nsNavHistory fails to register as a service if there is no profile in 1.2575 + // place (for instance, if user is choosing a profile). 1.2576 + // 1.2577 + // Maybe the correct thing to do is to not register this service if no 1.2578 + // profile has been selected? 1.2579 + // 1.2580 + NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE); 1.2581 + 1.2582 + bool canAdd; 1.2583 + nsresult rv = navHistory->CanAddURI(aURI, &canAdd); 1.2584 + NS_ENSURE_SUCCESS(rv, rv); 1.2585 + if (!canAdd) { 1.2586 + return NS_OK; 1.2587 + } 1.2588 + 1.2589 + // Embed visits don't have a database entry, thus don't set a title on them. 1.2590 + if (navHistory->hasEmbedVisit(aURI)) { 1.2591 + return NS_OK; 1.2592 + } 1.2593 + 1.2594 + mozIStorageConnection* dbConn = GetDBConn(); 1.2595 + NS_ENSURE_STATE(dbConn); 1.2596 + 1.2597 + rv = SetPageTitle::Start(dbConn, aURI, aTitle); 1.2598 + NS_ENSURE_SUCCESS(rv, rv); 1.2599 + 1.2600 + return NS_OK; 1.2601 +} 1.2602 + 1.2603 +//////////////////////////////////////////////////////////////////////////////// 1.2604 +//// nsIDownloadHistory 1.2605 + 1.2606 +NS_IMETHODIMP 1.2607 +History::AddDownload(nsIURI* aSource, nsIURI* aReferrer, 1.2608 + PRTime aStartTime, nsIURI* aDestination) 1.2609 +{ 1.2610 + MOZ_ASSERT(NS_IsMainThread()); 1.2611 + NS_ENSURE_ARG(aSource); 1.2612 + 1.2613 + if (mShuttingDown) { 1.2614 + return NS_OK; 1.2615 + } 1.2616 + 1.2617 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.2618 + NS_ERROR("Cannot add downloads to history from content process!"); 1.2619 + return NS_ERROR_NOT_AVAILABLE; 1.2620 + } 1.2621 + 1.2622 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.2623 + NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY); 1.2624 + 1.2625 + // Silently return if URI is something we shouldn't add to DB. 1.2626 + bool canAdd; 1.2627 + nsresult rv = navHistory->CanAddURI(aSource, &canAdd); 1.2628 + NS_ENSURE_SUCCESS(rv, rv); 1.2629 + if (!canAdd) { 1.2630 + return NS_OK; 1.2631 + } 1.2632 + 1.2633 + nsTArray<VisitData> placeArray(1); 1.2634 + NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)), 1.2635 + NS_ERROR_OUT_OF_MEMORY); 1.2636 + VisitData& place = placeArray.ElementAt(0); 1.2637 + NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG); 1.2638 + 1.2639 + place.visitTime = aStartTime; 1.2640 + place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD); 1.2641 + place.hidden = false; 1.2642 + 1.2643 + mozIStorageConnection* dbConn = GetDBConn(); 1.2644 + NS_ENSURE_STATE(dbConn); 1.2645 + 1.2646 + nsCOMPtr<mozIVisitInfoCallback> callback = aDestination 1.2647 + ? new SetDownloadAnnotations(aDestination) 1.2648 + : nullptr; 1.2649 + 1.2650 + rv = InsertVisitedURIs::Start(dbConn, placeArray, callback); 1.2651 + NS_ENSURE_SUCCESS(rv, rv); 1.2652 + 1.2653 + // Finally, notify that we've been visited. 1.2654 + nsCOMPtr<nsIObserverService> obsService = 1.2655 + mozilla::services::GetObserverService(); 1.2656 + if (obsService) { 1.2657 + obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr); 1.2658 + } 1.2659 + 1.2660 + return NS_OK; 1.2661 +} 1.2662 + 1.2663 +NS_IMETHODIMP 1.2664 +History::RemoveAllDownloads() 1.2665 +{ 1.2666 + MOZ_ASSERT(NS_IsMainThread()); 1.2667 + 1.2668 + if (mShuttingDown) { 1.2669 + return NS_OK; 1.2670 + } 1.2671 + 1.2672 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.2673 + NS_ERROR("Cannot remove downloads to history from content process!"); 1.2674 + return NS_ERROR_NOT_AVAILABLE; 1.2675 + } 1.2676 + 1.2677 + // Ensure navHistory is initialized. 1.2678 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.2679 + NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY); 1.2680 + mozIStorageConnection* dbConn = GetDBConn(); 1.2681 + NS_ENSURE_STATE(dbConn); 1.2682 + 1.2683 + RemoveVisitsFilter filter; 1.2684 + filter.transitionType = nsINavHistoryService::TRANSITION_DOWNLOAD; 1.2685 + 1.2686 + nsresult rv = RemoveVisits::Start(dbConn, filter); 1.2687 + NS_ENSURE_SUCCESS(rv, rv); 1.2688 + 1.2689 + return NS_OK; 1.2690 +} 1.2691 + 1.2692 +//////////////////////////////////////////////////////////////////////////////// 1.2693 +//// mozIAsyncHistory 1.2694 + 1.2695 +NS_IMETHODIMP 1.2696 +History::GetPlacesInfo(JS::Handle<JS::Value> aPlaceIdentifiers, 1.2697 + mozIVisitInfoCallback* aCallback, 1.2698 + JSContext* aCtx) { 1.2699 + nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); 1.2700 + NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!"); 1.2701 + 1.2702 + uint32_t placesIndentifiersLength; 1.2703 + JS::Rooted<JSObject*> placesIndentifiers(aCtx); 1.2704 + nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx, 1.2705 + &placesIndentifiers, 1.2706 + &placesIndentifiersLength); 1.2707 + NS_ENSURE_SUCCESS(rv, rv); 1.2708 + 1.2709 + nsTArray<VisitData> placesInfo; 1.2710 + placesInfo.SetCapacity(placesIndentifiersLength); 1.2711 + for (uint32_t i = 0; i < placesIndentifiersLength; i++) { 1.2712 + JS::Rooted<JS::Value> placeIdentifier(aCtx); 1.2713 + bool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier); 1.2714 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.2715 + 1.2716 + // GUID 1.2717 + nsAutoString fatGUID; 1.2718 + GetJSValueAsString(aCtx, placeIdentifier, fatGUID); 1.2719 + if (!fatGUID.IsVoid()) { 1.2720 + NS_ConvertUTF16toUTF8 guid(fatGUID); 1.2721 + if (!IsValidGUID(guid)) 1.2722 + return NS_ERROR_INVALID_ARG; 1.2723 + 1.2724 + VisitData& placeInfo = *placesInfo.AppendElement(VisitData()); 1.2725 + placeInfo.guid = guid; 1.2726 + } 1.2727 + else { 1.2728 + nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier); 1.2729 + if (!uri) 1.2730 + return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri. 1.2731 + placesInfo.AppendElement(VisitData(uri)); 1.2732 + } 1.2733 + } 1.2734 + 1.2735 + mozIStorageConnection* dbConn = GetDBConn(); 1.2736 + NS_ENSURE_STATE(dbConn); 1.2737 + 1.2738 + for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) { 1.2739 + nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback); 1.2740 + NS_ENSURE_SUCCESS(rv, rv); 1.2741 + } 1.2742 + 1.2743 + // Be sure to notify that all of our operations are complete. This 1.2744 + // is dispatched to the background thread first and redirected to the 1.2745 + // main thread from there to make sure that all database notifications 1.2746 + // and all embed or canAddURI notifications have finished. 1.2747 + if (aCallback) { 1.2748 + // NotifyCompletion does not hold a strong reference to the callback, 1.2749 + // so we have to manage it by AddRefing now. NotifyCompletion will 1.2750 + // release it for us once it has dispatched the callback to the main 1.2751 + // thread. 1.2752 + NS_ADDREF(aCallback); 1.2753 + 1.2754 + nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn); 1.2755 + NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED); 1.2756 + nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback); 1.2757 + return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2758 + } 1.2759 + 1.2760 + return NS_OK; 1.2761 +} 1.2762 + 1.2763 +NS_IMETHODIMP 1.2764 +History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos, 1.2765 + mozIVisitInfoCallback* aCallback, 1.2766 + JSContext* aCtx) 1.2767 +{ 1.2768 + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED); 1.2769 + NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG); 1.2770 + 1.2771 + uint32_t infosLength; 1.2772 + JS::Rooted<JSObject*> infos(aCtx); 1.2773 + nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength); 1.2774 + NS_ENSURE_SUCCESS(rv, rv); 1.2775 + 1.2776 + nsTArray<VisitData> visitData; 1.2777 + for (uint32_t i = 0; i < infosLength; i++) { 1.2778 + JS::Rooted<JSObject*> info(aCtx); 1.2779 + nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info); 1.2780 + NS_ENSURE_SUCCESS(rv, rv); 1.2781 + 1.2782 + nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri"); 1.2783 + nsCString guid; 1.2784 + { 1.2785 + nsString fatGUID; 1.2786 + GetStringFromJSObject(aCtx, info, "guid", fatGUID); 1.2787 + if (fatGUID.IsVoid()) { 1.2788 + guid.SetIsVoid(true); 1.2789 + } 1.2790 + else { 1.2791 + guid = NS_ConvertUTF16toUTF8(fatGUID); 1.2792 + } 1.2793 + } 1.2794 + 1.2795 + // Make sure that any uri we are given can be added to history, and if not, 1.2796 + // skip it (CanAddURI will notify our callback for us). 1.2797 + if (uri && !CanAddURI(uri, guid, aCallback)) { 1.2798 + continue; 1.2799 + } 1.2800 + 1.2801 + // We must have at least one of uri or guid. 1.2802 + NS_ENSURE_ARG(uri || !guid.IsVoid()); 1.2803 + 1.2804 + // If we were given a guid, make sure it is valid. 1.2805 + bool isValidGUID = IsValidGUID(guid); 1.2806 + NS_ENSURE_ARG(guid.IsVoid() || isValidGUID); 1.2807 + 1.2808 + nsString title; 1.2809 + GetStringFromJSObject(aCtx, info, "title", title); 1.2810 + 1.2811 + JS::Rooted<JSObject*> visits(aCtx, nullptr); 1.2812 + { 1.2813 + JS::Rooted<JS::Value> visitsVal(aCtx); 1.2814 + bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal); 1.2815 + NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED); 1.2816 + if (!JSVAL_IS_PRIMITIVE(visitsVal)) { 1.2817 + visits = JSVAL_TO_OBJECT(visitsVal); 1.2818 + NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits)); 1.2819 + } 1.2820 + } 1.2821 + NS_ENSURE_ARG(visits); 1.2822 + 1.2823 + uint32_t visitsLength = 0; 1.2824 + if (visits) { 1.2825 + (void)JS_GetArrayLength(aCtx, visits, &visitsLength); 1.2826 + } 1.2827 + NS_ENSURE_ARG(visitsLength > 0); 1.2828 + 1.2829 + // Check each visit, and build our array of VisitData objects. 1.2830 + visitData.SetCapacity(visitData.Length() + visitsLength); 1.2831 + for (uint32_t j = 0; j < visitsLength; j++) { 1.2832 + JS::Rooted<JSObject*> visit(aCtx); 1.2833 + rv = GetJSObjectFromArray(aCtx, visits, j, &visit); 1.2834 + NS_ENSURE_SUCCESS(rv, rv); 1.2835 + 1.2836 + VisitData& data = *visitData.AppendElement(VisitData(uri)); 1.2837 + data.title = title; 1.2838 + data.guid = guid; 1.2839 + 1.2840 + // We must have a date and a transaction type! 1.2841 + rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime); 1.2842 + NS_ENSURE_SUCCESS(rv, rv); 1.2843 + uint32_t transitionType = 0; 1.2844 + rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType); 1.2845 + NS_ENSURE_SUCCESS(rv, rv); 1.2846 + NS_ENSURE_ARG_RANGE(transitionType, 1.2847 + nsINavHistoryService::TRANSITION_LINK, 1.2848 + nsINavHistoryService::TRANSITION_FRAMED_LINK); 1.2849 + data.SetTransitionType(transitionType); 1.2850 + data.hidden = GetHiddenState(false, transitionType); 1.2851 + 1.2852 + // If the visit is an embed visit, we do not actually add it to the 1.2853 + // database. 1.2854 + if (transitionType == nsINavHistoryService::TRANSITION_EMBED) { 1.2855 + StoreAndNotifyEmbedVisit(data, aCallback); 1.2856 + visitData.RemoveElementAt(visitData.Length() - 1); 1.2857 + continue; 1.2858 + } 1.2859 + 1.2860 + // The referrer is optional. 1.2861 + nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit, 1.2862 + "referrerURI"); 1.2863 + if (referrer) { 1.2864 + (void)referrer->GetSpec(data.referrerSpec); 1.2865 + } 1.2866 + } 1.2867 + } 1.2868 + 1.2869 + mozIStorageConnection* dbConn = GetDBConn(); 1.2870 + NS_ENSURE_STATE(dbConn); 1.2871 + 1.2872 + // It is possible that all of the visits we were passed were dissallowed by 1.2873 + // CanAddURI, which isn't an error. If we have no visits to add, however, 1.2874 + // we should not call InsertVisitedURIs::Start. 1.2875 + if (visitData.Length()) { 1.2876 + nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback); 1.2877 + NS_ENSURE_SUCCESS(rv, rv); 1.2878 + } 1.2879 + 1.2880 + // Be sure to notify that all of our operations are complete. This 1.2881 + // is dispatched to the background thread first and redirected to the 1.2882 + // main thread from there to make sure that all database notifications 1.2883 + // and all embed or canAddURI notifications have finished. 1.2884 + if (aCallback) { 1.2885 + // NotifyCompletion does not hold a strong reference to the callback, 1.2886 + // so we have to manage it by AddRefing now. NotifyCompletion will 1.2887 + // release it for us once it has dispatched the callback to the main 1.2888 + // thread. 1.2889 + NS_ADDREF(aCallback); 1.2890 + 1.2891 + nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn); 1.2892 + NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED); 1.2893 + nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback); 1.2894 + return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.2895 + } 1.2896 + 1.2897 + return NS_OK; 1.2898 +} 1.2899 + 1.2900 +NS_IMETHODIMP 1.2901 +History::IsURIVisited(nsIURI* aURI, 1.2902 + mozIVisitedStatusCallback* aCallback) 1.2903 +{ 1.2904 + NS_ENSURE_STATE(NS_IsMainThread()); 1.2905 + NS_ENSURE_ARG(aURI); 1.2906 + NS_ENSURE_ARG(aCallback); 1.2907 + 1.2908 + nsresult rv = VisitedQuery::Start(aURI, aCallback); 1.2909 + NS_ENSURE_SUCCESS(rv, rv); 1.2910 + 1.2911 + return NS_OK; 1.2912 +} 1.2913 + 1.2914 +//////////////////////////////////////////////////////////////////////////////// 1.2915 +//// nsIObserver 1.2916 + 1.2917 +NS_IMETHODIMP 1.2918 +History::Observe(nsISupports* aSubject, const char* aTopic, 1.2919 + const char16_t* aData) 1.2920 +{ 1.2921 + if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) { 1.2922 + Shutdown(); 1.2923 + 1.2924 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.2925 + if (os) { 1.2926 + (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN); 1.2927 + } 1.2928 + } 1.2929 + 1.2930 + return NS_OK; 1.2931 +} 1.2932 + 1.2933 +//////////////////////////////////////////////////////////////////////////////// 1.2934 +//// nsISupports 1.2935 + 1.2936 +NS_IMPL_ISUPPORTS( 1.2937 + History 1.2938 +, IHistory 1.2939 +, nsIDownloadHistory 1.2940 +, mozIAsyncHistory 1.2941 +, nsIObserver 1.2942 +, nsIMemoryReporter 1.2943 +) 1.2944 + 1.2945 +} // namespace places 1.2946 +} // namespace mozilla