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/Attributes.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/dom/ContentChild.h"
12 #include "mozilla/dom/ContentParent.h"
13 #include "mozilla/dom/BrowserChild.h"
14 #include "nsXULAppAPI.h"
17 #include "nsNavHistory.h"
18 #include "nsNavBookmarks.h"
20 #include "PlaceInfo.h"
21 #include "VisitInfo.h"
22 #include "nsPlacesMacros.h"
23 #include "NotifyRankingChanged.h"
25 #include "mozilla/storage.h"
26 #include "mozilla/dom/Link.h"
27 #include "nsDocShellCID.h"
28 #include "mozilla/Components.h"
29 #include "nsThreadUtils.h"
30 #include "nsNetUtil.h"
31 #include "nsIWidget.h"
32 #include "nsIXPConnect.h"
33 #include "nsIXULRuntime.h"
34 #include "mozilla/Unused.h"
35 #include "nsContentUtils.h" // for nsAutoScriptBlocker
36 #include "nsJSUtils.h"
37 #include "mozilla/ipc/URIUtils.h"
38 #include "nsPrintfCString.h"
39 #include "nsTHashtable.h"
41 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
42 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetElement, JS_GetProperty
43 #include "mozilla/StaticPrefs_layout.h"
44 #include "mozilla/dom/ContentProcessMessageManager.h"
45 #include "mozilla/dom/Element.h"
46 #include "mozilla/dom/PlacesObservers.h"
47 #include "mozilla/dom/PlacesVisit.h"
48 #include "mozilla/dom/PlacesVisitTitle.h"
49 #include "mozilla/dom/ScriptSettings.h"
51 using namespace mozilla::dom
;
52 using namespace mozilla::ipc
;
53 using mozilla::Unused
;
58 ////////////////////////////////////////////////////////////////////////////////
61 // Observer event fired after a visit has been registered in the DB.
62 #define URI_VISIT_SAVED "uri-visit-saved"
64 #define DESTINATIONFILEURI_ANNO "downloads/destinationFileURI"_ns
66 ////////////////////////////////////////////////////////////////////////////////
74 shouldUpdateHidden(true),
76 transitionType(UINT32_MAX
),
84 shouldUpdateFrecency(true),
85 useFrecencyRedirectBonus(false) {
87 title
.SetIsVoid(true);
90 explicit VisitData(nsIURI
* aURI
, nsIURI
* aReferrer
= nullptr)
94 shouldUpdateHidden(true),
96 transitionType(UINT32_MAX
),
104 shouldUpdateFrecency(true),
105 useFrecencyRedirectBonus(false) {
108 (void)aURI
->GetSpec(spec
);
109 (void)GetReversedHostname(aURI
, revHost
);
112 (void)aReferrer
->GetSpec(referrerSpec
);
114 guid
.SetIsVoid(true);
115 title
.SetIsVoid(true);
119 * Sets the transition type of the visit, as well as if it was typed.
121 * @param aTransitionType
122 * The transition type constant to set. Must be one of the
123 * TRANSITION_ constants on nsINavHistoryService.
125 void SetTransitionType(uint32_t aTransitionType
) {
126 typed
= aTransitionType
== nsINavHistoryService::TRANSITION_TYPED
;
127 transitionType
= aTransitionType
;
136 bool shouldUpdateHidden
;
138 uint32_t transitionType
;
142 PRTime lastVisitTime
;
146 * Stores the title. If this is empty (IsEmpty() returns true), then the
147 * title should be removed from the Place. If the title is void (IsVoid()
148 * returns true), then no title has been set on this object, and titleChanged
149 * should remain false.
153 nsCString referrerSpec
;
154 int64_t referrerVisitId
;
156 // TODO bug 626836 hook up hidden and typed change tracking too!
159 // Indicates whether frecency should be updated for this visit.
160 bool shouldUpdateFrecency
;
162 // Whether to override the visit type bonus with a redirect bonus when
163 // calculating frecency on the most recent visit.
164 bool useFrecencyRedirectBonus
;
167 ////////////////////////////////////////////////////////////////////////////////
168 //// Anonymous Helpers
173 * Convert the given js value to a js array.
176 * the JS value to convert.
178 * The JSContext for aValue.
179 * @param [out] _array
181 * @param [out] _arrayLength
184 nsresult
GetJSArrayFromJSValue(JS::Handle
<JS::Value
> aValue
, JSContext
* aCtx
,
185 JS::MutableHandle
<JSObject
*> _array
,
186 uint32_t* _arrayLength
) {
187 if (aValue
.isObjectOrNull()) {
188 JS::Rooted
<JSObject
*> val(aCtx
, aValue
.toObjectOrNull());
190 if (!JS::IsArrayObject(aCtx
, val
, &isArray
)) {
191 return NS_ERROR_UNEXPECTED
;
195 (void)JS::GetArrayLength(aCtx
, _array
, _arrayLength
);
196 NS_ENSURE_ARG(*_arrayLength
> 0);
201 // Build a temporary array to store this one item so the code below can
204 _array
.set(JS::NewArrayObject(aCtx
, 0));
205 NS_ENSURE_TRUE(_array
, NS_ERROR_OUT_OF_MEMORY
);
207 bool rc
= JS_DefineElement(aCtx
, _array
, 0, aValue
, 0);
208 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
213 * Attemps to convert a given js value to a nsIURI object.
215 * The JSContext for aValue.
217 * The JS value to convert.
218 * @return the nsIURI object, or null if aValue is not a nsIURI object.
220 already_AddRefed
<nsIURI
> GetJSValueAsURI(JSContext
* aCtx
,
221 const JS::Value
& aValue
) {
222 if (!aValue
.isPrimitive()) {
223 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
225 nsCOMPtr
<nsIXPConnectWrappedNative
> wrappedObj
;
226 JS::Rooted
<JSObject
*> obj(aCtx
, aValue
.toObjectOrNull());
228 xpc
->GetWrappedNativeOfJSObject(aCtx
, obj
, getter_AddRefs(wrappedObj
));
229 NS_ENSURE_SUCCESS(rv
, nullptr);
230 nsCOMPtr
<nsIURI
> uri
= do_QueryInterface(wrappedObj
->Native());
237 * Obtains an nsIURI from the "uri" property of a JSObject.
240 * The JSContext for aObject.
242 * The JSObject to get the URI from.
244 * The name of the property to get the URI from.
245 * @return the URI if it exists.
247 already_AddRefed
<nsIURI
> GetURIFromJSObject(JSContext
* aCtx
,
248 JS::Handle
<JSObject
*> aObject
,
249 const char* aProperty
) {
250 JS::Rooted
<JS::Value
> uriVal(aCtx
);
251 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &uriVal
);
252 NS_ENSURE_TRUE(rc
, nullptr);
253 return GetJSValueAsURI(aCtx
, uriVal
);
257 * Attemps to convert a JS value to a string.
259 * The JSContext for aObject.
261 * The JS value to convert.
263 * The string to populate with the value, or set it to void.
265 void GetJSValueAsString(JSContext
* aCtx
, const JS::Value
& aValue
,
267 if (aValue
.isUndefined() || !(aValue
.isNull() || aValue
.isString())) {
268 _string
.SetIsVoid(true);
272 // |null| in JS maps to the empty string.
273 if (aValue
.isNull()) {
278 if (!AssignJSString(aCtx
, _string
, aValue
.toString())) {
279 _string
.SetIsVoid(true);
284 * Obtains the specified property of a JSObject.
287 * The JSContext for aObject.
289 * The JSObject to get the string from.
291 * The property to get the value from.
293 * The string to populate with the value, or set it to void.
295 void GetStringFromJSObject(JSContext
* aCtx
, JS::Handle
<JSObject
*> aObject
,
296 const char* aProperty
, nsString
& _string
) {
297 JS::Rooted
<JS::Value
> val(aCtx
);
298 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &val
);
300 _string
.SetIsVoid(true);
303 GetJSValueAsString(aCtx
, val
, _string
);
308 * Obtains the specified property of a JSObject.
311 * The JSContext for aObject.
313 * The JSObject to get the int from.
315 * The property to get the value from.
317 * The integer to populate with the value on success.
319 template <typename IntType
>
320 nsresult
GetIntFromJSObject(JSContext
* aCtx
, JS::Handle
<JSObject
*> aObject
,
321 const char* aProperty
, IntType
* _int
) {
322 JS::Rooted
<JS::Value
> value(aCtx
);
323 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &value
);
324 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
325 if (value
.isUndefined()) {
326 return NS_ERROR_INVALID_ARG
;
328 NS_ENSURE_ARG(value
.isPrimitive());
329 NS_ENSURE_ARG(value
.isNumber());
332 rc
= JS::ToNumber(aCtx
, value
, &num
);
333 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
334 NS_ENSURE_ARG(IntType(num
) == num
);
336 *_int
= IntType(num
);
341 * Obtains the specified property of a JSObject.
343 * @pre aArray must be an Array object.
346 * The JSContext for aArray.
348 * The JSObject to get the object from.
350 * The index to get the object from.
352 * Set to the JSObject pointer on success.
354 nsresult
GetJSObjectFromArray(JSContext
* aCtx
, JS::Handle
<JSObject
*> aArray
,
356 JS::MutableHandle
<JSObject
*> objOut
) {
357 JS::Rooted
<JS::Value
> value(aCtx
);
358 bool rc
= JS_GetElement(aCtx
, aArray
, aIndex
, &value
);
359 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
360 NS_ENSURE_ARG(!value
.isPrimitive());
361 objOut
.set(&value
.toObject());
367 class VisitedQuery final
: public AsyncStatementCallback
{
369 NS_DECL_ISUPPORTS_INHERITED
371 static nsresult
Start(nsIURI
* aURI
,
372 History::ContentParentSet
&& aContentProcessesToNotify
) {
373 MOZ_ASSERT(aURI
, "Null URI");
374 MOZ_ASSERT(XRE_IsParentProcess());
376 History
* history
= History::GetService();
377 NS_ENSURE_STATE(history
);
378 RefPtr
<VisitedQuery
> query
=
379 new VisitedQuery(aURI
, std::move(aContentProcessesToNotify
));
380 return history
->QueueVisitedStatement(std::move(query
));
383 static nsresult
Start(nsIURI
* aURI
,
384 mozIVisitedStatusCallback
* aCallback
= nullptr) {
385 MOZ_ASSERT(aURI
, "Null URI");
386 MOZ_ASSERT(XRE_IsParentProcess());
388 nsMainThreadPtrHandle
<mozIVisitedStatusCallback
> callback(
389 new nsMainThreadPtrHolder
<mozIVisitedStatusCallback
>(
390 "mozIVisitedStatusCallback", aCallback
));
392 History
* history
= History::GetService();
393 NS_ENSURE_STATE(history
);
394 RefPtr
<VisitedQuery
> query
= new VisitedQuery(aURI
, callback
);
395 return history
->QueueVisitedStatement(std::move(query
));
398 void Execute(mozIStorageAsyncStatement
& aStatement
) {
399 // Bind by index for performance.
400 nsresult rv
= URIBinder::Bind(&aStatement
, 0, mURI
);
401 if (NS_WARN_IF(NS_FAILED(rv
))) {
405 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
406 rv
= aStatement
.ExecuteAsync(this, getter_AddRefs(handle
));
407 MOZ_ASSERT(NS_SUCCEEDED(rv
));
411 NS_IMETHOD
HandleResult(mozIStorageResultSet
* aResults
) override
{
412 // If this method is called, we've gotten results, which means we have a
418 NS_IMETHOD
HandleError(mozIStorageError
* aError
) override
{
419 // mIsVisited is already set to false, and that's the assumption we will
420 // make if an error occurred.
424 NS_IMETHOD
HandleCompletion(uint16_t aReason
) override
{
425 if (aReason
== mozIStorageStatementCallback::REASON_FINISHED
) {
426 NotifyVisitedStatus();
431 void NotifyVisitedStatus() {
432 // If an external handling callback is provided, just notify through it.
434 mCallback
->IsVisited(mURI
, mIsVisited
);
438 if (History
* history
= History::GetService()) {
439 auto status
= mIsVisited
? IHistory::VisitedStatus::Visited
440 : IHistory::VisitedStatus::Unvisited
;
441 history
->NotifyVisited(mURI
, status
, &mContentProcessesToNotify
);
446 explicit VisitedQuery(
448 const nsMainThreadPtrHandle
<mozIVisitedStatusCallback
>& aCallback
)
449 : mURI(aURI
), mCallback(aCallback
) {}
451 explicit VisitedQuery(nsIURI
* aURI
,
452 History::ContentParentSet
&& aContentProcessesToNotify
)
454 mContentProcessesToNotify(std::move(aContentProcessesToNotify
)) {}
456 ~VisitedQuery() = default;
458 nsCOMPtr
<nsIURI
> mURI
;
459 nsMainThreadPtrHandle
<mozIVisitedStatusCallback
> mCallback
;
460 History::ContentParentSet mContentProcessesToNotify
;
461 bool mIsVisited
= false;
464 NS_IMPL_ISUPPORTS_INHERITED0(VisitedQuery
, AsyncStatementCallback
)
467 * Notifies observers about a visit or an array of visits.
469 class NotifyManyVisitsObservers
: public Runnable
{
471 explicit NotifyManyVisitsObservers(const VisitData
& aPlace
)
472 : Runnable("places::NotifyManyVisitsObservers"),
474 mHistory(History::GetService()) {}
476 explicit NotifyManyVisitsObservers(nsTArray
<VisitData
>&& aPlaces
)
477 : Runnable("places::NotifyManyVisitsObservers"),
478 mPlaces(std::move(aPlaces
)),
479 mHistory(History::GetService()) {}
481 nsresult
NotifyVisit(nsNavHistory
* aNavHistory
,
482 nsCOMPtr
<nsIObserverService
>& aObsService
, PRTime aNow
,
483 nsIURI
* aURI
, const VisitData
& aPlace
) {
485 DebugOnly
<nsresult
> rv
=
486 aObsService
->NotifyObservers(aURI
, URI_VISIT_SAVED
, nullptr);
487 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Could not notify observers");
490 if (aNow
- aPlace
.visitTime
< RECENTLY_VISITED_URIS_MAX_AGE
) {
491 mHistory
->AppendToRecentlyVisitedURIs(aURI
, aPlace
.hidden
);
493 mHistory
->NotifyVisited(aURI
, IHistory::VisitedStatus::Visited
);
495 aNavHistory
->UpdateDaysOfHistory(aPlace
.visitTime
);
500 void AddPlaceForNotify(const VisitData
& aPlace
,
501 Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
) {
502 if (aPlace
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
506 RefPtr
<PlacesVisit
> visitEvent
= new PlacesVisit();
507 visitEvent
->mVisitId
= aPlace
.visitId
;
508 visitEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(aPlace
.spec
));
509 visitEvent
->mVisitTime
= aPlace
.visitTime
/ 1000;
510 visitEvent
->mReferringVisitId
= aPlace
.referrerVisitId
;
511 visitEvent
->mTransitionType
= aPlace
.transitionType
;
512 visitEvent
->mPageGuid
.Assign(aPlace
.guid
);
513 visitEvent
->mHidden
= aPlace
.hidden
;
514 visitEvent
->mVisitCount
= aPlace
.visitCount
+ 1; // Add current visit
515 visitEvent
->mTypedCount
= static_cast<uint32_t>(aPlace
.typed
);
516 visitEvent
->mLastKnownTitle
.Assign(aPlace
.title
);
517 bool success
= !!aEvents
.AppendElement(visitEvent
.forget(), fallible
);
518 MOZ_RELEASE_ASSERT(success
);
520 if (aPlace
.titleChanged
) {
521 RefPtr
<PlacesVisitTitle
> titleEvent
= new PlacesVisitTitle();
522 titleEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(aPlace
.spec
));
523 titleEvent
->mPageGuid
.Assign(aPlace
.guid
);
524 titleEvent
->mTitle
.Assign(aPlace
.title
);
525 bool success
= !!aEvents
.AppendElement(titleEvent
.forget(), fallible
);
526 MOZ_RELEASE_ASSERT(success
);
530 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
531 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
532 MOZ_CAN_RUN_SCRIPT_BOUNDARY
533 NS_IMETHOD
Run() override
{
534 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
536 // We are in the main thread, no need to lock.
537 if (mHistory
->IsShuttingDown()) {
538 // If we are shutting down, we cannot notify the observers.
542 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
545 "Trying to notify visits observers but cannot get the history "
550 nsCOMPtr
<nsIObserverService
> obsService
=
551 mozilla::services::GetObserverService();
553 Sequence
<OwningNonNull
<PlacesEvent
>> events
;
554 PRTime now
= PR_Now();
555 for (uint32_t i
= 0; i
< mPlaces
.Length(); ++i
) {
556 nsCOMPtr
<nsIURI
> uri
;
557 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), mPlaces
[i
].spec
));
559 return NS_ERROR_UNEXPECTED
;
561 AddPlaceForNotify(mPlaces
[i
], events
);
563 nsresult rv
= NotifyVisit(navHistory
, obsService
, now
, uri
, mPlaces
[i
]);
564 NS_ENSURE_SUCCESS(rv
, rv
);
567 if (events
.Length() > 0) {
568 PlacesObservers::NotifyListeners(events
);
575 AutoTArray
<VisitData
, 1> mPlaces
;
576 RefPtr
<History
> mHistory
;
580 * Notifies observers about a pages title changing.
582 class NotifyTitleObservers
: public Runnable
{
585 * Notifies observers on the main thread.
588 * The spec of the URI to notify about.
590 * The new title to notify about.
592 NotifyTitleObservers(const nsCString
& aSpec
, const nsString
& aTitle
,
593 const nsCString
& aGUID
)
594 : Runnable("places::NotifyTitleObservers"),
599 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
600 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
601 MOZ_CAN_RUN_SCRIPT_BOUNDARY
602 NS_IMETHOD
Run() override
{
603 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
605 RefPtr
<PlacesVisitTitle
> titleEvent
= new PlacesVisitTitle();
606 titleEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(mSpec
));
607 titleEvent
->mPageGuid
.Assign(mGUID
);
608 titleEvent
->mTitle
.Assign(mTitle
);
610 Sequence
<OwningNonNull
<PlacesEvent
>> events
;
611 bool success
= !!events
.AppendElement(titleEvent
.forget(), fallible
);
612 MOZ_RELEASE_ASSERT(success
);
614 PlacesObservers::NotifyListeners(events
);
620 const nsCString mSpec
;
621 const nsString mTitle
;
622 const nsCString mGUID
;
626 * Helper class for methods which notify their callers through the
627 * mozIVisitInfoCallback interface.
629 class NotifyPlaceInfoCallback
: public Runnable
{
631 NotifyPlaceInfoCallback(
632 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
633 const VisitData
& aPlace
, bool aIsSingleVisit
, nsresult aResult
)
634 : Runnable("places::NotifyPlaceInfoCallback"),
635 mCallback(aCallback
),
638 mIsSingleVisit(aIsSingleVisit
) {
639 MOZ_ASSERT(aCallback
, "Must pass a non-null callback!");
642 NS_IMETHOD
Run() override
{
643 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
645 bool hasValidURIs
= true;
646 nsCOMPtr
<nsIURI
> referrerURI
;
647 if (!mPlace
.referrerSpec
.IsEmpty()) {
649 NS_NewURI(getter_AddRefs(referrerURI
), mPlace
.referrerSpec
));
650 hasValidURIs
= !!referrerURI
;
653 nsCOMPtr
<nsIURI
> uri
;
654 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), mPlace
.spec
));
655 hasValidURIs
= hasValidURIs
&& !!uri
;
657 nsCOMPtr
<mozIPlaceInfo
> place
;
658 if (mIsSingleVisit
) {
659 nsCOMPtr
<mozIVisitInfo
> visit
=
660 new VisitInfo(mPlace
.visitId
, mPlace
.visitTime
, mPlace
.transitionType
,
661 referrerURI
.forget());
662 PlaceInfo::VisitsArray visits
;
663 (void)visits
.AppendElement(visit
);
665 // The frecency isn't exposed because it may not reflect the updated value
666 // in the case of InsertVisitedURIs.
667 place
= new PlaceInfo(mPlace
.placeId
, mPlace
.guid
, uri
.forget(),
668 mPlace
.title
, -1, visits
);
671 place
= new PlaceInfo(mPlace
.placeId
, mPlace
.guid
, uri
.forget(),
675 if (NS_SUCCEEDED(mResult
) && hasValidURIs
) {
676 (void)mCallback
->HandleResult(place
);
678 (void)mCallback
->HandleError(mResult
, place
);
685 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
687 const nsresult mResult
;
692 * Notifies a callback object when the operation is complete.
694 class NotifyCompletion
: public Runnable
{
696 explicit NotifyCompletion(
697 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
698 uint32_t aUpdatedCount
= 0)
699 : Runnable("places::NotifyCompletion"),
700 mCallback(aCallback
),
701 mUpdatedCount(aUpdatedCount
) {
702 MOZ_ASSERT(aCallback
, "Must pass a non-null callback!");
705 NS_IMETHOD
Run() override
{
706 if (NS_IsMainThread()) {
707 (void)mCallback
->HandleCompletion(mUpdatedCount
);
709 (void)NS_DispatchToMainThread(this);
715 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
716 uint32_t mUpdatedCount
;
720 * Checks to see if we can add aURI to history, and dispatches an error to
721 * aCallback (if provided) if we cannot.
725 * @param [optional] aGUID
726 * The guid of the URI to check. This is passed back to the callback.
727 * @param [optional] aCallback
728 * The callback to notify if the URI cannot be added to history.
729 * @return true if the URI can be added to history, false otherwise.
731 bool CanAddURI(nsIURI
* aURI
, const nsCString
& aGUID
= ""_ns
,
732 mozIVisitInfoCallback
* aCallback
= nullptr) {
733 MOZ_ASSERT(NS_IsMainThread());
734 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
735 NS_ENSURE_TRUE(navHistory
, false);
738 nsresult rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
739 if (NS_SUCCEEDED(rv
) && canAdd
) {
743 // We cannot add the URI. Notify the callback, if we were given one.
745 VisitData
place(aURI
);
747 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
748 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
749 "mozIVisitInfoCallback", aCallback
));
750 nsCOMPtr
<nsIRunnable
> event
= new NotifyPlaceInfoCallback(
751 callback
, place
, true, NS_ERROR_INVALID_ARG
);
752 (void)NS_DispatchToMainThread(event
);
759 * Adds a visit to the database.
761 class InsertVisitedURIs final
: public Runnable
{
764 * Adds a visit to the database asynchronously.
767 * The database connection to use for these operations.
769 * The locations to record visits.
770 * @param [optional] aCallback
771 * The callback to notify about the visit.
773 static nsresult
Start(mozIStorageConnection
* aConnection
,
774 nsTArray
<VisitData
>&& aPlaces
,
775 mozIVisitInfoCallback
* aCallback
= nullptr,
776 uint32_t aInitialUpdatedCount
= 0) {
777 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
778 MOZ_ASSERT(aPlaces
.Length() > 0, "Must pass a non-empty array!");
780 // Make sure nsNavHistory service is up before proceeding:
781 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
782 MOZ_ASSERT(navHistory
, "Could not get nsNavHistory?!");
784 return NS_ERROR_FAILURE
;
787 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
788 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
789 "mozIVisitInfoCallback", aCallback
));
790 bool ignoreErrors
= false, ignoreResults
= false;
792 // We ignore errors from either of these methods in case old JS consumers
793 // don't implement them (in which case they will get error/result
794 // notifications as normal).
795 Unused
<< aCallback
->GetIgnoreErrors(&ignoreErrors
);
796 Unused
<< aCallback
->GetIgnoreResults(&ignoreResults
);
798 RefPtr
<InsertVisitedURIs
> event
= new InsertVisitedURIs(
799 aConnection
, std::move(aPlaces
), callback
, ignoreErrors
, ignoreResults
,
800 aInitialUpdatedCount
);
802 // Get the target thread, and then start the work!
803 nsCOMPtr
<nsIEventTarget
> target
= do_GetInterface(aConnection
);
804 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
805 nsresult rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
806 NS_ENSURE_SUCCESS(rv
, rv
);
811 NS_IMETHOD
Run() override
{
812 MOZ_ASSERT(!NS_IsMainThread(),
813 "This should not be called on the main thread");
815 // The inner run method may bail out at any point, so we ensure we do
816 // whatever we can and then notify the main thread we're done.
817 nsresult rv
= InnerRun();
819 if (mSuccessfulUpdatedCount
> 0) {
820 NS_DispatchToMainThread(new NotifyRankingChanged());
823 NS_DispatchToMainThread(
824 new NotifyCompletion(mCallback
, mSuccessfulUpdatedCount
));
829 nsresult
InnerRun() {
830 MOZ_ASSERT(!NS_IsMainThread());
831 // Prevent Shutdown() from proceeding while this is running.
832 MutexAutoLock
lockedScope(mHistory
->mBlockShutdownMutex
);
833 // Check if we were already shutting down.
834 if (mHistory
->IsShuttingDown()) {
838 mozStorageTransaction
transaction(
839 mDBConn
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
841 // XXX Handle the error, bug 1696133.
842 Unused
<< NS_WARN_IF(NS_FAILED(transaction
.Start()));
844 const VisitData
* lastFetchedPlace
= nullptr;
845 uint32_t lastFetchedVisitCount
= 0;
846 bool shouldChunkNotifications
= mPlaces
.Length() > NOTIFY_VISITS_CHUNK_SIZE
;
847 nsTArray
<VisitData
> notificationChunk
;
848 if (shouldChunkNotifications
) {
849 notificationChunk
.SetCapacity(NOTIFY_VISITS_CHUNK_SIZE
);
851 for (nsTArray
<VisitData
>::size_type i
= 0; i
< mPlaces
.Length(); i
++) {
852 VisitData
& place
= mPlaces
.ElementAt(i
);
854 // Fetching from the database can overwrite this information, so save it
856 bool typed
= place
.typed
;
857 bool hidden
= place
.hidden
;
859 // We can avoid a database lookup if it's the same place as the last
862 lastFetchedPlace
&& lastFetchedPlace
->spec
.Equals(place
.spec
);
864 nsresult rv
= mHistory
->FetchPageInfo(place
, &known
);
866 if (!!mCallback
&& !mIgnoreErrors
) {
867 nsCOMPtr
<nsIRunnable
> event
=
868 new NotifyPlaceInfoCallback(mCallback
, place
, true, rv
);
869 return NS_DispatchToMainThread(event
);
873 lastFetchedPlace
= &mPlaces
.ElementAt(i
);
874 lastFetchedVisitCount
= lastFetchedPlace
->visitCount
;
876 // Copy over the data from the already known place.
877 place
.placeId
= lastFetchedPlace
->placeId
;
878 place
.guid
= lastFetchedPlace
->guid
;
879 place
.lastVisitId
= lastFetchedPlace
->visitId
;
880 place
.lastVisitTime
= lastFetchedPlace
->visitTime
;
881 if (!place
.title
.IsVoid()) {
882 place
.titleChanged
= !lastFetchedPlace
->title
.Equals(place
.title
);
884 place
.frecency
= lastFetchedPlace
->frecency
;
885 // Add one visit for the previous loop.
886 place
.visitCount
= ++lastFetchedVisitCount
;
889 // If any transition is typed, ensure the page is marked as typed.
890 if (typed
!= lastFetchedPlace
->typed
) {
894 // If any transition is visible, ensure the page is marked as visible.
895 if (hidden
!= lastFetchedPlace
->hidden
) {
896 place
.hidden
= false;
899 // If this is a new page, or the existing page was already visible,
900 // there's no need to try to unhide it.
901 if (!known
|| !lastFetchedPlace
->hidden
) {
902 place
.shouldUpdateHidden
= false;
905 FetchReferrerInfo(place
);
907 nsresult rv
= DoDatabaseInserts(known
, place
);
909 // Check if consumers wanted to be notified about success/failure,
910 // depending on whether this action succeeded or not.
911 if ((NS_SUCCEEDED(rv
) && !mIgnoreResults
) ||
912 (NS_FAILED(rv
) && !mIgnoreErrors
)) {
913 nsCOMPtr
<nsIRunnable
> event
=
914 new NotifyPlaceInfoCallback(mCallback
, place
, true, rv
);
915 nsresult rv2
= NS_DispatchToMainThread(event
);
916 NS_ENSURE_SUCCESS(rv2
, rv2
);
919 NS_ENSURE_SUCCESS(rv
, rv
);
921 if (shouldChunkNotifications
) {
922 int32_t numRemaining
= mPlaces
.Length() - (i
+ 1);
923 notificationChunk
.AppendElement(place
);
924 if (notificationChunk
.Length() == NOTIFY_VISITS_CHUNK_SIZE
||
926 nsCOMPtr
<nsIRunnable
> event
=
927 new NotifyManyVisitsObservers(std::move(notificationChunk
));
928 rv
= NS_DispatchToMainThread(event
);
929 NS_ENSURE_SUCCESS(rv
, rv
);
931 int32_t nextCapacity
=
932 std::min(NOTIFY_VISITS_CHUNK_SIZE
, numRemaining
);
933 notificationChunk
.SetCapacity(nextCapacity
);
937 // If we get here, we must have been successful adding/updating this
938 // visit/place, so update the count:
939 mSuccessfulUpdatedCount
++;
943 // Trigger insertions for all the new origins of the places we inserted.
944 nsAutoCString
query("DELETE FROM moz_updateoriginsinsert_temp");
945 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(query
);
946 NS_ENSURE_STATE(stmt
);
947 mozStorageStatementScoper
scoper(stmt
);
948 nsresult rv
= stmt
->Execute();
949 NS_ENSURE_SUCCESS(rv
, rv
);
953 // Trigger frecency updates for all those origins.
954 nsAutoCString
query("DELETE FROM moz_updateoriginsupdate_temp");
955 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(query
);
956 NS_ENSURE_STATE(stmt
);
957 mozStorageStatementScoper
scoper(stmt
);
958 nsresult rv
= stmt
->Execute();
959 NS_ENSURE_SUCCESS(rv
, rv
);
962 nsresult rv
= transaction
.Commit();
963 NS_ENSURE_SUCCESS(rv
, rv
);
965 // If we don't need to chunk the notifications, just notify using the
966 // original mPlaces array.
967 if (!shouldChunkNotifications
) {
968 nsCOMPtr
<nsIRunnable
> event
=
969 new NotifyManyVisitsObservers(std::move(mPlaces
));
970 rv
= NS_DispatchToMainThread(event
);
971 NS_ENSURE_SUCCESS(rv
, rv
);
979 mozIStorageConnection
* aConnection
, nsTArray
<VisitData
>&& aPlaces
,
980 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
981 bool aIgnoreErrors
, bool aIgnoreResults
, uint32_t aInitialUpdatedCount
)
982 : Runnable("places::InsertVisitedURIs"),
983 mDBConn(aConnection
),
984 mPlaces(std::move(aPlaces
)),
985 mCallback(aCallback
),
986 mIgnoreErrors(aIgnoreErrors
),
987 mIgnoreResults(aIgnoreResults
),
988 mSuccessfulUpdatedCount(aInitialUpdatedCount
),
989 mHistory(History::GetService()) {
990 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
993 for (nsTArray
<VisitData
>::size_type i
= 0; i
< mPlaces
.Length(); i
++) {
994 nsCOMPtr
<nsIURI
> uri
;
995 MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri
), mPlaces
[i
].spec
)));
996 MOZ_ASSERT(CanAddURI(uri
),
997 "Passed a VisitData with a URI we cannot add to history!");
1003 * Inserts or updates the entry in moz_places for this visit, adds the visit,
1004 * and updates the frecency of the place.
1007 * True if we already have an entry for this place in moz_places, false
1010 * The place we are adding a visit for.
1012 nsresult
DoDatabaseInserts(bool aKnown
, VisitData
& aPlace
) {
1013 MOZ_ASSERT(!NS_IsMainThread(),
1014 "This should not be called on the main thread");
1016 // If the page was in moz_places, we need to update the entry.
1019 rv
= mHistory
->UpdatePlace(aPlace
);
1020 NS_ENSURE_SUCCESS(rv
, rv
);
1022 // Otherwise, the page was not in moz_places, so now we have to add it.
1024 rv
= mHistory
->InsertPlace(aPlace
);
1025 NS_ENSURE_SUCCESS(rv
, rv
);
1026 aPlace
.placeId
= nsNavHistory::sLastInsertedPlaceId
;
1028 MOZ_ASSERT(aPlace
.placeId
> 0);
1030 rv
= AddVisit(aPlace
);
1031 NS_ENSURE_SUCCESS(rv
, rv
);
1033 // TODO (bug 623969) we shouldn't update this after each visit, but
1034 // rather only for each unique place to save disk I/O.
1036 // Don't update frecency if the page should not appear in autocomplete.
1037 if (aPlace
.shouldUpdateFrecency
) {
1038 rv
= UpdateFrecency(aPlace
);
1039 NS_ENSURE_SUCCESS(rv
, rv
);
1046 * Fetches information about a referrer for aPlace if it was a recent
1050 * The VisitData for the visit we will eventually add.
1053 void FetchReferrerInfo(VisitData
& aPlace
) {
1054 if (aPlace
.referrerSpec
.IsEmpty()) {
1059 referrer
.spec
= aPlace
.referrerSpec
;
1060 // If the referrer is the same as the page, we don't need to fetch it.
1061 if (aPlace
.referrerSpec
.Equals(aPlace
.spec
)) {
1063 // The page last visit id is also the referrer visit id.
1064 aPlace
.referrerVisitId
= aPlace
.lastVisitId
;
1066 bool exists
= false;
1067 if (NS_SUCCEEDED(mHistory
->FetchPageInfo(referrer
, &exists
)) && exists
) {
1068 // Copy the referrer last visit id.
1069 aPlace
.referrerVisitId
= referrer
.lastVisitId
;
1073 // Check if the page has effectively been visited recently, otherwise
1074 // discard the referrer info.
1075 if (!aPlace
.referrerVisitId
|| !referrer
.lastVisitTime
||
1076 aPlace
.visitTime
- referrer
.lastVisitTime
> RECENT_EVENT_THRESHOLD
) {
1077 // We will not be using the referrer data.
1078 aPlace
.referrerSpec
.Truncate();
1079 aPlace
.referrerVisitId
= 0;
1084 * Adds a visit for _place and updates it with the right visit id.
1087 * The VisitData for the place we need to know visit information about.
1089 nsresult
AddVisit(VisitData
& _place
) {
1090 MOZ_ASSERT(_place
.placeId
> 0);
1093 nsCOMPtr
<mozIStorageStatement
> stmt
;
1094 stmt
= mHistory
->GetStatement(
1095 "INSERT INTO moz_historyvisits "
1096 "(from_visit, place_id, visit_date, visit_type, session) "
1097 "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) ");
1098 NS_ENSURE_STATE(stmt
);
1099 mozStorageStatementScoper
scoper(stmt
);
1101 rv
= stmt
->BindInt64ByName("page_id"_ns
, _place
.placeId
);
1102 NS_ENSURE_SUCCESS(rv
, rv
);
1103 rv
= stmt
->BindInt64ByName("from_visit"_ns
, _place
.referrerVisitId
);
1104 NS_ENSURE_SUCCESS(rv
, rv
);
1105 rv
= stmt
->BindInt64ByName("visit_date"_ns
, _place
.visitTime
);
1106 NS_ENSURE_SUCCESS(rv
, rv
);
1107 uint32_t transitionType
= _place
.transitionType
;
1108 MOZ_ASSERT(transitionType
>= nsINavHistoryService::TRANSITION_LINK
&&
1109 transitionType
<= nsINavHistoryService::TRANSITION_RELOAD
,
1110 "Invalid transition type!");
1111 rv
= stmt
->BindInt32ByName("visit_type"_ns
, transitionType
);
1112 NS_ENSURE_SUCCESS(rv
, rv
);
1114 rv
= stmt
->Execute();
1115 NS_ENSURE_SUCCESS(rv
, rv
);
1117 _place
.visitId
= nsNavHistory::sLastInsertedVisitId
;
1118 MOZ_ASSERT(_place
.visitId
> 0);
1124 * Updates the frecency, and possibly the hidden-ness of aPlace.
1127 * The VisitData for the place we want to update.
1129 nsresult
UpdateFrecency(const VisitData
& aPlace
) {
1130 MOZ_ASSERT(aPlace
.shouldUpdateFrecency
);
1131 MOZ_ASSERT(aPlace
.placeId
> 0);
1134 { // First, set our frecency to the proper value.
1135 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(
1136 "UPDATE moz_places "
1137 "SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
1138 "WHERE id = :page_id");
1139 NS_ENSURE_STATE(stmt
);
1140 mozStorageStatementScoper
scoper(stmt
);
1142 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1143 NS_ENSURE_SUCCESS(rv
, rv
);
1145 stmt
->BindInt32ByName("redirect"_ns
, aPlace
.useFrecencyRedirectBonus
);
1146 NS_ENSURE_SUCCESS(rv
, rv
);
1148 rv
= stmt
->Execute();
1149 NS_ENSURE_SUCCESS(rv
, rv
);
1152 if (!aPlace
.hidden
&& aPlace
.shouldUpdateHidden
) {
1153 // Mark the page as not hidden if the frecency is now nonzero.
1154 nsCOMPtr
<mozIStorageStatement
> stmt
;
1155 stmt
= mHistory
->GetStatement(
1156 "UPDATE moz_places "
1158 "WHERE id = :page_id AND frecency <> 0");
1159 NS_ENSURE_STATE(stmt
);
1160 mozStorageStatementScoper
scoper(stmt
);
1162 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1163 NS_ENSURE_SUCCESS(rv
, rv
);
1165 rv
= stmt
->Execute();
1166 NS_ENSURE_SUCCESS(rv
, rv
);
1172 mozIStorageConnection
* mDBConn
;
1174 nsTArray
<VisitData
> mPlaces
;
1176 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
1180 bool mIgnoreResults
;
1182 uint32_t mSuccessfulUpdatedCount
;
1185 * Strong reference to the History object because we do not want it to
1186 * disappear out from under us.
1188 RefPtr
<History
> mHistory
;
1192 * Sets the page title for a page in moz_places (if necessary).
1194 class SetPageTitle
: public Runnable
{
1197 * Sets a pages title in the database asynchronously.
1199 * @param aConnection
1200 * The database connection to use for this operation.
1202 * The URI to set the page title on.
1204 * The title to set for the page, if the page exists.
1206 static nsresult
Start(mozIStorageConnection
* aConnection
, nsIURI
* aURI
,
1207 const nsAString
& aTitle
) {
1208 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1209 MOZ_ASSERT(aURI
, "Must pass a non-null URI object!");
1212 nsresult rv
= aURI
->GetSpec(spec
);
1213 NS_ENSURE_SUCCESS(rv
, rv
);
1215 RefPtr
<SetPageTitle
> event
= new SetPageTitle(spec
, aTitle
);
1217 // Get the target thread, and then start the work!
1218 nsCOMPtr
<nsIEventTarget
> target
= do_GetInterface(aConnection
);
1219 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
1220 rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
1221 NS_ENSURE_SUCCESS(rv
, rv
);
1226 NS_IMETHOD
Run() override
{
1227 MOZ_ASSERT(!NS_IsMainThread(),
1228 "This should not be called on the main thread");
1230 // First, see if the page exists in the database (we'll need its id later).
1232 nsresult rv
= mHistory
->FetchPageInfo(mPlace
, &exists
);
1233 NS_ENSURE_SUCCESS(rv
, rv
);
1235 if (!exists
|| !mPlace
.titleChanged
) {
1236 // We have no record of this page, or we have no title change, so there
1237 // is no need to do any further work.
1241 MOZ_ASSERT(mPlace
.placeId
> 0, "We somehow have an invalid place id here!");
1243 // Now we can update our database record.
1244 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(
1245 "UPDATE moz_places "
1246 "SET title = :page_title "
1247 "WHERE id = :page_id ");
1248 NS_ENSURE_STATE(stmt
);
1251 mozStorageStatementScoper
scoper(stmt
);
1252 rv
= stmt
->BindInt64ByName("page_id"_ns
, mPlace
.placeId
);
1253 NS_ENSURE_SUCCESS(rv
, rv
);
1254 // Empty strings should clear the title, just like
1255 // nsNavHistory::SetPageTitle.
1256 if (mPlace
.title
.IsEmpty()) {
1257 rv
= stmt
->BindNullByName("page_title"_ns
);
1259 rv
= stmt
->BindStringByName("page_title"_ns
,
1260 StringHead(mPlace
.title
, TITLE_LENGTH_MAX
));
1262 NS_ENSURE_SUCCESS(rv
, rv
);
1263 rv
= stmt
->Execute();
1264 NS_ENSURE_SUCCESS(rv
, rv
);
1267 nsCOMPtr
<nsIRunnable
> event
=
1268 new NotifyTitleObservers(mPlace
.spec
, mPlace
.title
, mPlace
.guid
);
1269 rv
= NS_DispatchToMainThread(event
);
1270 NS_ENSURE_SUCCESS(rv
, rv
);
1276 SetPageTitle(const nsCString
& aSpec
, const nsAString
& aTitle
)
1277 : Runnable("places::SetPageTitle"), mHistory(History::GetService()) {
1278 mPlace
.spec
= aSpec
;
1279 mPlace
.title
= aTitle
;
1285 * Strong reference to the History object because we do not want it to
1286 * disappear out from under us.
1288 RefPtr
<History
> mHistory
;
1292 * Stores an embed visit, and notifies observers.
1295 * The VisitData of the visit to store as an embed visit.
1296 * @param [optional] aCallback
1297 * The mozIVisitInfoCallback to notify, if provided.
1299 * FIXME(emilio, bug 1595484): We should get rid of EMBED visits completely.
1301 void NotifyEmbedVisit(VisitData
& aPlace
,
1302 mozIVisitInfoCallback
* aCallback
= nullptr) {
1303 MOZ_ASSERT(aPlace
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
,
1304 "Must only pass TRANSITION_EMBED visits to this!");
1305 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
1307 nsCOMPtr
<nsIURI
> uri
;
1308 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), aPlace
.spec
));
1315 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
1316 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
1317 "mozIVisitInfoCallback", aCallback
));
1318 bool ignoreResults
= false;
1319 Unused
<< aCallback
->GetIgnoreResults(&ignoreResults
);
1320 if (!ignoreResults
) {
1321 nsCOMPtr
<nsIRunnable
> event
=
1322 new NotifyPlaceInfoCallback(callback
, aPlace
, true, NS_OK
);
1323 (void)NS_DispatchToMainThread(event
);
1327 nsCOMPtr
<nsIRunnable
> event
= new NotifyManyVisitsObservers(aPlace
);
1328 (void)NS_DispatchToMainThread(event
);
1331 ////////////////////////////////////////////////////////////////////////////////
1334 History
* History::gService
= nullptr;
1337 : mShuttingDown(false),
1338 mShuttingDownMutex("History::mShuttingDownMutex"),
1339 mBlockShutdownMutex("History::mBlockShutdownMutex"),
1340 mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE
) {
1341 NS_ASSERTION(!gService
, "Ruh-roh! This service has already been created!");
1342 if (XRE_IsParentProcess()) {
1343 nsCOMPtr
<nsIProperties
> dirsvc
= components::Directory::Service();
1344 bool haveProfile
= false;
1348 dirsvc
->Has(NS_APP_USER_PROFILE_50_DIR
, &haveProfile
)) &&
1350 "Can't construct history service if there is no profile.");
1354 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
1355 NS_WARNING_ASSERTION(os
, "Observer service was not found!");
1357 (void)os
->AddObserver(this, TOPIC_PLACES_SHUTDOWN
, false);
1361 History::~History() {
1362 UnregisterWeakMemoryReporter(this);
1364 MOZ_ASSERT(gService
== this);
1368 void History::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
1370 class ConcurrentStatementsHolder final
: public mozIStorageCompletionCallback
{
1374 explicit ConcurrentStatementsHolder(mozIStorageConnection
* aDBConn
)
1375 : mShutdownWasInvoked(false) {
1376 DebugOnly
<nsresult
> rv
= aDBConn
->AsyncClone(true, this);
1377 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1380 NS_IMETHOD
Complete(nsresult aStatus
, nsISupports
* aConnection
) override
{
1381 if (NS_FAILED(aStatus
)) {
1384 mReadOnlyDBConn
= do_QueryInterface(aConnection
);
1385 // It's possible Shutdown was invoked before we were handed back the
1386 // cloned connection handle.
1387 if (mShutdownWasInvoked
) {
1392 // Now we can create our cached statements.
1394 if (!mIsVisitedStatement
) {
1395 (void)mReadOnlyDBConn
->CreateAsyncStatement(
1396 nsLiteralCString("SELECT 1 FROM moz_places h "
1397 "WHERE url_hash = hash(?1) AND url = ?1 AND "
1398 "last_visit_date NOTNULL "),
1399 getter_AddRefs(mIsVisitedStatement
));
1400 MOZ_ASSERT(mIsVisitedStatement
);
1401 auto queries
= std::move(mVisitedQueries
);
1402 if (mIsVisitedStatement
) {
1403 for (auto& query
: queries
) {
1404 query
->Execute(*mIsVisitedStatement
);
1412 void QueueVisitedStatement(RefPtr
<VisitedQuery
> aCallback
) {
1413 if (mIsVisitedStatement
) {
1414 aCallback
->Execute(*mIsVisitedStatement
);
1416 mVisitedQueries
.AppendElement(std::move(aCallback
));
1421 mShutdownWasInvoked
= true;
1422 if (mReadOnlyDBConn
) {
1423 mVisitedQueries
.Clear();
1424 DebugOnly
<nsresult
> rv
;
1425 if (mIsVisitedStatement
) {
1426 rv
= mIsVisitedStatement
->Finalize();
1427 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1429 rv
= mReadOnlyDBConn
->AsyncClose(nullptr);
1430 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1431 mReadOnlyDBConn
= nullptr;
1436 ~ConcurrentStatementsHolder() = default;
1438 nsCOMPtr
<mozIStorageAsyncConnection
> mReadOnlyDBConn
;
1439 nsCOMPtr
<mozIStorageAsyncStatement
> mIsVisitedStatement
;
1440 nsTArray
<RefPtr
<VisitedQuery
>> mVisitedQueries
;
1441 bool mShutdownWasInvoked
;
1444 NS_IMPL_ISUPPORTS(ConcurrentStatementsHolder
, mozIStorageCompletionCallback
)
1446 nsresult
History::QueueVisitedStatement(RefPtr
<VisitedQuery
> aQuery
) {
1447 MOZ_ASSERT(NS_IsMainThread());
1448 if (IsShuttingDown()) {
1449 return NS_ERROR_NOT_AVAILABLE
;
1452 if (!mConcurrentStatementsHolder
) {
1453 mozIStorageConnection
* dbConn
= GetDBConn();
1454 NS_ENSURE_STATE(dbConn
);
1455 mConcurrentStatementsHolder
= new ConcurrentStatementsHolder(dbConn
);
1457 mConcurrentStatementsHolder
->QueueVisitedStatement(std::move(aQuery
));
1461 nsresult
History::InsertPlace(VisitData
& aPlace
) {
1462 MOZ_ASSERT(aPlace
.placeId
== 0, "should not have a valid place id!");
1463 MOZ_ASSERT(!aPlace
.shouldUpdateHidden
, "We should not need to update hidden");
1464 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1466 nsCOMPtr
<mozIStorageStatement
> stmt
= GetStatement(
1467 "INSERT INTO moz_places "
1468 "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
1469 "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, "
1470 ":frecency, :guid) ");
1471 NS_ENSURE_STATE(stmt
);
1472 mozStorageStatementScoper
scoper(stmt
);
1474 nsresult rv
= stmt
->BindStringByName("rev_host"_ns
, aPlace
.revHost
);
1475 NS_ENSURE_SUCCESS(rv
, rv
);
1476 rv
= URIBinder::Bind(stmt
, "url"_ns
, aPlace
.spec
);
1477 NS_ENSURE_SUCCESS(rv
, rv
);
1478 nsString title
= aPlace
.title
;
1479 // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
1480 if (title
.IsEmpty()) {
1481 rv
= stmt
->BindNullByName("title"_ns
);
1483 title
.Assign(StringHead(aPlace
.title
, TITLE_LENGTH_MAX
));
1484 rv
= stmt
->BindStringByName("title"_ns
, title
);
1486 NS_ENSURE_SUCCESS(rv
, rv
);
1487 rv
= stmt
->BindInt32ByName("typed"_ns
, aPlace
.typed
);
1488 NS_ENSURE_SUCCESS(rv
, rv
);
1489 // When inserting a page for a first visit that should not appear in
1490 // autocomplete, for example an error page, use a zero frecency.
1491 int32_t frecency
= aPlace
.shouldUpdateFrecency
? aPlace
.frecency
: 0;
1492 rv
= stmt
->BindInt32ByName("frecency"_ns
, frecency
);
1493 NS_ENSURE_SUCCESS(rv
, rv
);
1494 rv
= stmt
->BindInt32ByName("hidden"_ns
, aPlace
.hidden
);
1495 NS_ENSURE_SUCCESS(rv
, rv
);
1496 if (aPlace
.guid
.IsVoid()) {
1497 rv
= GenerateGUID(aPlace
.guid
);
1498 NS_ENSURE_SUCCESS(rv
, rv
);
1500 rv
= stmt
->BindUTF8StringByName("guid"_ns
, aPlace
.guid
);
1501 NS_ENSURE_SUCCESS(rv
, rv
);
1502 rv
= stmt
->Execute();
1503 NS_ENSURE_SUCCESS(rv
, rv
);
1508 nsresult
History::UpdatePlace(const VisitData
& aPlace
) {
1509 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1510 MOZ_ASSERT(aPlace
.placeId
> 0, "must have a valid place id!");
1511 MOZ_ASSERT(!aPlace
.guid
.IsVoid(), "must have a guid!");
1513 nsCOMPtr
<mozIStorageStatement
> stmt
;
1514 bool titleIsVoid
= aPlace
.title
.IsVoid();
1516 // Don't change the title.
1517 stmt
= GetStatement(
1518 "UPDATE moz_places "
1519 "SET hidden = :hidden, "
1522 "WHERE id = :page_id ");
1524 stmt
= GetStatement(
1525 "UPDATE moz_places "
1526 "SET title = :title, "
1527 "hidden = :hidden, "
1530 "WHERE id = :page_id ");
1532 NS_ENSURE_STATE(stmt
);
1533 mozStorageStatementScoper
scoper(stmt
);
1537 // An empty string clears the title.
1538 if (aPlace
.title
.IsEmpty()) {
1539 rv
= stmt
->BindNullByName("title"_ns
);
1541 rv
= stmt
->BindStringByName("title"_ns
,
1542 StringHead(aPlace
.title
, TITLE_LENGTH_MAX
));
1544 NS_ENSURE_SUCCESS(rv
, rv
);
1546 rv
= stmt
->BindInt32ByName("typed"_ns
, aPlace
.typed
);
1547 NS_ENSURE_SUCCESS(rv
, rv
);
1548 rv
= stmt
->BindInt32ByName("hidden"_ns
, aPlace
.hidden
);
1549 NS_ENSURE_SUCCESS(rv
, rv
);
1550 rv
= stmt
->BindUTF8StringByName("guid"_ns
, aPlace
.guid
);
1551 NS_ENSURE_SUCCESS(rv
, rv
);
1552 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1553 NS_ENSURE_SUCCESS(rv
, rv
);
1554 rv
= stmt
->Execute();
1555 NS_ENSURE_SUCCESS(rv
, rv
);
1560 nsresult
History::FetchPageInfo(VisitData
& _place
, bool* _exists
) {
1561 MOZ_ASSERT(!_place
.spec
.IsEmpty() || !_place
.guid
.IsEmpty(),
1562 "must have either a non-empty spec or guid!");
1563 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1567 // URI takes precedence.
1568 nsCOMPtr
<mozIStorageStatement
> stmt
;
1569 bool selectByURI
= !_place
.spec
.IsEmpty();
1571 stmt
= GetStatement(
1572 "SELECT guid, id, title, hidden, typed, frecency, visit_count, "
1574 "(SELECT id FROM moz_historyvisits "
1575 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1577 "FROM moz_places h "
1578 "WHERE url_hash = hash(:page_url) AND url = :page_url ");
1579 NS_ENSURE_STATE(stmt
);
1581 rv
= URIBinder::Bind(stmt
, "page_url"_ns
, _place
.spec
);
1582 NS_ENSURE_SUCCESS(rv
, rv
);
1584 stmt
= GetStatement(
1585 "SELECT url, id, title, hidden, typed, frecency, visit_count, "
1587 "(SELECT id FROM moz_historyvisits "
1588 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1590 "FROM moz_places h "
1591 "WHERE guid = :guid ");
1592 NS_ENSURE_STATE(stmt
);
1594 rv
= stmt
->BindUTF8StringByName("guid"_ns
, _place
.guid
);
1595 NS_ENSURE_SUCCESS(rv
, rv
);
1598 mozStorageStatementScoper
scoper(stmt
);
1600 rv
= stmt
->ExecuteStep(_exists
);
1601 NS_ENSURE_SUCCESS(rv
, rv
);
1608 if (_place
.guid
.IsEmpty()) {
1609 rv
= stmt
->GetUTF8String(0, _place
.guid
);
1610 NS_ENSURE_SUCCESS(rv
, rv
);
1614 rv
= stmt
->GetUTF8String(0, spec
);
1615 NS_ENSURE_SUCCESS(rv
, rv
);
1619 rv
= stmt
->GetInt64(1, &_place
.placeId
);
1620 NS_ENSURE_SUCCESS(rv
, rv
);
1623 rv
= stmt
->GetString(2, title
);
1624 NS_ENSURE_SUCCESS(rv
, rv
);
1626 // If the title we were given was void, that means we did not bother to set
1627 // it to anything. As a result, ignore the fact that we may have changed the
1628 // title (because we don't want to, that would be empty), and set the title
1629 // to what is currently stored in the datbase.
1630 if (_place
.title
.IsVoid()) {
1631 _place
.title
= title
;
1633 // Otherwise, just indicate if the title has changed.
1635 _place
.titleChanged
= !(_place
.title
.Equals(title
)) &&
1636 !(_place
.title
.IsEmpty() && title
.IsVoid());
1640 rv
= stmt
->GetInt32(3, &hidden
);
1641 NS_ENSURE_SUCCESS(rv
, rv
);
1642 _place
.hidden
= !!hidden
;
1645 rv
= stmt
->GetInt32(4, &typed
);
1646 NS_ENSURE_SUCCESS(rv
, rv
);
1647 _place
.typed
= !!typed
;
1649 rv
= stmt
->GetInt32(5, &_place
.frecency
);
1650 NS_ENSURE_SUCCESS(rv
, rv
);
1652 rv
= stmt
->GetInt32(6, &visitCount
);
1653 NS_ENSURE_SUCCESS(rv
, rv
);
1654 _place
.visitCount
= visitCount
;
1655 rv
= stmt
->GetInt64(7, &_place
.lastVisitTime
);
1656 NS_ENSURE_SUCCESS(rv
, rv
);
1657 rv
= stmt
->GetInt64(8, &_place
.lastVisitId
);
1658 NS_ENSURE_SUCCESS(rv
, rv
);
1663 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf
)
1666 History::CollectReports(nsIHandleReportCallback
* aHandleReport
,
1667 nsISupports
* aData
, bool aAnonymize
) {
1669 "explicit/history-links-hashtable", KIND_HEAP
, UNITS_BYTES
,
1670 SizeOfIncludingThis(HistoryMallocSizeOf
),
1671 "Memory used by the hashtable that records changes to the visited state "
1677 size_t History::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) {
1678 size_t size
= aMallocSizeOf(this);
1679 size
+= mTrackedURIs
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1680 for (const auto& entry
: mTrackedURIs
.Values()) {
1681 size
+= entry
.SizeOfExcludingThis(aMallocSizeOf
);
1687 History
* History::GetService() {
1692 nsCOMPtr
<IHistory
> service
= components::History::Service();
1694 NS_ASSERTION(gService
, "Our constructor was not run?!");
1701 already_AddRefed
<History
> History::GetSingleton() {
1703 RefPtr
<History
> svc
= new History();
1704 MOZ_ASSERT(gService
== svc
.get());
1705 svc
->InitMemoryReporter();
1706 return svc
.forget();
1709 return do_AddRef(gService
);
1712 mozIStorageConnection
* History::GetDBConn() {
1713 MOZ_ASSERT(NS_IsMainThread());
1714 if (IsShuttingDown()) {
1718 mDB
= Database::GetDatabase();
1719 NS_ENSURE_TRUE(mDB
, nullptr);
1720 // This must happen on the main-thread, so when we try to use the connection
1721 // later it's initialized.
1722 mDB
->EnsureConnection();
1723 NS_ENSURE_TRUE(mDB
, nullptr);
1725 return mDB
->MainConn();
1728 const mozIStorageConnection
* History::GetConstDBConn() {
1729 MOZ_ASSERT(!NS_IsMainThread());
1731 MOZ_ASSERT(mDB
|| IsShuttingDown());
1732 if (IsShuttingDown() || !mDB
) {
1736 return mDB
->MainConn();
1739 void History::Shutdown() {
1740 MOZ_ASSERT(NS_IsMainThread());
1741 MutexAutoLock
lockedScope(mBlockShutdownMutex
);
1743 MutexAutoLock
lockedScope(mShuttingDownMutex
);
1744 MOZ_ASSERT(!mShuttingDown
&& "Shutdown was called more than once!");
1745 mShuttingDown
= true;
1747 if (mConcurrentStatementsHolder
) {
1748 mConcurrentStatementsHolder
->Shutdown();
1752 void History::AppendToRecentlyVisitedURIs(nsIURI
* aURI
, bool aHidden
) {
1753 PRTime now
= PR_Now();
1755 mRecentlyVisitedURIs
.InsertOrUpdate(aURI
, RecentURIVisit
{now
, aHidden
});
1757 // Remove entries older than RECENTLY_VISITED_URIS_MAX_AGE.
1758 for (auto iter
= mRecentlyVisitedURIs
.Iter(); !iter
.Done(); iter
.Next()) {
1759 if ((now
- iter
.Data().mTime
) > RECENTLY_VISITED_URIS_MAX_AGE
) {
1765 ////////////////////////////////////////////////////////////////////////////////
1769 History::VisitURI(nsIWidget
* aWidget
, nsIURI
* aURI
, nsIURI
* aLastVisitedURI
,
1771 MOZ_ASSERT(NS_IsMainThread());
1772 NS_ENSURE_ARG(aURI
);
1774 if (IsShuttingDown()) {
1779 if (XRE_IsContentProcess()) {
1780 if (!BaseHistory::CanStore(aURI
)) {
1784 NS_ENSURE_ARG(aWidget
);
1785 BrowserChild
* browserChild
= aWidget
->GetOwningBrowserChild();
1786 NS_ENSURE_TRUE(browserChild
, NS_ERROR_FAILURE
);
1787 (void)browserChild
->SendVisitURI(aURI
, aLastVisitedURI
, aFlags
);
1791 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
1792 NS_ENSURE_TRUE(navHistory
, NS_ERROR_OUT_OF_MEMORY
);
1794 // Silently return if URI is something we shouldn't add to DB.
1796 rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
1797 NS_ENSURE_SUCCESS(rv
, rv
);
1802 bool reload
= false;
1803 if (aLastVisitedURI
) {
1804 rv
= aURI
->Equals(aLastVisitedURI
, &reload
);
1805 NS_ENSURE_SUCCESS(rv
, rv
);
1808 nsTArray
<VisitData
> placeArray(1);
1809 placeArray
.AppendElement(VisitData(aURI
, aLastVisitedURI
));
1810 VisitData
& place
= placeArray
.ElementAt(0);
1811 NS_ENSURE_FALSE(place
.spec
.IsEmpty(), NS_ERROR_INVALID_ARG
);
1813 place
.visitTime
= PR_Now();
1815 // Assigns a type to the edge in the visit linked list. Each type will be
1816 // considered differently when weighting the frecency of a location.
1817 uint32_t recentFlags
= navHistory
->GetRecentFlags(aURI
);
1818 bool isFollowedLink
= recentFlags
& nsNavHistory::RECENT_ACTIVATED
;
1820 // Embed visits should never be added to the database, and the same is valid
1821 // for redirects across frames.
1822 // For the above reasoning non-toplevel transitions are handled at first.
1823 // if the visit is toplevel or a non-toplevel followed link, then it can be
1824 // handled as usual and stored on disk.
1826 uint32_t transitionType
= nsINavHistoryService::TRANSITION_LINK
;
1828 if (!(aFlags
& IHistory::TOP_LEVEL
) && !isFollowedLink
) {
1829 // A frame redirected to a new site without user interaction.
1830 transitionType
= nsINavHistoryService::TRANSITION_EMBED
;
1831 } else if (aFlags
& IHistory::REDIRECT_TEMPORARY
) {
1832 transitionType
= nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
;
1833 } else if (aFlags
& IHistory::REDIRECT_PERMANENT
) {
1834 transitionType
= nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT
;
1835 } else if (reload
) {
1836 transitionType
= nsINavHistoryService::TRANSITION_RELOAD
;
1837 } else if ((recentFlags
& nsNavHistory::RECENT_TYPED
) &&
1838 !(aFlags
& IHistory::UNRECOVERABLE_ERROR
)) {
1839 // Don't mark error pages as typed, even if they were actually typed by
1840 // the user. This is useful to limit their score in autocomplete.
1841 transitionType
= nsINavHistoryService::TRANSITION_TYPED
;
1842 } else if (recentFlags
& nsNavHistory::RECENT_BOOKMARKED
) {
1843 transitionType
= nsINavHistoryService::TRANSITION_BOOKMARK
;
1844 } else if (!(aFlags
& IHistory::TOP_LEVEL
) && isFollowedLink
) {
1845 // User activated a link in a frame.
1846 transitionType
= nsINavHistoryService::TRANSITION_FRAMED_LINK
;
1849 place
.SetTransitionType(transitionType
);
1850 bool isRedirect
= aFlags
& IHistory::REDIRECT_SOURCE
;
1852 place
.useFrecencyRedirectBonus
=
1853 (aFlags
& IHistory::REDIRECT_SOURCE_PERMANENT
) ||
1854 transitionType
!= nsINavHistoryService::TRANSITION_TYPED
;
1856 place
.hidden
= GetHiddenState(isRedirect
, place
.transitionType
);
1858 // Error pages should never be autocompleted.
1859 if (aFlags
& IHistory::UNRECOVERABLE_ERROR
) {
1860 place
.shouldUpdateFrecency
= false;
1863 // Do not save a reloaded uri if we have visited the same URI recently.
1865 auto entry
= mRecentlyVisitedURIs
.Lookup(aURI
);
1866 // Check if the entry exists and is younger than
1867 // RECENTLY_VISITED_URIS_MAX_AGE.
1868 if (entry
&& (PR_Now() - entry
->mTime
) < RECENTLY_VISITED_URIS_MAX_AGE
) {
1869 bool wasHidden
= entry
->mHidden
;
1870 // Regardless of whether we store the visit or not, we must update the
1871 // stored visit time.
1872 AppendToRecentlyVisitedURIs(aURI
, place
.hidden
);
1873 // We always want to store an unhidden visit, if the previous visits were
1874 // hidden, because otherwise the page may not appear in the history UI.
1875 // This can happen for example at a page redirecting to itself.
1876 if (!wasHidden
|| place
.hidden
) {
1877 // We can skip this visit.
1883 // EMBED visits should not go through the database.
1884 // They exist only to keep track of isVisited status during the session.
1885 if (place
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
1886 NotifyEmbedVisit(place
);
1888 mozIStorageConnection
* dbConn
= GetDBConn();
1889 NS_ENSURE_STATE(dbConn
);
1891 rv
= InsertVisitedURIs::Start(dbConn
, std::move(placeArray
));
1892 NS_ENSURE_SUCCESS(rv
, rv
);
1899 History::SetURITitle(nsIURI
* aURI
, const nsAString
& aTitle
) {
1900 MOZ_ASSERT(NS_IsMainThread());
1901 NS_ENSURE_ARG(aURI
);
1903 if (IsShuttingDown()) {
1907 if (XRE_IsContentProcess()) {
1908 auto* cpc
= dom::ContentChild::GetSingleton();
1909 MOZ_ASSERT(cpc
, "Content Protocol is NULL!");
1910 Unused
<< cpc
->SendSetURITitle(aURI
, PromiseFlatString(aTitle
));
1914 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
1916 // At first, it seems like nav history should always be available here, no
1919 // nsNavHistory fails to register as a service if there is no profile in
1920 // place (for instance, if user is choosing a profile).
1922 // Maybe the correct thing to do is to not register this service if no
1923 // profile has been selected?
1925 NS_ENSURE_TRUE(navHistory
, NS_ERROR_FAILURE
);
1928 nsresult rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
1929 NS_ENSURE_SUCCESS(rv
, rv
);
1934 mozIStorageConnection
* dbConn
= GetDBConn();
1935 NS_ENSURE_STATE(dbConn
);
1937 return SetPageTitle::Start(dbConn
, aURI
, aTitle
);
1940 ////////////////////////////////////////////////////////////////////////////////
1941 //// mozIAsyncHistory
1944 History::UpdatePlaces(JS::Handle
<JS::Value
> aPlaceInfos
,
1945 mozIVisitInfoCallback
* aCallback
, JSContext
* aCtx
) {
1946 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED
);
1947 NS_ENSURE_TRUE(!aPlaceInfos
.isPrimitive(), NS_ERROR_INVALID_ARG
);
1949 uint32_t infosLength
;
1950 JS::Rooted
<JSObject
*> infos(aCtx
);
1951 nsresult rv
= GetJSArrayFromJSValue(aPlaceInfos
, aCtx
, &infos
, &infosLength
);
1952 NS_ENSURE_SUCCESS(rv
, rv
);
1954 uint32_t initialUpdatedCount
= 0;
1956 nsTArray
<VisitData
> visitData
;
1957 for (uint32_t i
= 0; i
< infosLength
; i
++) {
1958 JS::Rooted
<JSObject
*> info(aCtx
);
1959 nsresult rv
= GetJSObjectFromArray(aCtx
, infos
, i
, &info
);
1960 NS_ENSURE_SUCCESS(rv
, rv
);
1962 nsCOMPtr
<nsIURI
> uri
= GetURIFromJSObject(aCtx
, info
, "uri");
1966 GetStringFromJSObject(aCtx
, info
, "guid", fatGUID
);
1967 if (fatGUID
.IsVoid()) {
1968 guid
.SetIsVoid(true);
1970 CopyUTF16toUTF8(fatGUID
, guid
);
1974 // Make sure that any uri we are given can be added to history, and if not,
1975 // skip it (CanAddURI will notify our callback for us).
1976 if (uri
&& !CanAddURI(uri
, guid
, aCallback
)) {
1980 // We must have at least one of uri or guid.
1981 NS_ENSURE_ARG(uri
|| !guid
.IsVoid());
1983 // If we were given a guid, make sure it is valid.
1984 bool isValidGUID
= IsValidGUID(guid
);
1985 NS_ENSURE_ARG(guid
.IsVoid() || isValidGUID
);
1988 GetStringFromJSObject(aCtx
, info
, "title", title
);
1990 JS::Rooted
<JSObject
*> visits(aCtx
, nullptr);
1992 JS::Rooted
<JS::Value
> visitsVal(aCtx
);
1993 bool rc
= JS_GetProperty(aCtx
, info
, "visits", &visitsVal
);
1994 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
1995 if (!visitsVal
.isPrimitive()) {
1996 visits
= visitsVal
.toObjectOrNull();
1998 if (!JS::IsArrayObject(aCtx
, visits
, &isArray
)) {
1999 return NS_ERROR_UNEXPECTED
;
2002 return NS_ERROR_INVALID_ARG
;
2006 NS_ENSURE_ARG(visits
);
2008 uint32_t visitsLength
= 0;
2010 (void)JS::GetArrayLength(aCtx
, visits
, &visitsLength
);
2012 NS_ENSURE_ARG(visitsLength
> 0);
2014 // Check each visit, and build our array of VisitData objects.
2015 visitData
.SetCapacity(visitData
.Length() + visitsLength
);
2016 for (uint32_t j
= 0; j
< visitsLength
; j
++) {
2017 JS::Rooted
<JSObject
*> visit(aCtx
);
2018 rv
= GetJSObjectFromArray(aCtx
, visits
, j
, &visit
);
2019 NS_ENSURE_SUCCESS(rv
, rv
);
2021 VisitData
& data
= *visitData
.AppendElement(VisitData(uri
));
2022 if (!title
.IsEmpty()) {
2024 } else if (!title
.IsVoid()) {
2025 // Setting data.title to an empty string wouldn't make it non-void.
2026 data
.title
.SetIsVoid(false);
2030 // We must have a date and a transaction type!
2031 rv
= GetIntFromJSObject(aCtx
, visit
, "visitDate", &data
.visitTime
);
2032 NS_ENSURE_SUCCESS(rv
, rv
);
2033 // visitDate should be in microseconds. It's easy to do the wrong thing
2034 // and pass milliseconds to updatePlaces, so we lazily check for that.
2035 // While it's not easily distinguishable, since both are integers, we can
2036 // check if the value is very far in the past, and assume it's probably
2038 if (data
.visitTime
< (PR_Now() / 1000)) {
2040 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
2041 Unused
<< xpc
->DebugDumpJSStack(false, false, false);
2042 MOZ_CRASH("invalid time format passed to updatePlaces");
2044 return NS_ERROR_INVALID_ARG
;
2046 uint32_t transitionType
= 0;
2047 rv
= GetIntFromJSObject(aCtx
, visit
, "transitionType", &transitionType
);
2048 NS_ENSURE_SUCCESS(rv
, rv
);
2049 NS_ENSURE_ARG_RANGE(transitionType
, nsINavHistoryService::TRANSITION_LINK
,
2050 nsINavHistoryService::TRANSITION_RELOAD
);
2051 data
.SetTransitionType(transitionType
);
2052 data
.hidden
= GetHiddenState(false, transitionType
);
2054 // If the visit is an embed visit, we do not actually add it to the
2056 if (transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
2057 NotifyEmbedVisit(data
, aCallback
);
2058 visitData
.RemoveLastElement();
2059 initialUpdatedCount
++;
2063 // The referrer is optional.
2064 nsCOMPtr
<nsIURI
> referrer
=
2065 GetURIFromJSObject(aCtx
, visit
, "referrerURI");
2067 (void)referrer
->GetSpec(data
.referrerSpec
);
2072 mozIStorageConnection
* dbConn
= GetDBConn();
2073 NS_ENSURE_STATE(dbConn
);
2075 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
2076 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>("mozIVisitInfoCallback",
2079 // It is possible that all of the visits we were passed were dissallowed by
2080 // CanAddURI, which isn't an error. If we have no visits to add, however,
2081 // we should not call InsertVisitedURIs::Start.
2082 if (visitData
.Length()) {
2083 nsresult rv
= InsertVisitedURIs::Start(dbConn
, std::move(visitData
),
2084 callback
, initialUpdatedCount
);
2085 NS_ENSURE_SUCCESS(rv
, rv
);
2086 } else if (aCallback
) {
2087 // Be sure to notify that all of our operations are complete. This
2088 // is dispatched to the background thread first and redirected to the
2089 // main thread from there to make sure that all database notifications
2090 // and all embed or canAddURI notifications have finished.
2092 // Note: if we're inserting anything, it's the responsibility of
2093 // InsertVisitedURIs to call the completion callback, as here we won't
2094 // know how yet many items we will successfully insert/update.
2095 nsCOMPtr
<nsIEventTarget
> backgroundThread
= do_GetInterface(dbConn
);
2096 NS_ENSURE_TRUE(backgroundThread
, NS_ERROR_UNEXPECTED
);
2097 nsCOMPtr
<nsIRunnable
> event
=
2098 new NotifyCompletion(callback
, initialUpdatedCount
);
2099 return backgroundThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
2106 History::IsURIVisited(nsIURI
* aURI
, mozIVisitedStatusCallback
* aCallback
) {
2107 NS_ENSURE_STATE(NS_IsMainThread());
2108 NS_ENSURE_ARG(aURI
);
2109 NS_ENSURE_ARG(aCallback
);
2111 return VisitedQuery::Start(aURI
, aCallback
);
2114 void History::StartPendingVisitedQueries(PendingVisitedQueries
&& aQueries
) {
2115 if (XRE_IsContentProcess()) {
2116 nsTArray
<RefPtr
<nsIURI
>> uris(aQueries
.Count());
2117 for (const auto& entry
: aQueries
) {
2118 uris
.AppendElement(entry
.GetKey());
2119 MOZ_ASSERT(entry
.GetData().IsEmpty(),
2120 "Child process shouldn't have parent requests");
2122 auto* cpc
= mozilla::dom::ContentChild::GetSingleton();
2123 MOZ_ASSERT(cpc
, "Content Protocol is NULL!");
2124 Unused
<< cpc
->SendStartVisitedQueries(uris
);
2126 // TODO(bug 1594368): We could do a single query, as long as we can
2127 // then notify each URI individually.
2128 for (auto& entry
: aQueries
) {
2129 nsresult queryStatus
= VisitedQuery::Start(
2130 entry
.GetKey(), std::move(*entry
.GetModifiableData()));
2131 Unused
<< NS_WARN_IF(NS_FAILED(queryStatus
));
2136 ////////////////////////////////////////////////////////////////////////////////
2140 History::Observe(nsISupports
* aSubject
, const char* aTopic
,
2141 const char16_t
* aData
) {
2142 if (strcmp(aTopic
, TOPIC_PLACES_SHUTDOWN
) == 0) {
2145 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
2147 (void)os
->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN
);
2154 ////////////////////////////////////////////////////////////////////////////////
2157 NS_IMPL_ISUPPORTS(History
, IHistory
, mozIAsyncHistory
, nsIObserver
,
2160 } // namespace places
2161 } // namespace mozilla