toolkit/components/places/History.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/ArrayUtils.h"
michael@0 8 #include "mozilla/Attributes.h"
michael@0 9 #include "mozilla/DebugOnly.h"
michael@0 10 #include "mozilla/MemoryReporting.h"
michael@0 11
michael@0 12 #include "mozilla/dom/ContentChild.h"
michael@0 13 #include "mozilla/dom/ContentParent.h"
michael@0 14 #include "nsXULAppAPI.h"
michael@0 15
michael@0 16 #include "History.h"
michael@0 17 #include "nsNavHistory.h"
michael@0 18 #include "nsNavBookmarks.h"
michael@0 19 #include "nsAnnotationService.h"
michael@0 20 #include "Helpers.h"
michael@0 21 #include "PlaceInfo.h"
michael@0 22 #include "VisitInfo.h"
michael@0 23 #include "nsPlacesMacros.h"
michael@0 24
michael@0 25 #include "mozilla/storage.h"
michael@0 26 #include "mozilla/dom/Link.h"
michael@0 27 #include "nsDocShellCID.h"
michael@0 28 #include "mozilla/Services.h"
michael@0 29 #include "nsThreadUtils.h"
michael@0 30 #include "nsNetUtil.h"
michael@0 31 #include "nsIXPConnect.h"
michael@0 32 #include "mozilla/unused.h"
michael@0 33 #include "nsContentUtils.h" // for nsAutoScriptBlocker
michael@0 34 #include "mozilla/ipc/URIUtils.h"
michael@0 35 #include "nsPrintfCString.h"
michael@0 36 #include "nsTHashtable.h"
michael@0 37 #include "jsapi.h"
michael@0 38
michael@0 39 // Initial size for the cache holding visited status observers.
michael@0 40 #define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128
michael@0 41
michael@0 42 // Initial size for the visits removal hash.
michael@0 43 #define VISITS_REMOVAL_INITIAL_HASH_SIZE 128
michael@0 44
michael@0 45 using namespace mozilla::dom;
michael@0 46 using namespace mozilla::ipc;
michael@0 47 using mozilla::unused;
michael@0 48
michael@0 49 namespace mozilla {
michael@0 50 namespace places {
michael@0 51
michael@0 52 ////////////////////////////////////////////////////////////////////////////////
michael@0 53 //// Global Defines
michael@0 54
michael@0 55 #define URI_VISITED "visited"
michael@0 56 #define URI_NOT_VISITED "not visited"
michael@0 57 #define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
michael@0 58 // Observer event fired after a visit has been registered in the DB.
michael@0 59 #define URI_VISIT_SAVED "uri-visit-saved"
michael@0 60
michael@0 61 #define DESTINATIONFILEURI_ANNO \
michael@0 62 NS_LITERAL_CSTRING("downloads/destinationFileURI")
michael@0 63 #define DESTINATIONFILENAME_ANNO \
michael@0 64 NS_LITERAL_CSTRING("downloads/destinationFileName")
michael@0 65
michael@0 66 ////////////////////////////////////////////////////////////////////////////////
michael@0 67 //// VisitData
michael@0 68
michael@0 69 struct VisitData {
michael@0 70 VisitData()
michael@0 71 : placeId(0)
michael@0 72 , visitId(0)
michael@0 73 , hidden(true)
michael@0 74 , typed(false)
michael@0 75 , transitionType(UINT32_MAX)
michael@0 76 , visitTime(0)
michael@0 77 , frecency(-1)
michael@0 78 , titleChanged(false)
michael@0 79 , shouldUpdateFrecency(true)
michael@0 80 {
michael@0 81 guid.SetIsVoid(true);
michael@0 82 title.SetIsVoid(true);
michael@0 83 }
michael@0 84
michael@0 85 VisitData(nsIURI* aURI,
michael@0 86 nsIURI* aReferrer = nullptr)
michael@0 87 : placeId(0)
michael@0 88 , visitId(0)
michael@0 89 , hidden(true)
michael@0 90 , typed(false)
michael@0 91 , transitionType(UINT32_MAX)
michael@0 92 , visitTime(0)
michael@0 93 , frecency(-1)
michael@0 94 , titleChanged(false)
michael@0 95 , shouldUpdateFrecency(true)
michael@0 96 {
michael@0 97 (void)aURI->GetSpec(spec);
michael@0 98 (void)GetReversedHostname(aURI, revHost);
michael@0 99 if (aReferrer) {
michael@0 100 (void)aReferrer->GetSpec(referrerSpec);
michael@0 101 }
michael@0 102 guid.SetIsVoid(true);
michael@0 103 title.SetIsVoid(true);
michael@0 104 }
michael@0 105
michael@0 106 /**
michael@0 107 * Sets the transition type of the visit, as well as if it was typed.
michael@0 108 *
michael@0 109 * @param aTransitionType
michael@0 110 * The transition type constant to set. Must be one of the
michael@0 111 * TRANSITION_ constants on nsINavHistoryService.
michael@0 112 */
michael@0 113 void SetTransitionType(uint32_t aTransitionType)
michael@0 114 {
michael@0 115 typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
michael@0 116 transitionType = aTransitionType;
michael@0 117 }
michael@0 118
michael@0 119 /**
michael@0 120 * Determines if this refers to the same url as aOther, and updates aOther
michael@0 121 * with missing information if so.
michael@0 122 *
michael@0 123 * @param aOther
michael@0 124 * The other place to check against.
michael@0 125 * @return true if this is a visit for the same place as aOther, false
michael@0 126 * otherwise.
michael@0 127 */
michael@0 128 bool IsSamePlaceAs(VisitData& aOther)
michael@0 129 {
michael@0 130 if (!spec.Equals(aOther.spec)) {
michael@0 131 return false;
michael@0 132 }
michael@0 133
michael@0 134 aOther.placeId = placeId;
michael@0 135 aOther.guid = guid;
michael@0 136 return true;
michael@0 137 }
michael@0 138
michael@0 139 int64_t placeId;
michael@0 140 nsCString guid;
michael@0 141 int64_t visitId;
michael@0 142 nsCString spec;
michael@0 143 nsString revHost;
michael@0 144 bool hidden;
michael@0 145 bool typed;
michael@0 146 uint32_t transitionType;
michael@0 147 PRTime visitTime;
michael@0 148 int32_t frecency;
michael@0 149
michael@0 150 /**
michael@0 151 * Stores the title. If this is empty (IsEmpty() returns true), then the
michael@0 152 * title should be removed from the Place. If the title is void (IsVoid()
michael@0 153 * returns true), then no title has been set on this object, and titleChanged
michael@0 154 * should remain false.
michael@0 155 */
michael@0 156 nsString title;
michael@0 157
michael@0 158 nsCString referrerSpec;
michael@0 159
michael@0 160 // TODO bug 626836 hook up hidden and typed change tracking too!
michael@0 161 bool titleChanged;
michael@0 162
michael@0 163 // Indicates whether frecency should be updated for this visit.
michael@0 164 bool shouldUpdateFrecency;
michael@0 165 };
michael@0 166
michael@0 167 ////////////////////////////////////////////////////////////////////////////////
michael@0 168 //// RemoveVisitsFilter
michael@0 169
michael@0 170 /**
michael@0 171 * Used to store visit filters for RemoveVisits.
michael@0 172 */
michael@0 173 struct RemoveVisitsFilter {
michael@0 174 RemoveVisitsFilter()
michael@0 175 : transitionType(UINT32_MAX)
michael@0 176 {
michael@0 177 }
michael@0 178
michael@0 179 uint32_t transitionType;
michael@0 180 };
michael@0 181
michael@0 182 ////////////////////////////////////////////////////////////////////////////////
michael@0 183 //// PlaceHashKey
michael@0 184
michael@0 185 class PlaceHashKey : public nsCStringHashKey
michael@0 186 {
michael@0 187 public:
michael@0 188 PlaceHashKey(const nsACString& aSpec)
michael@0 189 : nsCStringHashKey(&aSpec)
michael@0 190 , visitCount(-1)
michael@0 191 , bookmarked(-1)
michael@0 192 {
michael@0 193 }
michael@0 194
michael@0 195 PlaceHashKey(const nsACString* aSpec)
michael@0 196 : nsCStringHashKey(aSpec)
michael@0 197 , visitCount(-1)
michael@0 198 , bookmarked(-1)
michael@0 199 {
michael@0 200 }
michael@0 201
michael@0 202 PlaceHashKey(const PlaceHashKey& aOther)
michael@0 203 : nsCStringHashKey(&aOther.GetKey())
michael@0 204 {
michael@0 205 MOZ_ASSERT(false, "Do not call me!");
michael@0 206 }
michael@0 207
michael@0 208 // Visit count for this place.
michael@0 209 int32_t visitCount;
michael@0 210 // Whether this place is bookmarked.
michael@0 211 int32_t bookmarked;
michael@0 212 // Array of VisitData objects.
michael@0 213 nsTArray<VisitData> visits;
michael@0 214 };
michael@0 215
michael@0 216 ////////////////////////////////////////////////////////////////////////////////
michael@0 217 //// Anonymous Helpers
michael@0 218
michael@0 219 namespace {
michael@0 220
michael@0 221 /**
michael@0 222 * Convert the given js value to a js array.
michael@0 223 *
michael@0 224 * @param [in] aValue
michael@0 225 * the JS value to convert.
michael@0 226 * @param [in] aCtx
michael@0 227 * The JSContext for aValue.
michael@0 228 * @param [out] _array
michael@0 229 * the JS array.
michael@0 230 * @param [out] _arrayLength
michael@0 231 * _array's length.
michael@0 232 */
michael@0 233 nsresult
michael@0 234 GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
michael@0 235 JSContext* aCtx,
michael@0 236 JS::MutableHandle<JSObject*> _array,
michael@0 237 uint32_t* _arrayLength) {
michael@0 238 if (aValue.isObjectOrNull()) {
michael@0 239 JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
michael@0 240 if (JS_IsArrayObject(aCtx, val)) {
michael@0 241 _array.set(val);
michael@0 242 (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
michael@0 243 NS_ENSURE_ARG(*_arrayLength > 0);
michael@0 244 return NS_OK;
michael@0 245 }
michael@0 246 }
michael@0 247
michael@0 248 // Build a temporary array to store this one item so the code below can
michael@0 249 // just loop.
michael@0 250 *_arrayLength = 1;
michael@0 251 _array.set(JS_NewArrayObject(aCtx, 0));
michael@0 252 NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
michael@0 253
michael@0 254 bool rc = JS_DefineElement(aCtx, _array, 0, aValue, nullptr, nullptr, 0);
michael@0 255 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 256 return NS_OK;
michael@0 257 }
michael@0 258
michael@0 259 /**
michael@0 260 * Attemps to convert a given js value to a nsIURI object.
michael@0 261 * @param aCtx
michael@0 262 * The JSContext for aValue.
michael@0 263 * @param aValue
michael@0 264 * The JS value to convert.
michael@0 265 * @return the nsIURI object, or null if aValue is not a nsIURI object.
michael@0 266 */
michael@0 267 already_AddRefed<nsIURI>
michael@0 268 GetJSValueAsURI(JSContext* aCtx,
michael@0 269 const JS::Value& aValue) {
michael@0 270 if (!JSVAL_IS_PRIMITIVE(aValue)) {
michael@0 271 nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
michael@0 272
michael@0 273 nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
michael@0 274 nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(aValue),
michael@0 275 getter_AddRefs(wrappedObj));
michael@0 276 NS_ENSURE_SUCCESS(rv, nullptr);
michael@0 277 nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
michael@0 278 return uri.forget();
michael@0 279 }
michael@0 280 return nullptr;
michael@0 281 }
michael@0 282
michael@0 283 /**
michael@0 284 * Obtains an nsIURI from the "uri" property of a JSObject.
michael@0 285 *
michael@0 286 * @param aCtx
michael@0 287 * The JSContext for aObject.
michael@0 288 * @param aObject
michael@0 289 * The JSObject to get the URI from.
michael@0 290 * @param aProperty
michael@0 291 * The name of the property to get the URI from.
michael@0 292 * @return the URI if it exists.
michael@0 293 */
michael@0 294 already_AddRefed<nsIURI>
michael@0 295 GetURIFromJSObject(JSContext* aCtx,
michael@0 296 JS::Handle<JSObject *> aObject,
michael@0 297 const char* aProperty)
michael@0 298 {
michael@0 299 JS::Rooted<JS::Value> uriVal(aCtx);
michael@0 300 bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
michael@0 301 NS_ENSURE_TRUE(rc, nullptr);
michael@0 302 return GetJSValueAsURI(aCtx, uriVal);
michael@0 303 }
michael@0 304
michael@0 305 /**
michael@0 306 * Attemps to convert a JS value to a string.
michael@0 307 * @param aCtx
michael@0 308 * The JSContext for aObject.
michael@0 309 * @param aValue
michael@0 310 * The JS value to convert.
michael@0 311 * @param _string
michael@0 312 * The string to populate with the value, or set it to void.
michael@0 313 */
michael@0 314 void
michael@0 315 GetJSValueAsString(JSContext* aCtx,
michael@0 316 const JS::Value& aValue,
michael@0 317 nsString& _string) {
michael@0 318 if (JSVAL_IS_VOID(aValue) ||
michael@0 319 !(JSVAL_IS_NULL(aValue) || JSVAL_IS_STRING(aValue))) {
michael@0 320 _string.SetIsVoid(true);
michael@0 321 return;
michael@0 322 }
michael@0 323
michael@0 324 // |null| in JS maps to the empty string.
michael@0 325 if (JSVAL_IS_NULL(aValue)) {
michael@0 326 _string.Truncate();
michael@0 327 return;
michael@0 328 }
michael@0 329 size_t length;
michael@0 330 const jschar* chars =
michael@0 331 JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(aValue), &length);
michael@0 332 if (!chars) {
michael@0 333 _string.SetIsVoid(true);
michael@0 334 return;
michael@0 335 }
michael@0 336 _string.Assign(static_cast<const char16_t*>(chars), length);
michael@0 337 }
michael@0 338
michael@0 339 /**
michael@0 340 * Obtains the specified property of a JSObject.
michael@0 341 *
michael@0 342 * @param aCtx
michael@0 343 * The JSContext for aObject.
michael@0 344 * @param aObject
michael@0 345 * The JSObject to get the string from.
michael@0 346 * @param aProperty
michael@0 347 * The property to get the value from.
michael@0 348 * @param _string
michael@0 349 * The string to populate with the value, or set it to void.
michael@0 350 */
michael@0 351 void
michael@0 352 GetStringFromJSObject(JSContext* aCtx,
michael@0 353 JS::Handle<JSObject *> aObject,
michael@0 354 const char* aProperty,
michael@0 355 nsString& _string)
michael@0 356 {
michael@0 357 JS::Rooted<JS::Value> val(aCtx);
michael@0 358 bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
michael@0 359 if (!rc) {
michael@0 360 _string.SetIsVoid(true);
michael@0 361 return;
michael@0 362 }
michael@0 363 else {
michael@0 364 GetJSValueAsString(aCtx, val, _string);
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 /**
michael@0 369 * Obtains the specified property of a JSObject.
michael@0 370 *
michael@0 371 * @param aCtx
michael@0 372 * The JSContext for aObject.
michael@0 373 * @param aObject
michael@0 374 * The JSObject to get the int from.
michael@0 375 * @param aProperty
michael@0 376 * The property to get the value from.
michael@0 377 * @param _int
michael@0 378 * The integer to populate with the value on success.
michael@0 379 */
michael@0 380 template <typename IntType>
michael@0 381 nsresult
michael@0 382 GetIntFromJSObject(JSContext* aCtx,
michael@0 383 JS::Handle<JSObject *> aObject,
michael@0 384 const char* aProperty,
michael@0 385 IntType* _int)
michael@0 386 {
michael@0 387 JS::Rooted<JS::Value> value(aCtx);
michael@0 388 bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
michael@0 389 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 390 if (JSVAL_IS_VOID(value)) {
michael@0 391 return NS_ERROR_INVALID_ARG;
michael@0 392 }
michael@0 393 NS_ENSURE_ARG(JSVAL_IS_PRIMITIVE(value));
michael@0 394 NS_ENSURE_ARG(JSVAL_IS_NUMBER(value));
michael@0 395
michael@0 396 double num;
michael@0 397 rc = JS::ToNumber(aCtx, value, &num);
michael@0 398 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 399 NS_ENSURE_ARG(IntType(num) == num);
michael@0 400
michael@0 401 *_int = IntType(num);
michael@0 402 return NS_OK;
michael@0 403 }
michael@0 404
michael@0 405 /**
michael@0 406 * Obtains the specified property of a JSObject.
michael@0 407 *
michael@0 408 * @pre aArray must be an Array object.
michael@0 409 *
michael@0 410 * @param aCtx
michael@0 411 * The JSContext for aArray.
michael@0 412 * @param aArray
michael@0 413 * The JSObject to get the object from.
michael@0 414 * @param aIndex
michael@0 415 * The index to get the object from.
michael@0 416 * @param objOut
michael@0 417 * Set to the JSObject pointer on success.
michael@0 418 */
michael@0 419 nsresult
michael@0 420 GetJSObjectFromArray(JSContext* aCtx,
michael@0 421 JS::Handle<JSObject*> aArray,
michael@0 422 uint32_t aIndex,
michael@0 423 JS::MutableHandle<JSObject*> objOut)
michael@0 424 {
michael@0 425 NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
michael@0 426 "Must provide an object that is an array!");
michael@0 427
michael@0 428 JS::Rooted<JS::Value> value(aCtx);
michael@0 429 bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
michael@0 430 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 431 NS_ENSURE_ARG(!value.isPrimitive());
michael@0 432 objOut.set(&value.toObject());
michael@0 433 return NS_OK;
michael@0 434 }
michael@0 435
michael@0 436 class VisitedQuery : public AsyncStatementCallback
michael@0 437 {
michael@0 438 public:
michael@0 439 static nsresult Start(nsIURI* aURI,
michael@0 440 mozIVisitedStatusCallback* aCallback=nullptr)
michael@0 441 {
michael@0 442 NS_PRECONDITION(aURI, "Null URI");
michael@0 443
michael@0 444 // If we are a content process, always remote the request to the
michael@0 445 // parent process.
michael@0 446 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 447 URIParams uri;
michael@0 448 SerializeURI(aURI, uri);
michael@0 449
michael@0 450 mozilla::dom::ContentChild* cpc =
michael@0 451 mozilla::dom::ContentChild::GetSingleton();
michael@0 452 NS_ASSERTION(cpc, "Content Protocol is NULL!");
michael@0 453 (void)cpc->SendStartVisitedQuery(uri);
michael@0 454 return NS_OK;
michael@0 455 }
michael@0 456
michael@0 457 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 458 NS_ENSURE_STATE(navHistory);
michael@0 459 if (navHistory->hasEmbedVisit(aURI)) {
michael@0 460 nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true);
michael@0 461 NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
michael@0 462 // As per IHistory contract, we must notify asynchronously.
michael@0 463 nsCOMPtr<nsIRunnable> event =
michael@0 464 NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
michael@0 465 NS_DispatchToMainThread(event);
michael@0 466
michael@0 467 return NS_OK;
michael@0 468 }
michael@0 469
michael@0 470 History* history = History::GetService();
michael@0 471 NS_ENSURE_STATE(history);
michael@0 472 mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
michael@0 473 NS_ENSURE_STATE(stmt);
michael@0 474
michael@0 475 // Bind by index for performance.
michael@0 476 nsresult rv = URIBinder::Bind(stmt, 0, aURI);
michael@0 477 NS_ENSURE_SUCCESS(rv, rv);
michael@0 478
michael@0 479 nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
michael@0 480 NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
michael@0 481
michael@0 482 nsCOMPtr<mozIStoragePendingStatement> handle;
michael@0 483 return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
michael@0 484 }
michael@0 485
michael@0 486 NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
michael@0 487 {
michael@0 488 // If this method is called, we've gotten results, which means we have a
michael@0 489 // visit.
michael@0 490 mIsVisited = true;
michael@0 491 return NS_OK;
michael@0 492 }
michael@0 493
michael@0 494 NS_IMETHOD HandleError(mozIStorageError* aError)
michael@0 495 {
michael@0 496 // mIsVisited is already set to false, and that's the assumption we will
michael@0 497 // make if an error occurred.
michael@0 498 return NS_OK;
michael@0 499 }
michael@0 500
michael@0 501 NS_IMETHOD HandleCompletion(uint16_t aReason)
michael@0 502 {
michael@0 503 if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
michael@0 504 return NS_OK;
michael@0 505 }
michael@0 506
michael@0 507 nsresult rv = NotifyVisitedStatus();
michael@0 508 NS_ENSURE_SUCCESS(rv, rv);
michael@0 509 return NS_OK;
michael@0 510 }
michael@0 511
michael@0 512 nsresult NotifyVisitedStatus()
michael@0 513 {
michael@0 514 // If an external handling callback is provided, just notify through it.
michael@0 515 if (mCallback) {
michael@0 516 mCallback->IsVisited(mURI, mIsVisited);
michael@0 517 return NS_OK;
michael@0 518 }
michael@0 519
michael@0 520 if (mIsVisited) {
michael@0 521 History* history = History::GetService();
michael@0 522 NS_ENSURE_STATE(history);
michael@0 523 history->NotifyVisited(mURI);
michael@0 524 }
michael@0 525
michael@0 526 nsCOMPtr<nsIObserverService> observerService =
michael@0 527 mozilla::services::GetObserverService();
michael@0 528 if (observerService) {
michael@0 529 nsAutoString status;
michael@0 530 if (mIsVisited) {
michael@0 531 status.AssignLiteral(URI_VISITED);
michael@0 532 }
michael@0 533 else {
michael@0 534 status.AssignLiteral(URI_NOT_VISITED);
michael@0 535 }
michael@0 536 (void)observerService->NotifyObservers(mURI,
michael@0 537 URI_VISITED_RESOLUTION_TOPIC,
michael@0 538 status.get());
michael@0 539 }
michael@0 540
michael@0 541 return NS_OK;
michael@0 542 }
michael@0 543
michael@0 544 private:
michael@0 545 VisitedQuery(nsIURI* aURI,
michael@0 546 mozIVisitedStatusCallback *aCallback=nullptr,
michael@0 547 bool aIsVisited=false)
michael@0 548 : mURI(aURI)
michael@0 549 , mCallback(aCallback)
michael@0 550 , mIsVisited(aIsVisited)
michael@0 551 {
michael@0 552 }
michael@0 553
michael@0 554 nsCOMPtr<nsIURI> mURI;
michael@0 555 nsCOMPtr<mozIVisitedStatusCallback> mCallback;
michael@0 556 bool mIsVisited;
michael@0 557 };
michael@0 558
michael@0 559 /**
michael@0 560 * Notifies observers about a visit.
michael@0 561 */
michael@0 562 class NotifyVisitObservers : public nsRunnable
michael@0 563 {
michael@0 564 public:
michael@0 565 NotifyVisitObservers(VisitData& aPlace,
michael@0 566 VisitData& aReferrer)
michael@0 567 : mPlace(aPlace)
michael@0 568 , mReferrer(aReferrer)
michael@0 569 , mHistory(History::GetService())
michael@0 570 {
michael@0 571 }
michael@0 572
michael@0 573 NS_IMETHOD Run()
michael@0 574 {
michael@0 575 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 576
michael@0 577 // We are in the main thread, no need to lock.
michael@0 578 if (mHistory->IsShuttingDown()) {
michael@0 579 // If we are shutting down, we cannot notify the observers.
michael@0 580 return NS_OK;
michael@0 581 }
michael@0 582
michael@0 583 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 584 if (!navHistory) {
michael@0 585 NS_WARNING("Trying to notify about a visit but cannot get the history service!");
michael@0 586 return NS_OK;
michael@0 587 }
michael@0 588
michael@0 589 nsCOMPtr<nsIURI> uri;
michael@0 590 (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
michael@0 591
michael@0 592 // Notify the visit. Note that TRANSITION_EMBED visits are never added
michael@0 593 // to the database, thus cannot be queried and we don't notify them.
michael@0 594 if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
michael@0 595 navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
michael@0 596 mReferrer.visitId, mPlace.transitionType,
michael@0 597 mPlace.guid, mPlace.hidden);
michael@0 598 }
michael@0 599
michael@0 600 nsCOMPtr<nsIObserverService> obsService =
michael@0 601 mozilla::services::GetObserverService();
michael@0 602 if (obsService) {
michael@0 603 DebugOnly<nsresult> rv =
michael@0 604 obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr);
michael@0 605 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers");
michael@0 606 }
michael@0 607
michael@0 608 History* history = History::GetService();
michael@0 609 NS_ENSURE_STATE(history);
michael@0 610 history->AppendToRecentlyVisitedURIs(uri);
michael@0 611 history->NotifyVisited(uri);
michael@0 612
michael@0 613 return NS_OK;
michael@0 614 }
michael@0 615 private:
michael@0 616 VisitData mPlace;
michael@0 617 VisitData mReferrer;
michael@0 618 nsRefPtr<History> mHistory;
michael@0 619 };
michael@0 620
michael@0 621 /**
michael@0 622 * Notifies observers about a pages title changing.
michael@0 623 */
michael@0 624 class NotifyTitleObservers : public nsRunnable
michael@0 625 {
michael@0 626 public:
michael@0 627 /**
michael@0 628 * Notifies observers on the main thread.
michael@0 629 *
michael@0 630 * @param aSpec
michael@0 631 * The spec of the URI to notify about.
michael@0 632 * @param aTitle
michael@0 633 * The new title to notify about.
michael@0 634 */
michael@0 635 NotifyTitleObservers(const nsCString& aSpec,
michael@0 636 const nsString& aTitle,
michael@0 637 const nsCString& aGUID)
michael@0 638 : mSpec(aSpec)
michael@0 639 , mTitle(aTitle)
michael@0 640 , mGUID(aGUID)
michael@0 641 {
michael@0 642 }
michael@0 643
michael@0 644 NS_IMETHOD Run()
michael@0 645 {
michael@0 646 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 647
michael@0 648 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 649 NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
michael@0 650 nsCOMPtr<nsIURI> uri;
michael@0 651 (void)NS_NewURI(getter_AddRefs(uri), mSpec);
michael@0 652 navHistory->NotifyTitleChange(uri, mTitle, mGUID);
michael@0 653
michael@0 654 return NS_OK;
michael@0 655 }
michael@0 656 private:
michael@0 657 const nsCString mSpec;
michael@0 658 const nsString mTitle;
michael@0 659 const nsCString mGUID;
michael@0 660 };
michael@0 661
michael@0 662 /**
michael@0 663 * Helper class for methods which notify their callers through the
michael@0 664 * mozIVisitInfoCallback interface.
michael@0 665 */
michael@0 666 class NotifyPlaceInfoCallback : public nsRunnable
michael@0 667 {
michael@0 668 public:
michael@0 669 NotifyPlaceInfoCallback(mozIVisitInfoCallback* aCallback,
michael@0 670 const VisitData& aPlace,
michael@0 671 bool aIsSingleVisit,
michael@0 672 nsresult aResult)
michael@0 673 : mCallback(aCallback)
michael@0 674 , mPlace(aPlace)
michael@0 675 , mResult(aResult)
michael@0 676 , mIsSingleVisit(aIsSingleVisit)
michael@0 677 {
michael@0 678 MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
michael@0 679 }
michael@0 680
michael@0 681 NS_IMETHOD Run()
michael@0 682 {
michael@0 683 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 684
michael@0 685 nsCOMPtr<nsIURI> referrerURI;
michael@0 686 if (!mPlace.referrerSpec.IsEmpty()) {
michael@0 687 (void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec);
michael@0 688 }
michael@0 689
michael@0 690 nsCOMPtr<nsIURI> uri;
michael@0 691 (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
michael@0 692
michael@0 693 nsCOMPtr<mozIPlaceInfo> place;
michael@0 694 if (mIsSingleVisit) {
michael@0 695 nsCOMPtr<mozIVisitInfo> visit =
michael@0 696 new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
michael@0 697 referrerURI.forget());
michael@0 698 PlaceInfo::VisitsArray visits;
michael@0 699 (void)visits.AppendElement(visit);
michael@0 700
michael@0 701 // The frecency isn't exposed because it may not reflect the updated value
michael@0 702 // in the case of InsertVisitedURIs.
michael@0 703 place =
michael@0 704 new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
michael@0 705 -1, visits);
michael@0 706 }
michael@0 707 else {
michael@0 708 // Same as above.
michael@0 709 place =
michael@0 710 new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
michael@0 711 -1);
michael@0 712 }
michael@0 713
michael@0 714 if (NS_SUCCEEDED(mResult)) {
michael@0 715 (void)mCallback->HandleResult(place);
michael@0 716 }
michael@0 717 else {
michael@0 718 (void)mCallback->HandleError(mResult, place);
michael@0 719 }
michael@0 720
michael@0 721 return NS_OK;
michael@0 722 }
michael@0 723
michael@0 724 private:
michael@0 725 /**
michael@0 726 * Callers MUST hold a strong reference to this that outlives us because we
michael@0 727 * may be created off of the main thread, and therefore cannot call AddRef on
michael@0 728 * this object (and therefore cannot hold a strong reference to it).
michael@0 729 */
michael@0 730 mozIVisitInfoCallback* mCallback;
michael@0 731 VisitData mPlace;
michael@0 732 const nsresult mResult;
michael@0 733 bool mIsSingleVisit;
michael@0 734 };
michael@0 735
michael@0 736 /**
michael@0 737 * Notifies a callback object when the operation is complete.
michael@0 738 */
michael@0 739 class NotifyCompletion : public nsRunnable
michael@0 740 {
michael@0 741 public:
michael@0 742 NotifyCompletion(mozIVisitInfoCallback* aCallback)
michael@0 743 : mCallback(aCallback)
michael@0 744 {
michael@0 745 MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
michael@0 746 }
michael@0 747
michael@0 748 NS_IMETHOD Run()
michael@0 749 {
michael@0 750 if (NS_IsMainThread()) {
michael@0 751 (void)mCallback->HandleCompletion();
michael@0 752 }
michael@0 753 else {
michael@0 754 (void)NS_DispatchToMainThread(this);
michael@0 755
michael@0 756 // Also dispatch an event to release the reference to the callback after
michael@0 757 // we have run.
michael@0 758 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 759 (void)NS_ProxyRelease(mainThread, mCallback, true);
michael@0 760 }
michael@0 761 return NS_OK;
michael@0 762 }
michael@0 763
michael@0 764 private:
michael@0 765 /**
michael@0 766 * Callers MUST hold a strong reference to this because we may be created
michael@0 767 * off of the main thread, and therefore cannot call AddRef on this object
michael@0 768 * (and therefore cannot hold a strong reference to it). If invoked from a
michael@0 769 * background thread, NotifyCompletion will release the reference to this.
michael@0 770 */
michael@0 771 mozIVisitInfoCallback* mCallback;
michael@0 772 };
michael@0 773
michael@0 774 /**
michael@0 775 * Checks to see if we can add aURI to history, and dispatches an error to
michael@0 776 * aCallback (if provided) if we cannot.
michael@0 777 *
michael@0 778 * @param aURI
michael@0 779 * The URI to check.
michael@0 780 * @param [optional] aGUID
michael@0 781 * The guid of the URI to check. This is passed back to the callback.
michael@0 782 * @param [optional] aCallback
michael@0 783 * The callback to notify if the URI cannot be added to history.
michael@0 784 * @return true if the URI can be added to history, false otherwise.
michael@0 785 */
michael@0 786 bool
michael@0 787 CanAddURI(nsIURI* aURI,
michael@0 788 const nsCString& aGUID = EmptyCString(),
michael@0 789 mozIVisitInfoCallback* aCallback = nullptr)
michael@0 790 {
michael@0 791 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 792 NS_ENSURE_TRUE(navHistory, false);
michael@0 793
michael@0 794 bool canAdd;
michael@0 795 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
michael@0 796 if (NS_SUCCEEDED(rv) && canAdd) {
michael@0 797 return true;
michael@0 798 };
michael@0 799
michael@0 800 // We cannot add the URI. Notify the callback, if we were given one.
michael@0 801 if (aCallback) {
michael@0 802 // NotifyPlaceInfoCallback does not hold a strong reference to the callback, so we
michael@0 803 // have to manage it by AddRefing now and then releasing it after the event
michael@0 804 // has run.
michael@0 805 NS_ADDREF(aCallback);
michael@0 806
michael@0 807 VisitData place(aURI);
michael@0 808 place.guid = aGUID;
michael@0 809 nsCOMPtr<nsIRunnable> event =
michael@0 810 new NotifyPlaceInfoCallback(aCallback, place, true, NS_ERROR_INVALID_ARG);
michael@0 811 (void)NS_DispatchToMainThread(event);
michael@0 812
michael@0 813 // Also dispatch an event to release our reference to the callback after
michael@0 814 // NotifyPlaceInfoCallback has run.
michael@0 815 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 816 (void)NS_ProxyRelease(mainThread, aCallback, true);
michael@0 817 }
michael@0 818
michael@0 819 return false;
michael@0 820 }
michael@0 821
michael@0 822 /**
michael@0 823 * Adds a visit to the database.
michael@0 824 */
michael@0 825 class InsertVisitedURIs : public nsRunnable
michael@0 826 {
michael@0 827 public:
michael@0 828 /**
michael@0 829 * Adds a visit to the database asynchronously.
michael@0 830 *
michael@0 831 * @param aConnection
michael@0 832 * The database connection to use for these operations.
michael@0 833 * @param aPlaces
michael@0 834 * The locations to record visits.
michael@0 835 * @param [optional] aCallback
michael@0 836 * The callback to notify about the visit.
michael@0 837 */
michael@0 838 static nsresult Start(mozIStorageConnection* aConnection,
michael@0 839 nsTArray<VisitData>& aPlaces,
michael@0 840 mozIVisitInfoCallback* aCallback = nullptr)
michael@0 841 {
michael@0 842 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 843 MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
michael@0 844
michael@0 845 nsRefPtr<InsertVisitedURIs> event =
michael@0 846 new InsertVisitedURIs(aConnection, aPlaces, aCallback);
michael@0 847
michael@0 848 // Get the target thread, and then start the work!
michael@0 849 nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
michael@0 850 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
michael@0 851 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 852 NS_ENSURE_SUCCESS(rv, rv);
michael@0 853
michael@0 854 return NS_OK;
michael@0 855 }
michael@0 856
michael@0 857 NS_IMETHOD Run()
michael@0 858 {
michael@0 859 MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
michael@0 860
michael@0 861 // Prevent the main thread from shutting down while this is running.
michael@0 862 MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
michael@0 863 if (mHistory->IsShuttingDown()) {
michael@0 864 // If we were already shutting down, we cannot insert the URIs.
michael@0 865 return NS_OK;
michael@0 866 }
michael@0 867
michael@0 868 mozStorageTransaction transaction(mDBConn, false,
michael@0 869 mozIStorageConnection::TRANSACTION_IMMEDIATE);
michael@0 870
michael@0 871 VisitData* lastPlace = nullptr;
michael@0 872 for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
michael@0 873 VisitData& place = mPlaces.ElementAt(i);
michael@0 874 VisitData& referrer = mReferrers.ElementAt(i);
michael@0 875
michael@0 876 // We can avoid a database lookup if it's the same place as the last
michael@0 877 // visit we added.
michael@0 878 bool known = lastPlace && lastPlace->IsSamePlaceAs(place);
michael@0 879 if (!known) {
michael@0 880 nsresult rv = mHistory->FetchPageInfo(place, &known);
michael@0 881 if (NS_FAILED(rv)) {
michael@0 882 if (mCallback) {
michael@0 883 nsCOMPtr<nsIRunnable> event =
michael@0 884 new NotifyPlaceInfoCallback(mCallback, place, true, rv);
michael@0 885 return NS_DispatchToMainThread(event);
michael@0 886 }
michael@0 887 return NS_OK;
michael@0 888 }
michael@0 889 }
michael@0 890
michael@0 891 FetchReferrerInfo(referrer, place);
michael@0 892
michael@0 893 nsresult rv = DoDatabaseInserts(known, place, referrer);
michael@0 894 if (mCallback) {
michael@0 895 nsCOMPtr<nsIRunnable> event =
michael@0 896 new NotifyPlaceInfoCallback(mCallback, place, true, rv);
michael@0 897 nsresult rv2 = NS_DispatchToMainThread(event);
michael@0 898 NS_ENSURE_SUCCESS(rv2, rv2);
michael@0 899 }
michael@0 900 NS_ENSURE_SUCCESS(rv, rv);
michael@0 901
michael@0 902 nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
michael@0 903 rv = NS_DispatchToMainThread(event);
michael@0 904 NS_ENSURE_SUCCESS(rv, rv);
michael@0 905
michael@0 906 // Notify about title change if needed.
michael@0 907 if ((!known && !place.title.IsVoid()) || place.titleChanged) {
michael@0 908 event = new NotifyTitleObservers(place.spec, place.title, place.guid);
michael@0 909 rv = NS_DispatchToMainThread(event);
michael@0 910 NS_ENSURE_SUCCESS(rv, rv);
michael@0 911 }
michael@0 912
michael@0 913 lastPlace = &mPlaces.ElementAt(i);
michael@0 914 }
michael@0 915
michael@0 916 nsresult rv = transaction.Commit();
michael@0 917 NS_ENSURE_SUCCESS(rv, rv);
michael@0 918
michael@0 919 return NS_OK;
michael@0 920 }
michael@0 921 private:
michael@0 922 InsertVisitedURIs(mozIStorageConnection* aConnection,
michael@0 923 nsTArray<VisitData>& aPlaces,
michael@0 924 mozIVisitInfoCallback* aCallback)
michael@0 925 : mDBConn(aConnection)
michael@0 926 , mCallback(aCallback)
michael@0 927 , mHistory(History::GetService())
michael@0 928 {
michael@0 929 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 930
michael@0 931 (void)mPlaces.SwapElements(aPlaces);
michael@0 932 (void)mReferrers.SetLength(mPlaces.Length());
michael@0 933
michael@0 934 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 935 NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
michael@0 936
michael@0 937 for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
michael@0 938 mReferrers[i].spec = mPlaces[i].referrerSpec;
michael@0 939
michael@0 940 #ifdef DEBUG
michael@0 941 nsCOMPtr<nsIURI> uri;
michael@0 942 (void)NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec);
michael@0 943 NS_ASSERTION(CanAddURI(uri),
michael@0 944 "Passed a VisitData with a URI we cannot add to history!");
michael@0 945 #endif
michael@0 946 }
michael@0 947 }
michael@0 948
michael@0 949 virtual ~InsertVisitedURIs()
michael@0 950 {
michael@0 951 if (mCallback) {
michael@0 952 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 953 (void)NS_ProxyRelease(mainThread, mCallback, true);
michael@0 954 }
michael@0 955 }
michael@0 956
michael@0 957 /**
michael@0 958 * Inserts or updates the entry in moz_places for this visit, adds the visit,
michael@0 959 * and updates the frecency of the place.
michael@0 960 *
michael@0 961 * @param aKnown
michael@0 962 * True if we already have an entry for this place in moz_places, false
michael@0 963 * otherwise.
michael@0 964 * @param aPlace
michael@0 965 * The place we are adding a visit for.
michael@0 966 * @param aReferrer
michael@0 967 * The referrer for aPlace.
michael@0 968 */
michael@0 969 nsresult DoDatabaseInserts(bool aKnown,
michael@0 970 VisitData& aPlace,
michael@0 971 VisitData& aReferrer)
michael@0 972 {
michael@0 973 MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
michael@0 974
michael@0 975 // If the page was in moz_places, we need to update the entry.
michael@0 976 nsresult rv;
michael@0 977 if (aKnown) {
michael@0 978 rv = mHistory->UpdatePlace(aPlace);
michael@0 979 NS_ENSURE_SUCCESS(rv, rv);
michael@0 980 }
michael@0 981 // Otherwise, the page was not in moz_places, so now we have to add it.
michael@0 982 else {
michael@0 983 rv = mHistory->InsertPlace(aPlace);
michael@0 984 NS_ENSURE_SUCCESS(rv, rv);
michael@0 985
michael@0 986 // We need the place id and guid of the page we just inserted when we
michael@0 987 // have a callback or when the GUID isn't known. No point in doing the
michael@0 988 // disk I/O if we do not need it.
michael@0 989 if (mCallback || aPlace.guid.IsEmpty()) {
michael@0 990 bool exists;
michael@0 991 rv = mHistory->FetchPageInfo(aPlace, &exists);
michael@0 992 NS_ENSURE_SUCCESS(rv, rv);
michael@0 993
michael@0 994 if (!exists) {
michael@0 995 NS_NOTREACHED("should have an entry in moz_places");
michael@0 996 }
michael@0 997 }
michael@0 998 }
michael@0 999
michael@0 1000 rv = AddVisit(aPlace, aReferrer);
michael@0 1001 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1002
michael@0 1003 // TODO (bug 623969) we shouldn't update this after each visit, but
michael@0 1004 // rather only for each unique place to save disk I/O.
michael@0 1005
michael@0 1006 // Don't update frecency if the page should not appear in autocomplete.
michael@0 1007 if (aPlace.shouldUpdateFrecency) {
michael@0 1008 rv = UpdateFrecency(aPlace);
michael@0 1009 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1010 }
michael@0 1011
michael@0 1012 return NS_OK;
michael@0 1013 }
michael@0 1014
michael@0 1015 /**
michael@0 1016 * Loads visit information about the page into _place.
michael@0 1017 *
michael@0 1018 * @param _place
michael@0 1019 * The VisitData for the place we need to know visit information about.
michael@0 1020 * @param [optional] aThresholdStart
michael@0 1021 * The timestamp of a new visit (not represented by _place) used to
michael@0 1022 * determine if the page was recently visited or not.
michael@0 1023 * @return true if the page was recently (determined with aThresholdStart)
michael@0 1024 * visited, false otherwise.
michael@0 1025 */
michael@0 1026 bool FetchVisitInfo(VisitData& _place,
michael@0 1027 PRTime aThresholdStart = 0)
michael@0 1028 {
michael@0 1029 NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
michael@0 1030
michael@0 1031 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1032 // If we have a visitTime, we want information on that specific visit.
michael@0 1033 if (_place.visitTime) {
michael@0 1034 stmt = mHistory->GetStatement(
michael@0 1035 "SELECT id, visit_date "
michael@0 1036 "FROM moz_historyvisits "
michael@0 1037 "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
michael@0 1038 "AND visit_date = :visit_date "
michael@0 1039 );
michael@0 1040 NS_ENSURE_TRUE(stmt, false);
michael@0 1041
michael@0 1042 mozStorageStatementScoper scoper(stmt);
michael@0 1043 nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
michael@0 1044 _place.visitTime);
michael@0 1045 NS_ENSURE_SUCCESS(rv, false);
michael@0 1046
michael@0 1047 scoper.Abandon();
michael@0 1048 }
michael@0 1049 // Otherwise, we want information about the most recent visit.
michael@0 1050 else {
michael@0 1051 stmt = mHistory->GetStatement(
michael@0 1052 "SELECT id, visit_date "
michael@0 1053 "FROM moz_historyvisits "
michael@0 1054 "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
michael@0 1055 "ORDER BY visit_date DESC "
michael@0 1056 );
michael@0 1057 NS_ENSURE_TRUE(stmt, false);
michael@0 1058 }
michael@0 1059 mozStorageStatementScoper scoper(stmt);
michael@0 1060
michael@0 1061 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
michael@0 1062 _place.spec);
michael@0 1063 NS_ENSURE_SUCCESS(rv, false);
michael@0 1064
michael@0 1065 bool hasResult;
michael@0 1066 rv = stmt->ExecuteStep(&hasResult);
michael@0 1067 NS_ENSURE_SUCCESS(rv, false);
michael@0 1068 if (!hasResult) {
michael@0 1069 return false;
michael@0 1070 }
michael@0 1071
michael@0 1072 rv = stmt->GetInt64(0, &_place.visitId);
michael@0 1073 NS_ENSURE_SUCCESS(rv, false);
michael@0 1074 rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_place.visitTime));
michael@0 1075 NS_ENSURE_SUCCESS(rv, false);
michael@0 1076
michael@0 1077 // If we have been given a visit threshold start time, go ahead and
michael@0 1078 // calculate if we have been recently visited.
michael@0 1079 if (aThresholdStart &&
michael@0 1080 aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
michael@0 1081 return true;
michael@0 1082 }
michael@0 1083
michael@0 1084 return false;
michael@0 1085 }
michael@0 1086
michael@0 1087 /**
michael@0 1088 * Fetches information about a referrer for aPlace if it was a recent
michael@0 1089 * visit or not.
michael@0 1090 *
michael@0 1091 * @param aReferrer
michael@0 1092 * The VisitData for the referrer. This will be populated with
michael@0 1093 * FetchVisitInfo.
michael@0 1094 * @param aPlace
michael@0 1095 * The VisitData for the visit we will eventually add.
michael@0 1096 *
michael@0 1097 */
michael@0 1098 void FetchReferrerInfo(VisitData& aReferrer,
michael@0 1099 VisitData& aPlace)
michael@0 1100 {
michael@0 1101 if (aReferrer.spec.IsEmpty()) {
michael@0 1102 return;
michael@0 1103 }
michael@0 1104
michael@0 1105 if (!FetchVisitInfo(aReferrer, aPlace.visitTime)) {
michael@0 1106 // We must change both the place and referrer to indicate that we will
michael@0 1107 // not be using the referrer's data. This behavior has test coverage, so
michael@0 1108 // if this invariant changes, we'll know.
michael@0 1109 aPlace.referrerSpec.Truncate();
michael@0 1110 aReferrer.visitId = 0;
michael@0 1111 }
michael@0 1112 }
michael@0 1113
michael@0 1114 /**
michael@0 1115 * Adds a visit for _place and updates it with the right visit id.
michael@0 1116 *
michael@0 1117 * @param _place
michael@0 1118 * The VisitData for the place we need to know visit information about.
michael@0 1119 * @param aReferrer
michael@0 1120 * A reference to the referrer's visit data.
michael@0 1121 */
michael@0 1122 nsresult AddVisit(VisitData& _place,
michael@0 1123 const VisitData& aReferrer)
michael@0 1124 {
michael@0 1125 nsresult rv;
michael@0 1126 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1127 if (_place.placeId) {
michael@0 1128 stmt = mHistory->GetStatement(
michael@0 1129 "INSERT INTO moz_historyvisits "
michael@0 1130 "(from_visit, place_id, visit_date, visit_type, session) "
michael@0 1131 "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
michael@0 1132 );
michael@0 1133 NS_ENSURE_STATE(stmt);
michael@0 1134 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
michael@0 1135 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1136 }
michael@0 1137 else {
michael@0 1138 stmt = mHistory->GetStatement(
michael@0 1139 "INSERT INTO moz_historyvisits "
michael@0 1140 "(from_visit, place_id, visit_date, visit_type, session) "
michael@0 1141 "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, 0) "
michael@0 1142 );
michael@0 1143 NS_ENSURE_STATE(stmt);
michael@0 1144 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
michael@0 1145 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1146 }
michael@0 1147 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
michael@0 1148 aReferrer.visitId);
michael@0 1149 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1150 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
michael@0 1151 _place.visitTime);
michael@0 1152 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1153 uint32_t transitionType = _place.transitionType;
michael@0 1154 NS_ASSERTION(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
michael@0 1155 transitionType <= nsINavHistoryService::TRANSITION_FRAMED_LINK,
michael@0 1156 "Invalid transition type!");
michael@0 1157 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
michael@0 1158 transitionType);
michael@0 1159 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1160
michael@0 1161 mozStorageStatementScoper scoper(stmt);
michael@0 1162 rv = stmt->Execute();
michael@0 1163 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1164
michael@0 1165 // Now that it should be in the database, we need to obtain the id of the
michael@0 1166 // visit we just added.
michael@0 1167 (void)FetchVisitInfo(_place);
michael@0 1168
michael@0 1169 return NS_OK;
michael@0 1170 }
michael@0 1171
michael@0 1172 /**
michael@0 1173 * Updates the frecency, and possibly the hidden-ness of aPlace.
michael@0 1174 *
michael@0 1175 * @param aPlace
michael@0 1176 * The VisitData for the place we want to update.
michael@0 1177 */
michael@0 1178 nsresult UpdateFrecency(const VisitData& aPlace)
michael@0 1179 {
michael@0 1180 MOZ_ASSERT(aPlace.shouldUpdateFrecency);
michael@0 1181
michael@0 1182 nsresult rv;
michael@0 1183 { // First, set our frecency to the proper value.
michael@0 1184 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1185 if (aPlace.placeId) {
michael@0 1186 stmt = mHistory->GetStatement(
michael@0 1187 "UPDATE moz_places "
michael@0 1188 "SET frecency = NOTIFY_FRECENCY("
michael@0 1189 "CALCULATE_FRECENCY(:page_id), "
michael@0 1190 "url, guid, hidden, last_visit_date"
michael@0 1191 ") "
michael@0 1192 "WHERE id = :page_id"
michael@0 1193 );
michael@0 1194 NS_ENSURE_STATE(stmt);
michael@0 1195 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
michael@0 1196 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1197 }
michael@0 1198 else {
michael@0 1199 stmt = mHistory->GetStatement(
michael@0 1200 "UPDATE moz_places "
michael@0 1201 "SET frecency = NOTIFY_FRECENCY("
michael@0 1202 "CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date"
michael@0 1203 ") "
michael@0 1204 "WHERE url = :page_url"
michael@0 1205 );
michael@0 1206 NS_ENSURE_STATE(stmt);
michael@0 1207 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
michael@0 1208 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1209 }
michael@0 1210 mozStorageStatementScoper scoper(stmt);
michael@0 1211
michael@0 1212 rv = stmt->Execute();
michael@0 1213 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1214 }
michael@0 1215
michael@0 1216 if (!aPlace.hidden) {
michael@0 1217 // Mark the page as not hidden if the frecency is now nonzero.
michael@0 1218 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 1219 if (aPlace.placeId) {
michael@0 1220 stmt = mHistory->GetStatement(
michael@0 1221 "UPDATE moz_places "
michael@0 1222 "SET hidden = 0 "
michael@0 1223 "WHERE id = :page_id AND frecency <> 0"
michael@0 1224 );
michael@0 1225 NS_ENSURE_STATE(stmt);
michael@0 1226 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
michael@0 1227 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1228 }
michael@0 1229 else {
michael@0 1230 stmt = mHistory->GetStatement(
michael@0 1231 "UPDATE moz_places "
michael@0 1232 "SET hidden = 0 "
michael@0 1233 "WHERE url = :page_url AND frecency <> 0"
michael@0 1234 );
michael@0 1235 NS_ENSURE_STATE(stmt);
michael@0 1236 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
michael@0 1237 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1238 }
michael@0 1239
michael@0 1240 mozStorageStatementScoper scoper(stmt);
michael@0 1241 rv = stmt->Execute();
michael@0 1242 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1243 }
michael@0 1244
michael@0 1245 return NS_OK;
michael@0 1246 }
michael@0 1247
michael@0 1248 mozIStorageConnection* mDBConn;
michael@0 1249
michael@0 1250 nsTArray<VisitData> mPlaces;
michael@0 1251 nsTArray<VisitData> mReferrers;
michael@0 1252
michael@0 1253 nsCOMPtr<mozIVisitInfoCallback> mCallback;
michael@0 1254
michael@0 1255 /**
michael@0 1256 * Strong reference to the History object because we do not want it to
michael@0 1257 * disappear out from under us.
michael@0 1258 */
michael@0 1259 nsRefPtr<History> mHistory;
michael@0 1260 };
michael@0 1261
michael@0 1262 class GetPlaceInfo MOZ_FINAL : public nsRunnable {
michael@0 1263 public:
michael@0 1264 /**
michael@0 1265 * Get the place info for a given place (by GUID or URI) asynchronously.
michael@0 1266 */
michael@0 1267 static nsresult Start(mozIStorageConnection* aConnection,
michael@0 1268 VisitData& aPlace,
michael@0 1269 mozIVisitInfoCallback* aCallback) {
michael@0 1270 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1271
michael@0 1272 nsRefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, aCallback);
michael@0 1273
michael@0 1274 // Get the target thread, and then start the work!
michael@0 1275 nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
michael@0 1276 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
michael@0 1277 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 1278 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1279
michael@0 1280 return NS_OK;
michael@0 1281 }
michael@0 1282
michael@0 1283 NS_IMETHOD Run()
michael@0 1284 {
michael@0 1285 MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
michael@0 1286
michael@0 1287 bool exists;
michael@0 1288 nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
michael@0 1289 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1290
michael@0 1291 if (!exists)
michael@0 1292 rv = NS_ERROR_NOT_AVAILABLE;
michael@0 1293
michael@0 1294 nsCOMPtr<nsIRunnable> event =
michael@0 1295 new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv);
michael@0 1296
michael@0 1297 rv = NS_DispatchToMainThread(event);
michael@0 1298 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1299
michael@0 1300 return NS_OK;
michael@0 1301 }
michael@0 1302 private:
michael@0 1303 GetPlaceInfo(VisitData& aPlace,
michael@0 1304 mozIVisitInfoCallback* aCallback)
michael@0 1305 : mPlace(aPlace)
michael@0 1306 , mCallback(aCallback)
michael@0 1307 , mHistory(History::GetService())
michael@0 1308 {
michael@0 1309 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1310 }
michael@0 1311
michael@0 1312 virtual ~GetPlaceInfo()
michael@0 1313 {
michael@0 1314 if (mCallback) {
michael@0 1315 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 1316 (void)NS_ProxyRelease(mainThread, mCallback, true);
michael@0 1317 }
michael@0 1318 }
michael@0 1319
michael@0 1320 VisitData mPlace;
michael@0 1321 nsCOMPtr<mozIVisitInfoCallback> mCallback;
michael@0 1322 nsRefPtr<History> mHistory;
michael@0 1323 };
michael@0 1324
michael@0 1325 /**
michael@0 1326 * Sets the page title for a page in moz_places (if necessary).
michael@0 1327 */
michael@0 1328 class SetPageTitle : public nsRunnable
michael@0 1329 {
michael@0 1330 public:
michael@0 1331 /**
michael@0 1332 * Sets a pages title in the database asynchronously.
michael@0 1333 *
michael@0 1334 * @param aConnection
michael@0 1335 * The database connection to use for this operation.
michael@0 1336 * @param aURI
michael@0 1337 * The URI to set the page title on.
michael@0 1338 * @param aTitle
michael@0 1339 * The title to set for the page, if the page exists.
michael@0 1340 */
michael@0 1341 static nsresult Start(mozIStorageConnection* aConnection,
michael@0 1342 nsIURI* aURI,
michael@0 1343 const nsAString& aTitle)
michael@0 1344 {
michael@0 1345 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1346 MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
michael@0 1347
michael@0 1348 nsCString spec;
michael@0 1349 nsresult rv = aURI->GetSpec(spec);
michael@0 1350 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1351
michael@0 1352 nsRefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
michael@0 1353
michael@0 1354 // Get the target thread, and then start the work!
michael@0 1355 nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
michael@0 1356 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
michael@0 1357 rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 1358 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1359
michael@0 1360 return NS_OK;
michael@0 1361 }
michael@0 1362
michael@0 1363 NS_IMETHOD Run()
michael@0 1364 {
michael@0 1365 MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
michael@0 1366
michael@0 1367 // First, see if the page exists in the database (we'll need its id later).
michael@0 1368 bool exists;
michael@0 1369 nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
michael@0 1370 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1371
michael@0 1372 if (!exists || !mPlace.titleChanged) {
michael@0 1373 // We have no record of this page, or we have no title change, so there
michael@0 1374 // is no need to do any further work.
michael@0 1375 return NS_OK;
michael@0 1376 }
michael@0 1377
michael@0 1378 NS_ASSERTION(mPlace.placeId > 0,
michael@0 1379 "We somehow have an invalid place id here!");
michael@0 1380
michael@0 1381 // Now we can update our database record.
michael@0 1382 nsCOMPtr<mozIStorageStatement> stmt =
michael@0 1383 mHistory->GetStatement(
michael@0 1384 "UPDATE moz_places "
michael@0 1385 "SET title = :page_title "
michael@0 1386 "WHERE id = :page_id "
michael@0 1387 );
michael@0 1388 NS_ENSURE_STATE(stmt);
michael@0 1389
michael@0 1390 {
michael@0 1391 mozStorageStatementScoper scoper(stmt);
michael@0 1392 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
michael@0 1393 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1394 // Empty strings should clear the title, just like
michael@0 1395 // nsNavHistory::SetPageTitle.
michael@0 1396 if (mPlace.title.IsEmpty()) {
michael@0 1397 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
michael@0 1398 }
michael@0 1399 else {
michael@0 1400 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
michael@0 1401 StringHead(mPlace.title, TITLE_LENGTH_MAX));
michael@0 1402 }
michael@0 1403 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1404 rv = stmt->Execute();
michael@0 1405 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1406 }
michael@0 1407
michael@0 1408 nsCOMPtr<nsIRunnable> event =
michael@0 1409 new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
michael@0 1410 rv = NS_DispatchToMainThread(event);
michael@0 1411 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1412
michael@0 1413 return NS_OK;
michael@0 1414 }
michael@0 1415
michael@0 1416 private:
michael@0 1417 SetPageTitle(const nsCString& aSpec,
michael@0 1418 const nsAString& aTitle)
michael@0 1419 : mHistory(History::GetService())
michael@0 1420 {
michael@0 1421 mPlace.spec = aSpec;
michael@0 1422 mPlace.title = aTitle;
michael@0 1423 }
michael@0 1424
michael@0 1425 VisitData mPlace;
michael@0 1426
michael@0 1427 /**
michael@0 1428 * Strong reference to the History object because we do not want it to
michael@0 1429 * disappear out from under us.
michael@0 1430 */
michael@0 1431 nsRefPtr<History> mHistory;
michael@0 1432 };
michael@0 1433
michael@0 1434 /**
michael@0 1435 * Adds download-specific annotations to a download page.
michael@0 1436 */
michael@0 1437 class SetDownloadAnnotations MOZ_FINAL : public mozIVisitInfoCallback
michael@0 1438 {
michael@0 1439 public:
michael@0 1440 NS_DECL_ISUPPORTS
michael@0 1441
michael@0 1442 SetDownloadAnnotations(nsIURI* aDestination)
michael@0 1443 : mDestination(aDestination)
michael@0 1444 , mHistory(History::GetService())
michael@0 1445 {
michael@0 1446 MOZ_ASSERT(mDestination);
michael@0 1447 MOZ_ASSERT(NS_IsMainThread());
michael@0 1448 }
michael@0 1449
michael@0 1450 NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo)
michael@0 1451 {
michael@0 1452 // Just don't add the annotations in case the visit isn't added.
michael@0 1453 return NS_OK;
michael@0 1454 }
michael@0 1455
michael@0 1456 NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo)
michael@0 1457 {
michael@0 1458 // Exit silently if the download destination is not a local file.
michael@0 1459 nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination);
michael@0 1460 if (!destinationFileURL) {
michael@0 1461 return NS_OK;
michael@0 1462 }
michael@0 1463
michael@0 1464 nsCOMPtr<nsIURI> source;
michael@0 1465 nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source));
michael@0 1466 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1467
michael@0 1468 nsCOMPtr<nsIFile> destinationFile;
michael@0 1469 rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile));
michael@0 1470 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1471
michael@0 1472 nsAutoString destinationFileName;
michael@0 1473 rv = destinationFile->GetLeafName(destinationFileName);
michael@0 1474 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1475
michael@0 1476 nsAutoCString destinationURISpec;
michael@0 1477 rv = destinationFileURL->GetSpec(destinationURISpec);
michael@0 1478 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1479
michael@0 1480 // Use annotations for storing the additional download metadata.
michael@0 1481 nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
michael@0 1482 NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
michael@0 1483
michael@0 1484 rv = annosvc->SetPageAnnotationString(
michael@0 1485 source,
michael@0 1486 DESTINATIONFILEURI_ANNO,
michael@0 1487 NS_ConvertUTF8toUTF16(destinationURISpec),
michael@0 1488 0,
michael@0 1489 nsIAnnotationService::EXPIRE_WITH_HISTORY
michael@0 1490 );
michael@0 1491 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1492
michael@0 1493 rv = annosvc->SetPageAnnotationString(
michael@0 1494 source,
michael@0 1495 DESTINATIONFILENAME_ANNO,
michael@0 1496 destinationFileName,
michael@0 1497 0,
michael@0 1498 nsIAnnotationService::EXPIRE_WITH_HISTORY
michael@0 1499 );
michael@0 1500 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1501
michael@0 1502 nsAutoString title;
michael@0 1503 rv = aPlaceInfo->GetTitle(title);
michael@0 1504 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1505
michael@0 1506 // In case we are downloading a file that does not correspond to a web
michael@0 1507 // page for which the title is present, we populate the otherwise empty
michael@0 1508 // history title with the name of the destination file, to allow it to be
michael@0 1509 // visible and searchable in history results.
michael@0 1510 if (title.IsEmpty()) {
michael@0 1511 rv = mHistory->SetURITitle(source, destinationFileName);
michael@0 1512 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1513 }
michael@0 1514
michael@0 1515 return NS_OK;
michael@0 1516 }
michael@0 1517
michael@0 1518 NS_IMETHOD HandleCompletion()
michael@0 1519 {
michael@0 1520 return NS_OK;
michael@0 1521 }
michael@0 1522
michael@0 1523 private:
michael@0 1524 nsCOMPtr<nsIURI> mDestination;
michael@0 1525
michael@0 1526 /**
michael@0 1527 * Strong reference to the History object because we do not want it to
michael@0 1528 * disappear out from under us.
michael@0 1529 */
michael@0 1530 nsRefPtr<History> mHistory;
michael@0 1531 };
michael@0 1532 NS_IMPL_ISUPPORTS(
michael@0 1533 SetDownloadAnnotations,
michael@0 1534 mozIVisitInfoCallback
michael@0 1535 )
michael@0 1536
michael@0 1537 /**
michael@0 1538 * Enumerator used by NotifyRemoveVisits to transfer the hash entries.
michael@0 1539 */
michael@0 1540 static PLDHashOperator TransferHashEntries(PlaceHashKey* aEntry,
michael@0 1541 void* aHash)
michael@0 1542 {
michael@0 1543 nsTHashtable<PlaceHashKey>* hash =
michael@0 1544 static_cast<nsTHashtable<PlaceHashKey> *>(aHash);
michael@0 1545 PlaceHashKey* copy = hash->PutEntry(aEntry->GetKey());
michael@0 1546 copy->visitCount = aEntry->visitCount;
michael@0 1547 copy->bookmarked = aEntry->bookmarked;
michael@0 1548 aEntry->visits.SwapElements(copy->visits);
michael@0 1549 return PL_DHASH_NEXT;
michael@0 1550 }
michael@0 1551
michael@0 1552 /**
michael@0 1553 * Enumerator used by NotifyRemoveVisits to notify removals.
michael@0 1554 */
michael@0 1555 static PLDHashOperator NotifyVisitRemoval(PlaceHashKey* aEntry,
michael@0 1556 void* aHistory)
michael@0 1557 {
michael@0 1558 nsNavHistory* history = static_cast<nsNavHistory *>(aHistory);
michael@0 1559 const nsTArray<VisitData>& visits = aEntry->visits;
michael@0 1560 nsCOMPtr<nsIURI> uri;
michael@0 1561 (void)NS_NewURI(getter_AddRefs(uri), visits[0].spec);
michael@0 1562 bool removingPage = visits.Length() == aEntry->visitCount &&
michael@0 1563 !aEntry->bookmarked;
michael@0 1564 // FindRemovableVisits only sets the transition type on the VisitData objects
michael@0 1565 // it collects if the visits were filtered by transition type.
michael@0 1566 // RemoveVisitsFilter currently only supports filtering by transition type, so
michael@0 1567 // FindRemovableVisits will either find all visits, or all visits of a given
michael@0 1568 // type. Therefore, if transitionType is set on this visit, we pass the
michael@0 1569 // transition type to NotifyOnPageExpired which in turns passes it to
michael@0 1570 // OnDeleteVisits to indicate that all visits of a given type were removed.
michael@0 1571 uint32_t transition = visits[0].transitionType < UINT32_MAX ?
michael@0 1572 visits[0].transitionType : 0;
michael@0 1573 history->NotifyOnPageExpired(uri, visits[0].visitTime, removingPage,
michael@0 1574 visits[0].guid,
michael@0 1575 nsINavHistoryObserver::REASON_DELETED,
michael@0 1576 transition);
michael@0 1577 return PL_DHASH_NEXT;
michael@0 1578 }
michael@0 1579
michael@0 1580 /**
michael@0 1581 * Notify removed visits to observers.
michael@0 1582 */
michael@0 1583 class NotifyRemoveVisits : public nsRunnable
michael@0 1584 {
michael@0 1585 public:
michael@0 1586
michael@0 1587 NotifyRemoveVisits(nsTHashtable<PlaceHashKey>& aPlaces)
michael@0 1588 : mPlaces(VISITS_REMOVAL_INITIAL_HASH_SIZE)
michael@0 1589 , mHistory(History::GetService())
michael@0 1590 {
michael@0 1591 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 1592 "This should not be called on the main thread");
michael@0 1593 aPlaces.EnumerateEntries(TransferHashEntries, &mPlaces);
michael@0 1594 }
michael@0 1595
michael@0 1596 NS_IMETHOD Run()
michael@0 1597 {
michael@0 1598 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1599
michael@0 1600 // We are in the main thread, no need to lock.
michael@0 1601 if (mHistory->IsShuttingDown()) {
michael@0 1602 // If we are shutting down, we cannot notify the observers.
michael@0 1603 return NS_OK;
michael@0 1604 }
michael@0 1605
michael@0 1606 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 1607 if (!navHistory) {
michael@0 1608 NS_WARNING("Cannot notify without the history service!");
michael@0 1609 return NS_OK;
michael@0 1610 }
michael@0 1611
michael@0 1612 // Wrap all notifications in a batch, so the view can handle changes in a
michael@0 1613 // more performant way, by initiating a refresh after a limited number of
michael@0 1614 // single changes.
michael@0 1615 (void)navHistory->BeginUpdateBatch();
michael@0 1616 mPlaces.EnumerateEntries(NotifyVisitRemoval, navHistory);
michael@0 1617 (void)navHistory->EndUpdateBatch();
michael@0 1618
michael@0 1619 return NS_OK;
michael@0 1620 }
michael@0 1621
michael@0 1622 private:
michael@0 1623 nsTHashtable<PlaceHashKey> mPlaces;
michael@0 1624
michael@0 1625 /**
michael@0 1626 * Strong reference to the History object because we do not want it to
michael@0 1627 * disappear out from under us.
michael@0 1628 */
michael@0 1629 nsRefPtr<History> mHistory;
michael@0 1630 };
michael@0 1631
michael@0 1632 /**
michael@0 1633 * Enumerator used by RemoveVisits to populate list of removed place ids.
michael@0 1634 */
michael@0 1635 static PLDHashOperator ListToBeRemovedPlaceIds(PlaceHashKey* aEntry,
michael@0 1636 void* aIdsList)
michael@0 1637 {
michael@0 1638 const nsTArray<VisitData>& visits = aEntry->visits;
michael@0 1639 // Only orphan ids should be listed.
michael@0 1640 if (visits.Length() == aEntry->visitCount && !aEntry->bookmarked) {
michael@0 1641 nsCString* list = static_cast<nsCString*>(aIdsList);
michael@0 1642 if (!list->IsEmpty())
michael@0 1643 list->AppendLiteral(",");
michael@0 1644 list->AppendInt(visits[0].placeId);
michael@0 1645 }
michael@0 1646 return PL_DHASH_NEXT;
michael@0 1647 }
michael@0 1648
michael@0 1649 /**
michael@0 1650 * Remove visits from history.
michael@0 1651 */
michael@0 1652 class RemoveVisits : public nsRunnable
michael@0 1653 {
michael@0 1654 public:
michael@0 1655 /**
michael@0 1656 * Asynchronously removes visits from history.
michael@0 1657 *
michael@0 1658 * @param aConnection
michael@0 1659 * The database connection to use for these operations.
michael@0 1660 * @param aFilter
michael@0 1661 * Filter to remove visits.
michael@0 1662 */
michael@0 1663 static nsresult Start(mozIStorageConnection* aConnection,
michael@0 1664 RemoveVisitsFilter& aFilter)
michael@0 1665 {
michael@0 1666 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1667
michael@0 1668 nsRefPtr<RemoveVisits> event = new RemoveVisits(aConnection, aFilter);
michael@0 1669
michael@0 1670 // Get the target thread, and then start the work!
michael@0 1671 nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
michael@0 1672 NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
michael@0 1673 nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 1674 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1675
michael@0 1676 return NS_OK;
michael@0 1677 }
michael@0 1678
michael@0 1679 NS_IMETHOD Run()
michael@0 1680 {
michael@0 1681 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 1682 "This should not be called on the main thread");
michael@0 1683
michael@0 1684 // Prevent the main thread from shutting down while this is running.
michael@0 1685 MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
michael@0 1686 if (mHistory->IsShuttingDown()) {
michael@0 1687 // If we were already shutting down, we cannot remove the visits.
michael@0 1688 return NS_OK;
michael@0 1689 }
michael@0 1690
michael@0 1691 // Find all the visits relative to the current filters and whether their
michael@0 1692 // pages will be removed or not.
michael@0 1693 nsTHashtable<PlaceHashKey> places(VISITS_REMOVAL_INITIAL_HASH_SIZE);
michael@0 1694 nsresult rv = FindRemovableVisits(places);
michael@0 1695 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1696
michael@0 1697 if (places.Count() == 0)
michael@0 1698 return NS_OK;
michael@0 1699
michael@0 1700 mozStorageTransaction transaction(mDBConn, false,
michael@0 1701 mozIStorageConnection::TRANSACTION_IMMEDIATE);
michael@0 1702
michael@0 1703 rv = RemoveVisitsFromDatabase();
michael@0 1704 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1705 rv = RemovePagesFromDatabase(places);
michael@0 1706 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1707
michael@0 1708 rv = transaction.Commit();
michael@0 1709 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1710
michael@0 1711 nsCOMPtr<nsIRunnable> event = new NotifyRemoveVisits(places);
michael@0 1712 rv = NS_DispatchToMainThread(event);
michael@0 1713 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1714
michael@0 1715 return NS_OK;
michael@0 1716 }
michael@0 1717
michael@0 1718 private:
michael@0 1719 RemoveVisits(mozIStorageConnection* aConnection,
michael@0 1720 RemoveVisitsFilter& aFilter)
michael@0 1721 : mDBConn(aConnection)
michael@0 1722 , mHasTransitionType(false)
michael@0 1723 , mHistory(History::GetService())
michael@0 1724 {
michael@0 1725 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
michael@0 1726
michael@0 1727 // Build query conditions.
michael@0 1728 nsTArray<nsCString> conditions;
michael@0 1729 // TODO: add support for binding params when adding further stuff here.
michael@0 1730 if (aFilter.transitionType < UINT32_MAX) {
michael@0 1731 conditions.AppendElement(nsPrintfCString("visit_type = %d", aFilter.transitionType));
michael@0 1732 mHasTransitionType = true;
michael@0 1733 }
michael@0 1734 if (conditions.Length() > 0) {
michael@0 1735 mWhereClause.AppendLiteral (" WHERE ");
michael@0 1736 for (uint32_t i = 0; i < conditions.Length(); ++i) {
michael@0 1737 if (i > 0)
michael@0 1738 mWhereClause.AppendLiteral(" AND ");
michael@0 1739 mWhereClause.Append(conditions[i]);
michael@0 1740 }
michael@0 1741 }
michael@0 1742 }
michael@0 1743
michael@0 1744 nsresult
michael@0 1745 FindRemovableVisits(nsTHashtable<PlaceHashKey>& aPlaces)
michael@0 1746 {
michael@0 1747 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 1748 "This should not be called on the main thread");
michael@0 1749
michael@0 1750 nsCString query("SELECT h.id, url, guid, visit_date, visit_type, "
michael@0 1751 "(SELECT count(*) FROM moz_historyvisits WHERE place_id = h.id) as full_visit_count, "
michael@0 1752 "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) as bookmarked "
michael@0 1753 "FROM moz_historyvisits "
michael@0 1754 "JOIN moz_places h ON place_id = h.id");
michael@0 1755 query.Append(mWhereClause);
michael@0 1756
michael@0 1757 nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
michael@0 1758 NS_ENSURE_STATE(stmt);
michael@0 1759 mozStorageStatementScoper scoper(stmt);
michael@0 1760
michael@0 1761 bool hasResult;
michael@0 1762 nsresult rv;
michael@0 1763 while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
michael@0 1764 VisitData visit;
michael@0 1765 rv = stmt->GetInt64(0, &visit.placeId);
michael@0 1766 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1767 rv = stmt->GetUTF8String(1, visit.spec);
michael@0 1768 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1769 rv = stmt->GetUTF8String(2, visit.guid);
michael@0 1770 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1771 rv = stmt->GetInt64(3, &visit.visitTime);
michael@0 1772 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1773 if (mHasTransitionType) {
michael@0 1774 int32_t transition;
michael@0 1775 rv = stmt->GetInt32(4, &transition);
michael@0 1776 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1777 visit.transitionType = static_cast<uint32_t>(transition);
michael@0 1778 }
michael@0 1779 int32_t visitCount, bookmarked;
michael@0 1780 rv = stmt->GetInt32(5, &visitCount);
michael@0 1781 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1782 rv = stmt->GetInt32(6, &bookmarked);
michael@0 1783 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1784
michael@0 1785 PlaceHashKey* entry = aPlaces.GetEntry(visit.spec);
michael@0 1786 if (!entry) {
michael@0 1787 entry = aPlaces.PutEntry(visit.spec);
michael@0 1788 }
michael@0 1789 entry->visitCount = visitCount;
michael@0 1790 entry->bookmarked = bookmarked;
michael@0 1791 entry->visits.AppendElement(visit);
michael@0 1792 }
michael@0 1793 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1794
michael@0 1795 return NS_OK;
michael@0 1796 }
michael@0 1797
michael@0 1798 nsresult
michael@0 1799 RemoveVisitsFromDatabase()
michael@0 1800 {
michael@0 1801 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 1802 "This should not be called on the main thread");
michael@0 1803
michael@0 1804 nsCString query("DELETE FROM moz_historyvisits");
michael@0 1805 query.Append(mWhereClause);
michael@0 1806
michael@0 1807 nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
michael@0 1808 NS_ENSURE_STATE(stmt);
michael@0 1809 mozStorageStatementScoper scoper(stmt);
michael@0 1810 nsresult rv = stmt->Execute();
michael@0 1811 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1812
michael@0 1813 return NS_OK;
michael@0 1814 }
michael@0 1815
michael@0 1816 nsresult
michael@0 1817 RemovePagesFromDatabase(nsTHashtable<PlaceHashKey>& aPlaces)
michael@0 1818 {
michael@0 1819 MOZ_ASSERT(!NS_IsMainThread(),
michael@0 1820 "This should not be called on the main thread");
michael@0 1821
michael@0 1822 nsCString placeIdsToRemove;
michael@0 1823 aPlaces.EnumerateEntries(ListToBeRemovedPlaceIds, &placeIdsToRemove);
michael@0 1824
michael@0 1825 #ifdef DEBUG
michael@0 1826 {
michael@0 1827 // Ensure that we are not removing any problematic entry.
michael@0 1828 nsCString query("SELECT id FROM moz_places h WHERE id IN (");
michael@0 1829 query.Append(placeIdsToRemove);
michael@0 1830 query.AppendLiteral(") AND ("
michael@0 1831 "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) OR "
michael@0 1832 "EXISTS(SELECT 1 FROM moz_historyvisits WHERE place_id = h.id) OR "
michael@0 1833 "SUBSTR(h.url, 1, 6) = 'place:' "
michael@0 1834 ")");
michael@0 1835 nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
michael@0 1836 NS_ENSURE_STATE(stmt);
michael@0 1837 mozStorageStatementScoper scoper(stmt);
michael@0 1838 bool hasResult;
michael@0 1839 MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult,
michael@0 1840 "Trying to remove a non-oprhan place from the database");
michael@0 1841 }
michael@0 1842 #endif
michael@0 1843
michael@0 1844 nsCString query("DELETE FROM moz_places "
michael@0 1845 "WHERE id IN (");
michael@0 1846 query.Append(placeIdsToRemove);
michael@0 1847 query.AppendLiteral(")");
michael@0 1848
michael@0 1849 nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
michael@0 1850 NS_ENSURE_STATE(stmt);
michael@0 1851 mozStorageStatementScoper scoper(stmt);
michael@0 1852 nsresult rv = stmt->Execute();
michael@0 1853 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1854
michael@0 1855 return NS_OK;
michael@0 1856 }
michael@0 1857
michael@0 1858 mozIStorageConnection* mDBConn;
michael@0 1859 bool mHasTransitionType;
michael@0 1860 nsCString mWhereClause;
michael@0 1861
michael@0 1862 /**
michael@0 1863 * Strong reference to the History object because we do not want it to
michael@0 1864 * disappear out from under us.
michael@0 1865 */
michael@0 1866 nsRefPtr<History> mHistory;
michael@0 1867 };
michael@0 1868
michael@0 1869 /**
michael@0 1870 * Stores an embed visit, and notifies observers.
michael@0 1871 *
michael@0 1872 * @param aPlace
michael@0 1873 * The VisitData of the visit to store as an embed visit.
michael@0 1874 * @param [optional] aCallback
michael@0 1875 * The mozIVisitInfoCallback to notify, if provided.
michael@0 1876 */
michael@0 1877 void
michael@0 1878 StoreAndNotifyEmbedVisit(VisitData& aPlace,
michael@0 1879 mozIVisitInfoCallback* aCallback = nullptr)
michael@0 1880 {
michael@0 1881 MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
michael@0 1882 "Must only pass TRANSITION_EMBED visits to this!");
michael@0 1883 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
michael@0 1884
michael@0 1885 nsCOMPtr<nsIURI> uri;
michael@0 1886 (void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
michael@0 1887
michael@0 1888 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 1889 if (!navHistory || !uri) {
michael@0 1890 return;
michael@0 1891 }
michael@0 1892
michael@0 1893 navHistory->registerEmbedVisit(uri, aPlace.visitTime);
michael@0 1894
michael@0 1895 if (aCallback) {
michael@0 1896 // NotifyPlaceInfoCallback does not hold a strong reference to the callback,
michael@0 1897 // so we have to manage it by AddRefing now and then releasing it after the
michael@0 1898 // event has run.
michael@0 1899 NS_ADDREF(aCallback);
michael@0 1900 nsCOMPtr<nsIRunnable> event =
michael@0 1901 new NotifyPlaceInfoCallback(aCallback, aPlace, true, NS_OK);
michael@0 1902 (void)NS_DispatchToMainThread(event);
michael@0 1903
michael@0 1904 // Also dispatch an event to release our reference to the callback after
michael@0 1905 // NotifyPlaceInfoCallback has run.
michael@0 1906 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
michael@0 1907 (void)NS_ProxyRelease(mainThread, aCallback, true);
michael@0 1908 }
michael@0 1909
michael@0 1910 VisitData noReferrer;
michael@0 1911 nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
michael@0 1912 (void)NS_DispatchToMainThread(event);
michael@0 1913 }
michael@0 1914
michael@0 1915 } // anonymous namespace
michael@0 1916
michael@0 1917 ////////////////////////////////////////////////////////////////////////////////
michael@0 1918 //// History
michael@0 1919
michael@0 1920 History* History::gService = nullptr;
michael@0 1921
michael@0 1922 History::History()
michael@0 1923 : mShuttingDown(false)
michael@0 1924 , mShutdownMutex("History::mShutdownMutex")
michael@0 1925 , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_SIZE)
michael@0 1926 , mRecentlyVisitedURIsNextIndex(0)
michael@0 1927 {
michael@0 1928 NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!");
michael@0 1929 gService = this;
michael@0 1930
michael@0 1931 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
michael@0 1932 NS_WARN_IF_FALSE(os, "Observer service was not found!");
michael@0 1933 if (os) {
michael@0 1934 (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
michael@0 1935 }
michael@0 1936 }
michael@0 1937
michael@0 1938 History::~History()
michael@0 1939 {
michael@0 1940 UnregisterWeakMemoryReporter(this);
michael@0 1941
michael@0 1942 gService = nullptr;
michael@0 1943
michael@0 1944 NS_ASSERTION(mObservers.Count() == 0,
michael@0 1945 "Not all Links were removed before we disappear!");
michael@0 1946 }
michael@0 1947
michael@0 1948 void
michael@0 1949 History::InitMemoryReporter()
michael@0 1950 {
michael@0 1951 RegisterWeakMemoryReporter(this);
michael@0 1952 }
michael@0 1953
michael@0 1954 NS_IMETHODIMP
michael@0 1955 History::NotifyVisited(nsIURI* aURI)
michael@0 1956 {
michael@0 1957 NS_ENSURE_ARG(aURI);
michael@0 1958
michael@0 1959 nsAutoScriptBlocker scriptBlocker;
michael@0 1960
michael@0 1961 if (XRE_GetProcessType() == GeckoProcessType_Default) {
michael@0 1962 nsTArray<ContentParent*> cplist;
michael@0 1963 ContentParent::GetAll(cplist);
michael@0 1964
michael@0 1965 if (!cplist.IsEmpty()) {
michael@0 1966 URIParams uri;
michael@0 1967 SerializeURI(aURI, uri);
michael@0 1968 for (uint32_t i = 0; i < cplist.Length(); ++i) {
michael@0 1969 unused << cplist[i]->SendNotifyVisited(uri);
michael@0 1970 }
michael@0 1971 }
michael@0 1972 }
michael@0 1973
michael@0 1974 // If we have no observers for this URI, we have nothing to notify about.
michael@0 1975 KeyClass* key = mObservers.GetEntry(aURI);
michael@0 1976 if (!key) {
michael@0 1977 return NS_OK;
michael@0 1978 }
michael@0 1979
michael@0 1980 // Update status of each Link node.
michael@0 1981 {
michael@0 1982 // RemoveEntry will destroy the array, this iterator should not survive it.
michael@0 1983 ObserverArray::ForwardIterator iter(key->array);
michael@0 1984 while (iter.HasMore()) {
michael@0 1985 Link* link = iter.GetNext();
michael@0 1986 link->SetLinkState(eLinkState_Visited);
michael@0 1987 // Verify that the observers hash doesn't mutate while looping through
michael@0 1988 // the links associated with this URI.
michael@0 1989 NS_ABORT_IF_FALSE(key == mObservers.GetEntry(aURI),
michael@0 1990 "The URIs hash mutated!");
michael@0 1991 }
michael@0 1992 }
michael@0 1993
michael@0 1994 // All the registered nodes can now be removed for this URI.
michael@0 1995 mObservers.RemoveEntry(aURI);
michael@0 1996 return NS_OK;
michael@0 1997 }
michael@0 1998
michael@0 1999 mozIStorageAsyncStatement*
michael@0 2000 History::GetIsVisitedStatement()
michael@0 2001 {
michael@0 2002 if (mIsVisitedStatement) {
michael@0 2003 return mIsVisitedStatement;
michael@0 2004 }
michael@0 2005
michael@0 2006 // If we don't yet have a database connection, go ahead and clone it now.
michael@0 2007 if (!mReadOnlyDBConn) {
michael@0 2008 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2009 NS_ENSURE_TRUE(dbConn, nullptr);
michael@0 2010
michael@0 2011 (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
michael@0 2012 NS_ENSURE_TRUE(mReadOnlyDBConn, nullptr);
michael@0 2013 }
michael@0 2014
michael@0 2015 // Now we can create our cached statement.
michael@0 2016 nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
michael@0 2017 "SELECT 1 "
michael@0 2018 "FROM moz_places h "
michael@0 2019 "WHERE url = ?1 "
michael@0 2020 "AND last_visit_date NOTNULL "
michael@0 2021 ), getter_AddRefs(mIsVisitedStatement));
michael@0 2022 NS_ENSURE_SUCCESS(rv, nullptr);
michael@0 2023 return mIsVisitedStatement;
michael@0 2024 }
michael@0 2025
michael@0 2026 nsresult
michael@0 2027 History::InsertPlace(const VisitData& aPlace)
michael@0 2028 {
michael@0 2029 NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
michael@0 2030 NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
michael@0 2031
michael@0 2032 nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
michael@0 2033 "INSERT INTO moz_places "
michael@0 2034 "(url, title, rev_host, hidden, typed, frecency, guid) "
michael@0 2035 "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) "
michael@0 2036 );
michael@0 2037 NS_ENSURE_STATE(stmt);
michael@0 2038 mozStorageStatementScoper scoper(stmt);
michael@0 2039
michael@0 2040 nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
michael@0 2041 aPlace.revHost);
michael@0 2042 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2043 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
michael@0 2044 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2045 nsString title = aPlace.title;
michael@0 2046 // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
michael@0 2047 if (title.IsEmpty()) {
michael@0 2048 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
michael@0 2049 }
michael@0 2050 else {
michael@0 2051 title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
michael@0 2052 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
michael@0 2053 }
michael@0 2054 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2055 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
michael@0 2056 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2057 // When inserting a page for a first visit that should not appear in
michael@0 2058 // autocomplete, for example an error page, use a zero frecency.
michael@0 2059 int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
michael@0 2060 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
michael@0 2061 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2062 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
michael@0 2063 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2064 nsAutoCString guid(aPlace.guid);
michael@0 2065 if (aPlace.guid.IsVoid()) {
michael@0 2066 rv = GenerateGUID(guid);
michael@0 2067 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2068 }
michael@0 2069 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
michael@0 2070 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2071 rv = stmt->Execute();
michael@0 2072 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2073
michael@0 2074 // Post an onFrecencyChanged observer notification.
michael@0 2075 const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
michael@0 2076 NS_ENSURE_STATE(navHistory);
michael@0 2077 navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency, guid,
michael@0 2078 aPlace.hidden,
michael@0 2079 aPlace.visitTime);
michael@0 2080
michael@0 2081 return NS_OK;
michael@0 2082 }
michael@0 2083
michael@0 2084 nsresult
michael@0 2085 History::UpdatePlace(const VisitData& aPlace)
michael@0 2086 {
michael@0 2087 NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
michael@0 2088 NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
michael@0 2089 NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
michael@0 2090
michael@0 2091 nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
michael@0 2092 "UPDATE moz_places "
michael@0 2093 "SET title = :title, "
michael@0 2094 "hidden = :hidden, "
michael@0 2095 "typed = :typed, "
michael@0 2096 "guid = :guid "
michael@0 2097 "WHERE id = :page_id "
michael@0 2098 );
michael@0 2099 NS_ENSURE_STATE(stmt);
michael@0 2100 mozStorageStatementScoper scoper(stmt);
michael@0 2101
michael@0 2102 nsresult rv;
michael@0 2103 // Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
michael@0 2104 if (aPlace.title.IsEmpty()) {
michael@0 2105 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
michael@0 2106 }
michael@0 2107 else {
michael@0 2108 rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
michael@0 2109 StringHead(aPlace.title, TITLE_LENGTH_MAX));
michael@0 2110 }
michael@0 2111 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2112 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
michael@0 2113 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2114 rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
michael@0 2115 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2116 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
michael@0 2117 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2118 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
michael@0 2119 aPlace.placeId);
michael@0 2120 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2121 rv = stmt->Execute();
michael@0 2122 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2123
michael@0 2124 return NS_OK;
michael@0 2125 }
michael@0 2126
michael@0 2127 nsresult
michael@0 2128 History::FetchPageInfo(VisitData& _place, bool* _exists)
michael@0 2129 {
michael@0 2130 NS_PRECONDITION(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
michael@0 2131 NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
michael@0 2132
michael@0 2133 nsresult rv;
michael@0 2134
michael@0 2135 // URI takes precedence.
michael@0 2136 nsCOMPtr<mozIStorageStatement> stmt;
michael@0 2137 bool selectByURI = !_place.spec.IsEmpty();
michael@0 2138 if (selectByURI) {
michael@0 2139 stmt = GetStatement(
michael@0 2140 "SELECT guid, id, title, hidden, typed, frecency "
michael@0 2141 "FROM moz_places "
michael@0 2142 "WHERE url = :page_url "
michael@0 2143 );
michael@0 2144 NS_ENSURE_STATE(stmt);
michael@0 2145
michael@0 2146 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
michael@0 2147 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2148 }
michael@0 2149 else {
michael@0 2150 stmt = GetStatement(
michael@0 2151 "SELECT url, id, title, hidden, typed, frecency "
michael@0 2152 "FROM moz_places "
michael@0 2153 "WHERE guid = :guid "
michael@0 2154 );
michael@0 2155 NS_ENSURE_STATE(stmt);
michael@0 2156
michael@0 2157 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
michael@0 2158 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2159 }
michael@0 2160
michael@0 2161 mozStorageStatementScoper scoper(stmt);
michael@0 2162
michael@0 2163 rv = stmt->ExecuteStep(_exists);
michael@0 2164 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2165
michael@0 2166 if (!*_exists) {
michael@0 2167 return NS_OK;
michael@0 2168 }
michael@0 2169
michael@0 2170 if (selectByURI) {
michael@0 2171 if (_place.guid.IsEmpty()) {
michael@0 2172 rv = stmt->GetUTF8String(0, _place.guid);
michael@0 2173 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2174 }
michael@0 2175 }
michael@0 2176 else {
michael@0 2177 nsAutoCString spec;
michael@0 2178 rv = stmt->GetUTF8String(0, spec);
michael@0 2179 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2180 _place.spec = spec;
michael@0 2181 }
michael@0 2182
michael@0 2183 rv = stmt->GetInt64(1, &_place.placeId);
michael@0 2184 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2185
michael@0 2186 nsAutoString title;
michael@0 2187 rv = stmt->GetString(2, title);
michael@0 2188 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2189
michael@0 2190 // If the title we were given was void, that means we did not bother to set
michael@0 2191 // it to anything. As a result, ignore the fact that we may have changed the
michael@0 2192 // title (because we don't want to, that would be empty), and set the title
michael@0 2193 // to what is currently stored in the datbase.
michael@0 2194 if (_place.title.IsVoid()) {
michael@0 2195 _place.title = title;
michael@0 2196 }
michael@0 2197 // Otherwise, just indicate if the title has changed.
michael@0 2198 else {
michael@0 2199 _place.titleChanged = !(_place.title.Equals(title) ||
michael@0 2200 (_place.title.IsEmpty() && title.IsVoid()));
michael@0 2201 }
michael@0 2202
michael@0 2203 if (_place.hidden) {
michael@0 2204 // If this transition was hidden, it is possible that others were not.
michael@0 2205 // Any one visible transition makes this location visible. If database
michael@0 2206 // has location as visible, reflect that in our data structure.
michael@0 2207 int32_t hidden;
michael@0 2208 rv = stmt->GetInt32(3, &hidden);
michael@0 2209 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2210 _place.hidden = !!hidden;
michael@0 2211 }
michael@0 2212
michael@0 2213 if (!_place.typed) {
michael@0 2214 // If this transition wasn't typed, others might have been. If database
michael@0 2215 // has location as typed, reflect that in our data structure.
michael@0 2216 int32_t typed;
michael@0 2217 rv = stmt->GetInt32(4, &typed);
michael@0 2218 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2219 _place.typed = !!typed;
michael@0 2220 }
michael@0 2221
michael@0 2222 rv = stmt->GetInt32(5, &_place.frecency);
michael@0 2223 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2224 return NS_OK;
michael@0 2225 }
michael@0 2226
michael@0 2227 /* static */ size_t
michael@0 2228 History::SizeOfEntryExcludingThis(KeyClass* aEntry, mozilla::MallocSizeOf aMallocSizeOf, void *)
michael@0 2229 {
michael@0 2230 return aEntry->array.SizeOfExcludingThis(aMallocSizeOf);
michael@0 2231 }
michael@0 2232
michael@0 2233 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
michael@0 2234
michael@0 2235 NS_IMETHODIMP
michael@0 2236 History::CollectReports(nsIHandleReportCallback* aHandleReport,
michael@0 2237 nsISupports* aData)
michael@0 2238 {
michael@0 2239 return MOZ_COLLECT_REPORT(
michael@0 2240 "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
michael@0 2241 SizeOfIncludingThis(HistoryMallocSizeOf),
michael@0 2242 "Memory used by the hashtable that records changes to the visited state "
michael@0 2243 "of links.");
michael@0 2244 }
michael@0 2245
michael@0 2246 size_t
michael@0 2247 History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis)
michael@0 2248 {
michael@0 2249 return aMallocSizeOfThis(this) +
michael@0 2250 mObservers.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOfThis);
michael@0 2251 }
michael@0 2252
michael@0 2253 /* static */
michael@0 2254 History*
michael@0 2255 History::GetService()
michael@0 2256 {
michael@0 2257 if (gService) {
michael@0 2258 return gService;
michael@0 2259 }
michael@0 2260
michael@0 2261 nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
michael@0 2262 NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!");
michael@0 2263 NS_ASSERTION(gService, "Our constructor was not run?!");
michael@0 2264
michael@0 2265 return gService;
michael@0 2266 }
michael@0 2267
michael@0 2268 /* static */
michael@0 2269 History*
michael@0 2270 History::GetSingleton()
michael@0 2271 {
michael@0 2272 if (!gService) {
michael@0 2273 gService = new History();
michael@0 2274 NS_ENSURE_TRUE(gService, nullptr);
michael@0 2275 gService->InitMemoryReporter();
michael@0 2276 }
michael@0 2277
michael@0 2278 NS_ADDREF(gService);
michael@0 2279 return gService;
michael@0 2280 }
michael@0 2281
michael@0 2282 mozIStorageConnection*
michael@0 2283 History::GetDBConn()
michael@0 2284 {
michael@0 2285 if (!mDB) {
michael@0 2286 mDB = Database::GetDatabase();
michael@0 2287 NS_ENSURE_TRUE(mDB, nullptr);
michael@0 2288 }
michael@0 2289 return mDB->MainConn();
michael@0 2290 }
michael@0 2291
michael@0 2292 void
michael@0 2293 History::Shutdown()
michael@0 2294 {
michael@0 2295 MOZ_ASSERT(NS_IsMainThread());
michael@0 2296
michael@0 2297 // Prevent other threads from scheduling uses of the DB while we mark
michael@0 2298 // ourselves as shutting down.
michael@0 2299 MutexAutoLock lockedScope(mShutdownMutex);
michael@0 2300 MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
michael@0 2301
michael@0 2302 mShuttingDown = true;
michael@0 2303
michael@0 2304 if (mReadOnlyDBConn) {
michael@0 2305 if (mIsVisitedStatement) {
michael@0 2306 (void)mIsVisitedStatement->Finalize();
michael@0 2307 }
michael@0 2308 (void)mReadOnlyDBConn->AsyncClose(nullptr);
michael@0 2309 }
michael@0 2310 }
michael@0 2311
michael@0 2312 void
michael@0 2313 History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
michael@0 2314 if (mRecentlyVisitedURIs.Length() < RECENTLY_VISITED_URI_SIZE) {
michael@0 2315 // Append a new element while the array is not full.
michael@0 2316 mRecentlyVisitedURIs.AppendElement(aURI);
michael@0 2317 } else {
michael@0 2318 // Otherwise, replace the oldest member.
michael@0 2319 mRecentlyVisitedURIsNextIndex %= RECENTLY_VISITED_URI_SIZE;
michael@0 2320 mRecentlyVisitedURIs.ElementAt(mRecentlyVisitedURIsNextIndex) = aURI;
michael@0 2321 mRecentlyVisitedURIsNextIndex++;
michael@0 2322 }
michael@0 2323 }
michael@0 2324
michael@0 2325 inline bool
michael@0 2326 History::IsRecentlyVisitedURI(nsIURI* aURI) {
michael@0 2327 bool equals = false;
michael@0 2328 RecentlyVisitedArray::index_type i;
michael@0 2329 for (i = 0; i < mRecentlyVisitedURIs.Length() && !equals; ++i) {
michael@0 2330 aURI->Equals(mRecentlyVisitedURIs.ElementAt(i), &equals);
michael@0 2331 }
michael@0 2332 return equals;
michael@0 2333 }
michael@0 2334
michael@0 2335 ////////////////////////////////////////////////////////////////////////////////
michael@0 2336 //// IHistory
michael@0 2337
michael@0 2338 NS_IMETHODIMP
michael@0 2339 History::VisitURI(nsIURI* aURI,
michael@0 2340 nsIURI* aLastVisitedURI,
michael@0 2341 uint32_t aFlags)
michael@0 2342 {
michael@0 2343 NS_PRECONDITION(aURI, "URI should not be NULL.");
michael@0 2344 if (mShuttingDown) {
michael@0 2345 return NS_OK;
michael@0 2346 }
michael@0 2347
michael@0 2348 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 2349 URIParams uri;
michael@0 2350 SerializeURI(aURI, uri);
michael@0 2351
michael@0 2352 OptionalURIParams lastVisitedURI;
michael@0 2353 SerializeURI(aLastVisitedURI, lastVisitedURI);
michael@0 2354
michael@0 2355 mozilla::dom::ContentChild* cpc =
michael@0 2356 mozilla::dom::ContentChild::GetSingleton();
michael@0 2357 NS_ASSERTION(cpc, "Content Protocol is NULL!");
michael@0 2358 (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags);
michael@0 2359 return NS_OK;
michael@0 2360 }
michael@0 2361
michael@0 2362 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 2363 NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
michael@0 2364
michael@0 2365 // Silently return if URI is something we shouldn't add to DB.
michael@0 2366 bool canAdd;
michael@0 2367 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
michael@0 2368 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2369 if (!canAdd) {
michael@0 2370 return NS_OK;
michael@0 2371 }
michael@0 2372
michael@0 2373 if (aLastVisitedURI) {
michael@0 2374 bool same;
michael@0 2375 rv = aURI->Equals(aLastVisitedURI, &same);
michael@0 2376 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2377 if (same && IsRecentlyVisitedURI(aURI)) {
michael@0 2378 // Do not save refresh visits if we have visited this URI recently.
michael@0 2379 return NS_OK;
michael@0 2380 }
michael@0 2381 }
michael@0 2382
michael@0 2383 nsTArray<VisitData> placeArray(1);
michael@0 2384 NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
michael@0 2385 NS_ERROR_OUT_OF_MEMORY);
michael@0 2386 VisitData& place = placeArray.ElementAt(0);
michael@0 2387 NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
michael@0 2388
michael@0 2389 place.visitTime = PR_Now();
michael@0 2390
michael@0 2391 // Assigns a type to the edge in the visit linked list. Each type will be
michael@0 2392 // considered differently when weighting the frecency of a location.
michael@0 2393 uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
michael@0 2394 bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
michael@0 2395
michael@0 2396 // Embed visits should never be added to the database, and the same is valid
michael@0 2397 // for redirects across frames.
michael@0 2398 // For the above reasoning non-toplevel transitions are handled at first.
michael@0 2399 // if the visit is toplevel or a non-toplevel followed link, then it can be
michael@0 2400 // handled as usual and stored on disk.
michael@0 2401
michael@0 2402 uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK;
michael@0 2403
michael@0 2404 if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
michael@0 2405 // A frame redirected to a new site without user interaction.
michael@0 2406 transitionType = nsINavHistoryService::TRANSITION_EMBED;
michael@0 2407 }
michael@0 2408 else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
michael@0 2409 transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
michael@0 2410 }
michael@0 2411 else if (aFlags & IHistory::REDIRECT_PERMANENT) {
michael@0 2412 transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
michael@0 2413 }
michael@0 2414 else if ((recentFlags & nsNavHistory::RECENT_TYPED) &&
michael@0 2415 !(aFlags & IHistory::UNRECOVERABLE_ERROR)) {
michael@0 2416 // Don't mark error pages as typed, even if they were actually typed by
michael@0 2417 // the user. This is useful to limit their score in autocomplete.
michael@0 2418 transitionType = nsINavHistoryService::TRANSITION_TYPED;
michael@0 2419 }
michael@0 2420 else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
michael@0 2421 transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
michael@0 2422 }
michael@0 2423 else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
michael@0 2424 // User activated a link in a frame.
michael@0 2425 transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
michael@0 2426 }
michael@0 2427
michael@0 2428 place.SetTransitionType(transitionType);
michael@0 2429 place.hidden = GetHiddenState(aFlags & IHistory::REDIRECT_SOURCE,
michael@0 2430 transitionType);
michael@0 2431
michael@0 2432 // Error pages should never be autocompleted.
michael@0 2433 if (aFlags & IHistory::UNRECOVERABLE_ERROR) {
michael@0 2434 place.shouldUpdateFrecency = false;
michael@0 2435 }
michael@0 2436
michael@0 2437 // EMBED visits are session-persistent and should not go through the database.
michael@0 2438 // They exist only to keep track of isVisited status during the session.
michael@0 2439 if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
michael@0 2440 StoreAndNotifyEmbedVisit(place);
michael@0 2441 }
michael@0 2442 else {
michael@0 2443 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2444 NS_ENSURE_STATE(dbConn);
michael@0 2445
michael@0 2446 rv = InsertVisitedURIs::Start(dbConn, placeArray);
michael@0 2447 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2448 }
michael@0 2449
michael@0 2450 // Finally, notify that we've been visited.
michael@0 2451 nsCOMPtr<nsIObserverService> obsService =
michael@0 2452 mozilla::services::GetObserverService();
michael@0 2453 if (obsService) {
michael@0 2454 obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
michael@0 2455 }
michael@0 2456
michael@0 2457 return NS_OK;
michael@0 2458 }
michael@0 2459
michael@0 2460 NS_IMETHODIMP
michael@0 2461 History::RegisterVisitedCallback(nsIURI* aURI,
michael@0 2462 Link* aLink)
michael@0 2463 {
michael@0 2464 NS_ASSERTION(aURI, "Must pass a non-null URI!");
michael@0 2465 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 2466 NS_PRECONDITION(aLink, "Must pass a non-null Link!");
michael@0 2467 }
michael@0 2468
michael@0 2469 // Obtain our array of observers for this URI.
michael@0 2470 #ifdef DEBUG
michael@0 2471 bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
michael@0 2472 #endif
michael@0 2473 KeyClass* key = mObservers.PutEntry(aURI);
michael@0 2474 NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
michael@0 2475 ObserverArray& observers = key->array;
michael@0 2476
michael@0 2477 if (observers.IsEmpty()) {
michael@0 2478 NS_ASSERTION(!keyAlreadyExists,
michael@0 2479 "An empty key was kept around in our hashtable!");
michael@0 2480
michael@0 2481 // We are the first Link node to ask about this URI, or there are no pending
michael@0 2482 // Links wanting to know about this URI. Therefore, we should query the
michael@0 2483 // database now.
michael@0 2484 nsresult rv = VisitedQuery::Start(aURI);
michael@0 2485
michael@0 2486 // In IPC builds, we are passed a nullptr Link from
michael@0 2487 // ContentParent::RecvStartVisitedQuery. Since we won't be adding a
michael@0 2488 // nullptr entry to our list of observers, and the code after this point
michael@0 2489 // assumes that aLink is non-nullptr, we will need to return now.
michael@0 2490 if (NS_FAILED(rv) || !aLink) {
michael@0 2491 // Remove our array from the hashtable so we don't keep it around.
michael@0 2492 mObservers.RemoveEntry(aURI);
michael@0 2493 return rv;
michael@0 2494 }
michael@0 2495 }
michael@0 2496 // In IPC builds, we are passed a nullptr Link from
michael@0 2497 // ContentParent::RecvStartVisitedQuery. All of our code after this point
michael@0 2498 // assumes aLink is non-nullptr, so we have to return now.
michael@0 2499 else if (!aLink) {
michael@0 2500 NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Default,
michael@0 2501 "We should only ever get a null Link in the default process!");
michael@0 2502 return NS_OK;
michael@0 2503 }
michael@0 2504
michael@0 2505 // Sanity check that Links are not registered more than once for a given URI.
michael@0 2506 // This will not catch a case where it is registered for two different URIs.
michael@0 2507 NS_ASSERTION(!observers.Contains(aLink),
michael@0 2508 "Already tracking this Link object!");
michael@0 2509
michael@0 2510 // Start tracking our Link.
michael@0 2511 if (!observers.AppendElement(aLink)) {
michael@0 2512 // Curses - unregister and return failure.
michael@0 2513 (void)UnregisterVisitedCallback(aURI, aLink);
michael@0 2514 return NS_ERROR_OUT_OF_MEMORY;
michael@0 2515 }
michael@0 2516
michael@0 2517 return NS_OK;
michael@0 2518 }
michael@0 2519
michael@0 2520 NS_IMETHODIMP
michael@0 2521 History::UnregisterVisitedCallback(nsIURI* aURI,
michael@0 2522 Link* aLink)
michael@0 2523 {
michael@0 2524 NS_ASSERTION(aURI, "Must pass a non-null URI!");
michael@0 2525 NS_ASSERTION(aLink, "Must pass a non-null Link object!");
michael@0 2526
michael@0 2527 // Get the array, and remove the item from it.
michael@0 2528 KeyClass* key = mObservers.GetEntry(aURI);
michael@0 2529 if (!key) {
michael@0 2530 NS_ERROR("Trying to unregister for a URI that wasn't registered!");
michael@0 2531 return NS_ERROR_UNEXPECTED;
michael@0 2532 }
michael@0 2533 ObserverArray& observers = key->array;
michael@0 2534 if (!observers.RemoveElement(aLink)) {
michael@0 2535 NS_ERROR("Trying to unregister a node that wasn't registered!");
michael@0 2536 return NS_ERROR_UNEXPECTED;
michael@0 2537 }
michael@0 2538
michael@0 2539 // If the array is now empty, we should remove it from the hashtable.
michael@0 2540 if (observers.IsEmpty()) {
michael@0 2541 mObservers.RemoveEntry(aURI);
michael@0 2542 }
michael@0 2543
michael@0 2544 return NS_OK;
michael@0 2545 }
michael@0 2546
michael@0 2547 NS_IMETHODIMP
michael@0 2548 History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
michael@0 2549 {
michael@0 2550 NS_PRECONDITION(aURI, "Must pass a non-null URI!");
michael@0 2551 if (mShuttingDown) {
michael@0 2552 return NS_OK;
michael@0 2553 }
michael@0 2554
michael@0 2555 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 2556 URIParams uri;
michael@0 2557 SerializeURI(aURI, uri);
michael@0 2558
michael@0 2559 mozilla::dom::ContentChild * cpc =
michael@0 2560 mozilla::dom::ContentChild::GetSingleton();
michael@0 2561 NS_ASSERTION(cpc, "Content Protocol is NULL!");
michael@0 2562 (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle));
michael@0 2563 return NS_OK;
michael@0 2564 }
michael@0 2565
michael@0 2566 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 2567
michael@0 2568 // At first, it seems like nav history should always be available here, no
michael@0 2569 // matter what.
michael@0 2570 //
michael@0 2571 // nsNavHistory fails to register as a service if there is no profile in
michael@0 2572 // place (for instance, if user is choosing a profile).
michael@0 2573 //
michael@0 2574 // Maybe the correct thing to do is to not register this service if no
michael@0 2575 // profile has been selected?
michael@0 2576 //
michael@0 2577 NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
michael@0 2578
michael@0 2579 bool canAdd;
michael@0 2580 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
michael@0 2581 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2582 if (!canAdd) {
michael@0 2583 return NS_OK;
michael@0 2584 }
michael@0 2585
michael@0 2586 // Embed visits don't have a database entry, thus don't set a title on them.
michael@0 2587 if (navHistory->hasEmbedVisit(aURI)) {
michael@0 2588 return NS_OK;
michael@0 2589 }
michael@0 2590
michael@0 2591 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2592 NS_ENSURE_STATE(dbConn);
michael@0 2593
michael@0 2594 rv = SetPageTitle::Start(dbConn, aURI, aTitle);
michael@0 2595 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2596
michael@0 2597 return NS_OK;
michael@0 2598 }
michael@0 2599
michael@0 2600 ////////////////////////////////////////////////////////////////////////////////
michael@0 2601 //// nsIDownloadHistory
michael@0 2602
michael@0 2603 NS_IMETHODIMP
michael@0 2604 History::AddDownload(nsIURI* aSource, nsIURI* aReferrer,
michael@0 2605 PRTime aStartTime, nsIURI* aDestination)
michael@0 2606 {
michael@0 2607 MOZ_ASSERT(NS_IsMainThread());
michael@0 2608 NS_ENSURE_ARG(aSource);
michael@0 2609
michael@0 2610 if (mShuttingDown) {
michael@0 2611 return NS_OK;
michael@0 2612 }
michael@0 2613
michael@0 2614 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 2615 NS_ERROR("Cannot add downloads to history from content process!");
michael@0 2616 return NS_ERROR_NOT_AVAILABLE;
michael@0 2617 }
michael@0 2618
michael@0 2619 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 2620 NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
michael@0 2621
michael@0 2622 // Silently return if URI is something we shouldn't add to DB.
michael@0 2623 bool canAdd;
michael@0 2624 nsresult rv = navHistory->CanAddURI(aSource, &canAdd);
michael@0 2625 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2626 if (!canAdd) {
michael@0 2627 return NS_OK;
michael@0 2628 }
michael@0 2629
michael@0 2630 nsTArray<VisitData> placeArray(1);
michael@0 2631 NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)),
michael@0 2632 NS_ERROR_OUT_OF_MEMORY);
michael@0 2633 VisitData& place = placeArray.ElementAt(0);
michael@0 2634 NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
michael@0 2635
michael@0 2636 place.visitTime = aStartTime;
michael@0 2637 place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD);
michael@0 2638 place.hidden = false;
michael@0 2639
michael@0 2640 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2641 NS_ENSURE_STATE(dbConn);
michael@0 2642
michael@0 2643 nsCOMPtr<mozIVisitInfoCallback> callback = aDestination
michael@0 2644 ? new SetDownloadAnnotations(aDestination)
michael@0 2645 : nullptr;
michael@0 2646
michael@0 2647 rv = InsertVisitedURIs::Start(dbConn, placeArray, callback);
michael@0 2648 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2649
michael@0 2650 // Finally, notify that we've been visited.
michael@0 2651 nsCOMPtr<nsIObserverService> obsService =
michael@0 2652 mozilla::services::GetObserverService();
michael@0 2653 if (obsService) {
michael@0 2654 obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
michael@0 2655 }
michael@0 2656
michael@0 2657 return NS_OK;
michael@0 2658 }
michael@0 2659
michael@0 2660 NS_IMETHODIMP
michael@0 2661 History::RemoveAllDownloads()
michael@0 2662 {
michael@0 2663 MOZ_ASSERT(NS_IsMainThread());
michael@0 2664
michael@0 2665 if (mShuttingDown) {
michael@0 2666 return NS_OK;
michael@0 2667 }
michael@0 2668
michael@0 2669 if (XRE_GetProcessType() == GeckoProcessType_Content) {
michael@0 2670 NS_ERROR("Cannot remove downloads to history from content process!");
michael@0 2671 return NS_ERROR_NOT_AVAILABLE;
michael@0 2672 }
michael@0 2673
michael@0 2674 // Ensure navHistory is initialized.
michael@0 2675 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 2676 NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
michael@0 2677 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2678 NS_ENSURE_STATE(dbConn);
michael@0 2679
michael@0 2680 RemoveVisitsFilter filter;
michael@0 2681 filter.transitionType = nsINavHistoryService::TRANSITION_DOWNLOAD;
michael@0 2682
michael@0 2683 nsresult rv = RemoveVisits::Start(dbConn, filter);
michael@0 2684 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2685
michael@0 2686 return NS_OK;
michael@0 2687 }
michael@0 2688
michael@0 2689 ////////////////////////////////////////////////////////////////////////////////
michael@0 2690 //// mozIAsyncHistory
michael@0 2691
michael@0 2692 NS_IMETHODIMP
michael@0 2693 History::GetPlacesInfo(JS::Handle<JS::Value> aPlaceIdentifiers,
michael@0 2694 mozIVisitInfoCallback* aCallback,
michael@0 2695 JSContext* aCtx) {
michael@0 2696 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
michael@0 2697 NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
michael@0 2698
michael@0 2699 uint32_t placesIndentifiersLength;
michael@0 2700 JS::Rooted<JSObject*> placesIndentifiers(aCtx);
michael@0 2701 nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx,
michael@0 2702 &placesIndentifiers,
michael@0 2703 &placesIndentifiersLength);
michael@0 2704 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2705
michael@0 2706 nsTArray<VisitData> placesInfo;
michael@0 2707 placesInfo.SetCapacity(placesIndentifiersLength);
michael@0 2708 for (uint32_t i = 0; i < placesIndentifiersLength; i++) {
michael@0 2709 JS::Rooted<JS::Value> placeIdentifier(aCtx);
michael@0 2710 bool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier);
michael@0 2711 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 2712
michael@0 2713 // GUID
michael@0 2714 nsAutoString fatGUID;
michael@0 2715 GetJSValueAsString(aCtx, placeIdentifier, fatGUID);
michael@0 2716 if (!fatGUID.IsVoid()) {
michael@0 2717 NS_ConvertUTF16toUTF8 guid(fatGUID);
michael@0 2718 if (!IsValidGUID(guid))
michael@0 2719 return NS_ERROR_INVALID_ARG;
michael@0 2720
michael@0 2721 VisitData& placeInfo = *placesInfo.AppendElement(VisitData());
michael@0 2722 placeInfo.guid = guid;
michael@0 2723 }
michael@0 2724 else {
michael@0 2725 nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier);
michael@0 2726 if (!uri)
michael@0 2727 return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri.
michael@0 2728 placesInfo.AppendElement(VisitData(uri));
michael@0 2729 }
michael@0 2730 }
michael@0 2731
michael@0 2732 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2733 NS_ENSURE_STATE(dbConn);
michael@0 2734
michael@0 2735 for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) {
michael@0 2736 nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback);
michael@0 2737 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2738 }
michael@0 2739
michael@0 2740 // Be sure to notify that all of our operations are complete. This
michael@0 2741 // is dispatched to the background thread first and redirected to the
michael@0 2742 // main thread from there to make sure that all database notifications
michael@0 2743 // and all embed or canAddURI notifications have finished.
michael@0 2744 if (aCallback) {
michael@0 2745 // NotifyCompletion does not hold a strong reference to the callback,
michael@0 2746 // so we have to manage it by AddRefing now. NotifyCompletion will
michael@0 2747 // release it for us once it has dispatched the callback to the main
michael@0 2748 // thread.
michael@0 2749 NS_ADDREF(aCallback);
michael@0 2750
michael@0 2751 nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
michael@0 2752 NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
michael@0 2753 nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
michael@0 2754 return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2755 }
michael@0 2756
michael@0 2757 return NS_OK;
michael@0 2758 }
michael@0 2759
michael@0 2760 NS_IMETHODIMP
michael@0 2761 History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
michael@0 2762 mozIVisitInfoCallback* aCallback,
michael@0 2763 JSContext* aCtx)
michael@0 2764 {
michael@0 2765 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
michael@0 2766 NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG);
michael@0 2767
michael@0 2768 uint32_t infosLength;
michael@0 2769 JS::Rooted<JSObject*> infos(aCtx);
michael@0 2770 nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
michael@0 2771 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2772
michael@0 2773 nsTArray<VisitData> visitData;
michael@0 2774 for (uint32_t i = 0; i < infosLength; i++) {
michael@0 2775 JS::Rooted<JSObject*> info(aCtx);
michael@0 2776 nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
michael@0 2777 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2778
michael@0 2779 nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
michael@0 2780 nsCString guid;
michael@0 2781 {
michael@0 2782 nsString fatGUID;
michael@0 2783 GetStringFromJSObject(aCtx, info, "guid", fatGUID);
michael@0 2784 if (fatGUID.IsVoid()) {
michael@0 2785 guid.SetIsVoid(true);
michael@0 2786 }
michael@0 2787 else {
michael@0 2788 guid = NS_ConvertUTF16toUTF8(fatGUID);
michael@0 2789 }
michael@0 2790 }
michael@0 2791
michael@0 2792 // Make sure that any uri we are given can be added to history, and if not,
michael@0 2793 // skip it (CanAddURI will notify our callback for us).
michael@0 2794 if (uri && !CanAddURI(uri, guid, aCallback)) {
michael@0 2795 continue;
michael@0 2796 }
michael@0 2797
michael@0 2798 // We must have at least one of uri or guid.
michael@0 2799 NS_ENSURE_ARG(uri || !guid.IsVoid());
michael@0 2800
michael@0 2801 // If we were given a guid, make sure it is valid.
michael@0 2802 bool isValidGUID = IsValidGUID(guid);
michael@0 2803 NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
michael@0 2804
michael@0 2805 nsString title;
michael@0 2806 GetStringFromJSObject(aCtx, info, "title", title);
michael@0 2807
michael@0 2808 JS::Rooted<JSObject*> visits(aCtx, nullptr);
michael@0 2809 {
michael@0 2810 JS::Rooted<JS::Value> visitsVal(aCtx);
michael@0 2811 bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
michael@0 2812 NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
michael@0 2813 if (!JSVAL_IS_PRIMITIVE(visitsVal)) {
michael@0 2814 visits = JSVAL_TO_OBJECT(visitsVal);
michael@0 2815 NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits));
michael@0 2816 }
michael@0 2817 }
michael@0 2818 NS_ENSURE_ARG(visits);
michael@0 2819
michael@0 2820 uint32_t visitsLength = 0;
michael@0 2821 if (visits) {
michael@0 2822 (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
michael@0 2823 }
michael@0 2824 NS_ENSURE_ARG(visitsLength > 0);
michael@0 2825
michael@0 2826 // Check each visit, and build our array of VisitData objects.
michael@0 2827 visitData.SetCapacity(visitData.Length() + visitsLength);
michael@0 2828 for (uint32_t j = 0; j < visitsLength; j++) {
michael@0 2829 JS::Rooted<JSObject*> visit(aCtx);
michael@0 2830 rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
michael@0 2831 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2832
michael@0 2833 VisitData& data = *visitData.AppendElement(VisitData(uri));
michael@0 2834 data.title = title;
michael@0 2835 data.guid = guid;
michael@0 2836
michael@0 2837 // We must have a date and a transaction type!
michael@0 2838 rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
michael@0 2839 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2840 uint32_t transitionType = 0;
michael@0 2841 rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
michael@0 2842 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2843 NS_ENSURE_ARG_RANGE(transitionType,
michael@0 2844 nsINavHistoryService::TRANSITION_LINK,
michael@0 2845 nsINavHistoryService::TRANSITION_FRAMED_LINK);
michael@0 2846 data.SetTransitionType(transitionType);
michael@0 2847 data.hidden = GetHiddenState(false, transitionType);
michael@0 2848
michael@0 2849 // If the visit is an embed visit, we do not actually add it to the
michael@0 2850 // database.
michael@0 2851 if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
michael@0 2852 StoreAndNotifyEmbedVisit(data, aCallback);
michael@0 2853 visitData.RemoveElementAt(visitData.Length() - 1);
michael@0 2854 continue;
michael@0 2855 }
michael@0 2856
michael@0 2857 // The referrer is optional.
michael@0 2858 nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
michael@0 2859 "referrerURI");
michael@0 2860 if (referrer) {
michael@0 2861 (void)referrer->GetSpec(data.referrerSpec);
michael@0 2862 }
michael@0 2863 }
michael@0 2864 }
michael@0 2865
michael@0 2866 mozIStorageConnection* dbConn = GetDBConn();
michael@0 2867 NS_ENSURE_STATE(dbConn);
michael@0 2868
michael@0 2869 // It is possible that all of the visits we were passed were dissallowed by
michael@0 2870 // CanAddURI, which isn't an error. If we have no visits to add, however,
michael@0 2871 // we should not call InsertVisitedURIs::Start.
michael@0 2872 if (visitData.Length()) {
michael@0 2873 nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback);
michael@0 2874 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2875 }
michael@0 2876
michael@0 2877 // Be sure to notify that all of our operations are complete. This
michael@0 2878 // is dispatched to the background thread first and redirected to the
michael@0 2879 // main thread from there to make sure that all database notifications
michael@0 2880 // and all embed or canAddURI notifications have finished.
michael@0 2881 if (aCallback) {
michael@0 2882 // NotifyCompletion does not hold a strong reference to the callback,
michael@0 2883 // so we have to manage it by AddRefing now. NotifyCompletion will
michael@0 2884 // release it for us once it has dispatched the callback to the main
michael@0 2885 // thread.
michael@0 2886 NS_ADDREF(aCallback);
michael@0 2887
michael@0 2888 nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
michael@0 2889 NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
michael@0 2890 nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
michael@0 2891 return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
michael@0 2892 }
michael@0 2893
michael@0 2894 return NS_OK;
michael@0 2895 }
michael@0 2896
michael@0 2897 NS_IMETHODIMP
michael@0 2898 History::IsURIVisited(nsIURI* aURI,
michael@0 2899 mozIVisitedStatusCallback* aCallback)
michael@0 2900 {
michael@0 2901 NS_ENSURE_STATE(NS_IsMainThread());
michael@0 2902 NS_ENSURE_ARG(aURI);
michael@0 2903 NS_ENSURE_ARG(aCallback);
michael@0 2904
michael@0 2905 nsresult rv = VisitedQuery::Start(aURI, aCallback);
michael@0 2906 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2907
michael@0 2908 return NS_OK;
michael@0 2909 }
michael@0 2910
michael@0 2911 ////////////////////////////////////////////////////////////////////////////////
michael@0 2912 //// nsIObserver
michael@0 2913
michael@0 2914 NS_IMETHODIMP
michael@0 2915 History::Observe(nsISupports* aSubject, const char* aTopic,
michael@0 2916 const char16_t* aData)
michael@0 2917 {
michael@0 2918 if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
michael@0 2919 Shutdown();
michael@0 2920
michael@0 2921 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
michael@0 2922 if (os) {
michael@0 2923 (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
michael@0 2924 }
michael@0 2925 }
michael@0 2926
michael@0 2927 return NS_OK;
michael@0 2928 }
michael@0 2929
michael@0 2930 ////////////////////////////////////////////////////////////////////////////////
michael@0 2931 //// nsISupports
michael@0 2932
michael@0 2933 NS_IMPL_ISUPPORTS(
michael@0 2934 History
michael@0 2935 , IHistory
michael@0 2936 , nsIDownloadHistory
michael@0 2937 , mozIAsyncHistory
michael@0 2938 , nsIObserver
michael@0 2939 , nsIMemoryReporter
michael@0 2940 )
michael@0 2941
michael@0 2942 } // namespace places
michael@0 2943 } // namespace mozilla

mercurial