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