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 "nsAppDirectoryServiceDefs.h"
30 #include "nsThreadUtils.h"
31 #include "nsNetUtil.h"
32 #include "nsIWidget.h"
33 #include "nsIXPConnect.h"
34 #include "nsIXULRuntime.h"
35 #include "mozilla/Unused.h"
36 #include "nsContentUtils.h" // for nsAutoScriptBlocker
37 #include "nsJSUtils.h"
38 #include "nsStandardURL.h"
39 #include "mozilla/ipc/URIUtils.h"
40 #include "nsPrintfCString.h"
41 #include "nsTHashtable.h"
43 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject
44 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetElement, JS_GetProperty
45 #include "mozilla/StaticPrefs_layout.h"
46 #include "mozilla/dom/ContentProcessMessageManager.h"
47 #include "mozilla/dom/Element.h"
48 #include "mozilla/dom/PlacesObservers.h"
49 #include "mozilla/dom/PlacesVisit.h"
50 #include "mozilla/dom/PlacesVisitTitle.h"
51 #include "mozilla/dom/ScriptSettings.h"
53 #include "nsIBrowserWindowTracker.h"
54 #include "nsImportModule.h"
55 #include "mozilla/StaticPrefs_browser.h"
57 using namespace mozilla::dom
;
58 using namespace mozilla::ipc
;
59 using mozilla::Unused
;
64 ////////////////////////////////////////////////////////////////////////////////
67 // Observer event fired after a visit has been registered in the DB.
68 #define URI_VISIT_SAVED "uri-visit-saved"
70 #define DESTINATIONFILEURI_ANNO "downloads/destinationFileURI"_ns
72 ////////////////////////////////////////////////////////////////////////////////
80 shouldUpdateHidden(true),
82 transitionType(UINT32_MAX
),
90 shouldUpdateFrecency(true),
91 useFrecencyRedirectBonus(false),
92 source(nsINavHistoryService::VISIT_SOURCE_ORGANIC
),
94 triggeringSponsoredURLVisitTimeMS(0),
97 title
.SetIsVoid(true);
98 baseDomain
.SetIsVoid(true);
99 triggeringSearchEngine
.SetIsVoid(true);
100 triggeringSponsoredURL
.SetIsVoid(true);
101 triggeringSponsoredURLBaseDomain
.SetIsVoid(true);
104 explicit VisitData(nsIURI
* aURI
, nsIURI
* aReferrer
= nullptr)
108 shouldUpdateHidden(true),
110 transitionType(UINT32_MAX
),
118 shouldUpdateFrecency(true),
119 useFrecencyRedirectBonus(false),
120 source(nsINavHistoryService::VISIT_SOURCE_ORGANIC
),
121 triggeringPlaceId(0),
122 triggeringSponsoredURLVisitTimeMS(0),
126 (void)aURI
->GetSpec(spec
);
127 (void)GetReversedHostname(aURI
, revHost
);
130 (void)aReferrer
->GetSpec(referrerSpec
);
132 guid
.SetIsVoid(true);
133 title
.SetIsVoid(true);
134 baseDomain
.SetIsVoid(true);
135 triggeringSearchEngine
.SetIsVoid(true);
136 triggeringSponsoredURL
.SetIsVoid(true);
137 triggeringSponsoredURLBaseDomain
.SetIsVoid(true);
141 * Sets the transition type of the visit, as well as if it was typed.
143 * @param aTransitionType
144 * The transition type constant to set. Must be one of the
145 * TRANSITION_ constants on nsINavHistoryService.
147 void SetTransitionType(uint32_t aTransitionType
) {
148 typed
= aTransitionType
== nsINavHistoryService::TRANSITION_TYPED
;
149 transitionType
= aTransitionType
;
156 nsCString baseDomain
;
159 bool shouldUpdateHidden
;
161 uint32_t transitionType
;
165 PRTime lastVisitTime
;
169 * Stores the title. If this is empty (IsEmpty() returns true), then the
170 * title should be removed from the Place. If the title is void (IsVoid()
171 * returns true), then no title has been set on this object, and titleChanged
172 * should remain false.
176 nsCString referrerSpec
;
177 int64_t referrerVisitId
;
179 // TODO bug 626836 hook up hidden and typed change tracking too!
182 // Indicates whether frecency should be updated for this visit.
183 bool shouldUpdateFrecency
;
185 // Whether to override the visit type bonus with a redirect bonus when
186 // calculating frecency on the most recent visit.
187 bool useFrecencyRedirectBonus
;
190 nsCString triggeringSearchEngine
;
191 int64_t triggeringPlaceId
;
192 nsCString triggeringSponsoredURL
;
193 nsCString triggeringSponsoredURLBaseDomain
;
194 int64_t triggeringSponsoredURLVisitTimeMS
;
198 ////////////////////////////////////////////////////////////////////////////////
199 //// Anonymous Helpers
204 * Convert the given js value to a js array.
207 * the JS value to convert.
209 * The JSContext for aValue.
210 * @param [out] _array
212 * @param [out] _arrayLength
215 nsresult
GetJSArrayFromJSValue(JS::Handle
<JS::Value
> aValue
, JSContext
* aCtx
,
216 JS::MutableHandle
<JSObject
*> _array
,
217 uint32_t* _arrayLength
) {
218 if (aValue
.isObjectOrNull()) {
219 JS::Rooted
<JSObject
*> val(aCtx
, aValue
.toObjectOrNull());
221 if (!JS::IsArrayObject(aCtx
, val
, &isArray
)) {
222 return NS_ERROR_UNEXPECTED
;
226 (void)JS::GetArrayLength(aCtx
, _array
, _arrayLength
);
227 NS_ENSURE_ARG(*_arrayLength
> 0);
232 // Build a temporary array to store this one item so the code below can
235 _array
.set(JS::NewArrayObject(aCtx
, 0));
236 NS_ENSURE_TRUE(_array
, NS_ERROR_OUT_OF_MEMORY
);
238 bool rc
= JS_DefineElement(aCtx
, _array
, 0, aValue
, 0);
239 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
244 * Attemps to convert a given js value to a nsIURI object.
246 * The JSContext for aValue.
248 * The JS value to convert.
249 * @return the nsIURI object, or null if aValue is not a nsIURI object.
251 already_AddRefed
<nsIURI
> GetJSValueAsURI(JSContext
* aCtx
,
252 const JS::Value
& aValue
) {
253 if (!aValue
.isPrimitive()) {
254 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
256 nsCOMPtr
<nsIXPConnectWrappedNative
> wrappedObj
;
257 JS::Rooted
<JSObject
*> obj(aCtx
, aValue
.toObjectOrNull());
259 xpc
->GetWrappedNativeOfJSObject(aCtx
, obj
, getter_AddRefs(wrappedObj
));
260 NS_ENSURE_SUCCESS(rv
, nullptr);
261 nsCOMPtr
<nsIURI
> uri
= do_QueryInterface(wrappedObj
->Native());
268 * Obtains an nsIURI from the "uri" property of a JSObject.
271 * The JSContext for aObject.
273 * The JSObject to get the URI from.
275 * The name of the property to get the URI from.
276 * @return the URI if it exists.
278 already_AddRefed
<nsIURI
> GetURIFromJSObject(JSContext
* aCtx
,
279 JS::Handle
<JSObject
*> aObject
,
280 const char* aProperty
) {
281 JS::Rooted
<JS::Value
> uriVal(aCtx
);
282 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &uriVal
);
283 NS_ENSURE_TRUE(rc
, nullptr);
284 return GetJSValueAsURI(aCtx
, uriVal
);
288 * Attemps to convert a JS value to a string.
290 * The JSContext for aObject.
292 * The JS value to convert.
294 * The string to populate with the value, or set it to void.
296 void GetJSValueAsString(JSContext
* aCtx
, const JS::Value
& aValue
,
298 if (aValue
.isUndefined() || !(aValue
.isNull() || aValue
.isString())) {
299 _string
.SetIsVoid(true);
303 // |null| in JS maps to the empty string.
304 if (aValue
.isNull()) {
309 if (!AssignJSString(aCtx
, _string
, aValue
.toString())) {
310 _string
.SetIsVoid(true);
315 * Obtains the specified property of a JSObject.
318 * The JSContext for aObject.
320 * The JSObject to get the string from.
322 * The property to get the value from.
324 * The string to populate with the value, or set it to void.
326 void GetStringFromJSObject(JSContext
* aCtx
, JS::Handle
<JSObject
*> aObject
,
327 const char* aProperty
, nsString
& _string
) {
328 JS::Rooted
<JS::Value
> val(aCtx
);
329 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &val
);
331 _string
.SetIsVoid(true);
334 GetJSValueAsString(aCtx
, val
, _string
);
339 * Obtains the specified property of a JSObject.
342 * The JSContext for aObject.
344 * The JSObject to get the int from.
346 * The property to get the value from.
348 * The integer to populate with the value on success.
350 template <typename IntType
>
351 nsresult
GetIntFromJSObject(JSContext
* aCtx
, JS::Handle
<JSObject
*> aObject
,
352 const char* aProperty
, IntType
* _int
) {
353 JS::Rooted
<JS::Value
> value(aCtx
);
354 bool rc
= JS_GetProperty(aCtx
, aObject
, aProperty
, &value
);
355 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
356 if (value
.isUndefined()) {
357 return NS_ERROR_INVALID_ARG
;
359 NS_ENSURE_ARG(value
.isPrimitive());
360 NS_ENSURE_ARG(value
.isNumber());
363 rc
= JS::ToNumber(aCtx
, value
, &num
);
364 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
365 NS_ENSURE_ARG(IntType(num
) == num
);
367 *_int
= IntType(num
);
372 * Obtains the specified property of a JSObject.
374 * @pre aArray must be an Array object.
377 * The JSContext for aArray.
379 * The JSObject to get the object from.
381 * The index to get the object from.
383 * Set to the JSObject pointer on success.
385 nsresult
GetJSObjectFromArray(JSContext
* aCtx
, JS::Handle
<JSObject
*> aArray
,
387 JS::MutableHandle
<JSObject
*> objOut
) {
388 JS::Rooted
<JS::Value
> value(aCtx
);
389 bool rc
= JS_GetElement(aCtx
, aArray
, aIndex
, &value
);
390 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
391 NS_ENSURE_ARG(!value
.isPrimitive());
392 objOut
.set(&value
.toObject());
398 class VisitedQuery final
: public AsyncStatementCallback
{
400 NS_DECL_ISUPPORTS_INHERITED
402 static nsresult
Start(nsIURI
* aURI
,
403 History::ContentParentSet
&& aContentProcessesToNotify
) {
404 MOZ_ASSERT(aURI
, "Null URI");
405 MOZ_ASSERT(XRE_IsParentProcess());
407 History
* history
= History::GetService();
408 NS_ENSURE_STATE(history
);
409 RefPtr
<VisitedQuery
> query
=
410 new VisitedQuery(aURI
, std::move(aContentProcessesToNotify
));
411 return history
->QueueVisitedStatement(std::move(query
));
414 static nsresult
Start(nsIURI
* aURI
,
415 mozIVisitedStatusCallback
* aCallback
= nullptr) {
416 MOZ_ASSERT(aURI
, "Null URI");
417 MOZ_ASSERT(XRE_IsParentProcess());
419 nsMainThreadPtrHandle
<mozIVisitedStatusCallback
> callback(
420 new nsMainThreadPtrHolder
<mozIVisitedStatusCallback
>(
421 "mozIVisitedStatusCallback", aCallback
));
423 History
* history
= History::GetService();
424 NS_ENSURE_STATE(history
);
425 RefPtr
<VisitedQuery
> query
= new VisitedQuery(aURI
, callback
);
426 return history
->QueueVisitedStatement(std::move(query
));
429 void Execute(mozIStorageAsyncStatement
& aStatement
) {
430 // Bind by index for performance.
431 nsresult rv
= URIBinder::Bind(&aStatement
, 0, mURI
);
432 if (NS_WARN_IF(NS_FAILED(rv
))) {
436 nsCOMPtr
<mozIStoragePendingStatement
> handle
;
437 rv
= aStatement
.ExecuteAsync(this, getter_AddRefs(handle
));
438 MOZ_ASSERT(NS_SUCCEEDED(rv
));
442 NS_IMETHOD
HandleResult(mozIStorageResultSet
* aResults
) override
{
443 // If this method is called, we've gotten results, which means we have a
449 NS_IMETHOD
HandleError(mozIStorageError
* aError
) override
{
450 // mIsVisited is already set to false, and that's the assumption we will
451 // make if an error occurred.
455 NS_IMETHOD
HandleCompletion(uint16_t aReason
) override
{
456 if (aReason
== mozIStorageStatementCallback::REASON_FINISHED
) {
457 NotifyVisitedStatus();
462 void NotifyVisitedStatus() {
463 // If an external handling callback is provided, just notify through it.
465 mCallback
->IsVisited(mURI
, mIsVisited
);
469 if (History
* history
= History::GetService()) {
470 auto status
= mIsVisited
? IHistory::VisitedStatus::Visited
471 : IHistory::VisitedStatus::Unvisited
;
472 history
->NotifyVisited(mURI
, status
, &mContentProcessesToNotify
);
477 explicit VisitedQuery(
479 const nsMainThreadPtrHandle
<mozIVisitedStatusCallback
>& aCallback
)
480 : mURI(aURI
), mCallback(aCallback
) {}
482 explicit VisitedQuery(nsIURI
* aURI
,
483 History::ContentParentSet
&& aContentProcessesToNotify
)
485 mContentProcessesToNotify(std::move(aContentProcessesToNotify
)) {}
487 ~VisitedQuery() = default;
489 nsCOMPtr
<nsIURI
> mURI
;
490 nsMainThreadPtrHandle
<mozIVisitedStatusCallback
> mCallback
;
491 History::ContentParentSet mContentProcessesToNotify
;
492 bool mIsVisited
= false;
495 NS_IMPL_ISUPPORTS_INHERITED0(VisitedQuery
, AsyncStatementCallback
)
498 * Notifies observers about a visit or an array of visits.
500 class NotifyManyVisitsObservers
: public Runnable
{
502 explicit NotifyManyVisitsObservers(const VisitData
& aPlace
)
503 : Runnable("places::NotifyManyVisitsObservers"),
505 mHistory(History::GetService()) {}
507 explicit NotifyManyVisitsObservers(nsTArray
<VisitData
>&& aPlaces
)
508 : Runnable("places::NotifyManyVisitsObservers"),
509 mPlaces(std::move(aPlaces
)),
510 mHistory(History::GetService()) {}
512 nsresult
NotifyVisit(nsNavHistory
* aNavHistory
,
513 nsCOMPtr
<nsIObserverService
>& aObsService
, PRTime aNow
,
514 nsIURI
* aURI
, const VisitData
& aPlace
) {
516 DebugOnly
<nsresult
> rv
=
517 aObsService
->NotifyObservers(aURI
, URI_VISIT_SAVED
, nullptr);
518 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Could not notify observers");
521 if (aNow
- aPlace
.visitTime
< RECENTLY_VISITED_URIS_MAX_AGE
) {
522 mHistory
->AppendToRecentlyVisitedURIs(aURI
, aPlace
.hidden
);
524 mHistory
->NotifyVisited(aURI
, IHistory::VisitedStatus::Visited
);
526 aNavHistory
->UpdateDaysOfHistory(aPlace
.visitTime
);
531 void AddPlaceForNotify(const VisitData
& aPlace
,
532 Sequence
<OwningNonNull
<PlacesEvent
>>& aEvents
) {
533 if (aPlace
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
537 RefPtr
<PlacesVisit
> visitEvent
= new PlacesVisit();
538 visitEvent
->mVisitId
= aPlace
.visitId
;
539 visitEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(aPlace
.spec
));
540 visitEvent
->mVisitTime
= aPlace
.visitTime
/ 1000;
541 visitEvent
->mReferringVisitId
= aPlace
.referrerVisitId
;
542 visitEvent
->mTransitionType
= aPlace
.transitionType
;
543 visitEvent
->mPageGuid
.Assign(aPlace
.guid
);
544 visitEvent
->mHidden
= aPlace
.hidden
;
545 visitEvent
->mVisitCount
= aPlace
.visitCount
+ 1; // Add current visit
546 visitEvent
->mTypedCount
= static_cast<uint32_t>(aPlace
.typed
);
547 visitEvent
->mLastKnownTitle
.Assign(aPlace
.title
);
548 bool success
= !!aEvents
.AppendElement(visitEvent
.forget(), fallible
);
549 MOZ_RELEASE_ASSERT(success
);
551 if (aPlace
.titleChanged
) {
552 RefPtr
<PlacesVisitTitle
> titleEvent
= new PlacesVisitTitle();
553 titleEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(aPlace
.spec
));
554 titleEvent
->mPageGuid
.Assign(aPlace
.guid
);
555 titleEvent
->mTitle
.Assign(aPlace
.title
);
556 bool success
= !!aEvents
.AppendElement(titleEvent
.forget(), fallible
);
557 MOZ_RELEASE_ASSERT(success
);
561 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
562 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
563 MOZ_CAN_RUN_SCRIPT_BOUNDARY
564 NS_IMETHOD
Run() override
{
565 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
567 // We are in the main thread, no need to lock.
568 if (mHistory
->IsShuttingDown()) {
569 // If we are shutting down, we cannot notify the observers.
573 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
576 "Trying to notify visits observers but cannot get the history "
581 nsCOMPtr
<nsIObserverService
> obsService
=
582 mozilla::services::GetObserverService();
584 Sequence
<OwningNonNull
<PlacesEvent
>> events
;
585 PRTime now
= PR_Now();
586 for (uint32_t i
= 0; i
< mPlaces
.Length(); ++i
) {
587 nsCOMPtr
<nsIURI
> uri
;
588 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), mPlaces
[i
].spec
));
590 return NS_ERROR_UNEXPECTED
;
592 AddPlaceForNotify(mPlaces
[i
], events
);
594 nsresult rv
= NotifyVisit(navHistory
, obsService
, now
, uri
, mPlaces
[i
]);
595 NS_ENSURE_SUCCESS(rv
, rv
);
598 if (events
.Length() > 0) {
599 PlacesObservers::NotifyListeners(events
);
606 AutoTArray
<VisitData
, 1> mPlaces
;
607 RefPtr
<History
> mHistory
;
611 * Notifies observers about a pages title changing.
613 class NotifyTitleObservers
: public Runnable
{
616 * Notifies observers on the main thread.
619 * The spec of the URI to notify about.
621 * The new title to notify about.
623 NotifyTitleObservers(const nsCString
& aSpec
, const nsString
& aTitle
,
624 const nsCString
& aGUID
)
625 : Runnable("places::NotifyTitleObservers"),
630 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is marked
631 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
632 MOZ_CAN_RUN_SCRIPT_BOUNDARY
633 NS_IMETHOD
Run() override
{
634 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
636 RefPtr
<PlacesVisitTitle
> titleEvent
= new PlacesVisitTitle();
637 titleEvent
->mUrl
.Assign(NS_ConvertUTF8toUTF16(mSpec
));
638 titleEvent
->mPageGuid
.Assign(mGUID
);
639 titleEvent
->mTitle
.Assign(mTitle
);
641 Sequence
<OwningNonNull
<PlacesEvent
>> events
;
642 bool success
= !!events
.AppendElement(titleEvent
.forget(), fallible
);
643 MOZ_RELEASE_ASSERT(success
);
645 PlacesObservers::NotifyListeners(events
);
651 const nsCString mSpec
;
652 const nsString mTitle
;
653 const nsCString mGUID
;
657 * Helper class for methods which notify their callers through the
658 * mozIVisitInfoCallback interface.
660 class NotifyPlaceInfoCallback
: public Runnable
{
662 NotifyPlaceInfoCallback(
663 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
664 const VisitData
& aPlace
, bool aIsSingleVisit
, nsresult aResult
)
665 : Runnable("places::NotifyPlaceInfoCallback"),
666 mCallback(aCallback
),
669 mIsSingleVisit(aIsSingleVisit
) {
670 MOZ_ASSERT(aCallback
, "Must pass a non-null callback!");
673 NS_IMETHOD
Run() override
{
674 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
676 bool hasValidURIs
= true;
677 nsCOMPtr
<nsIURI
> referrerURI
;
678 if (!mPlace
.referrerSpec
.IsEmpty()) {
680 NS_NewURI(getter_AddRefs(referrerURI
), mPlace
.referrerSpec
));
681 hasValidURIs
= !!referrerURI
;
684 nsCOMPtr
<nsIURI
> uri
;
685 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), mPlace
.spec
));
686 hasValidURIs
= hasValidURIs
&& !!uri
;
688 nsCOMPtr
<mozIPlaceInfo
> place
;
689 if (mIsSingleVisit
) {
690 nsCOMPtr
<mozIVisitInfo
> visit
=
691 new VisitInfo(mPlace
.visitId
, mPlace
.visitTime
, mPlace
.transitionType
,
692 referrerURI
.forget());
693 PlaceInfo::VisitsArray visits
;
694 (void)visits
.AppendElement(visit
);
696 // The frecency isn't exposed because it may not reflect the updated value
697 // in the case of InsertVisitedURIs.
698 place
= new PlaceInfo(mPlace
.placeId
, mPlace
.guid
, uri
.forget(),
699 mPlace
.title
, -1, visits
);
702 place
= new PlaceInfo(mPlace
.placeId
, mPlace
.guid
, uri
.forget(),
706 if (NS_SUCCEEDED(mResult
) && hasValidURIs
) {
707 (void)mCallback
->HandleResult(place
);
709 (void)mCallback
->HandleError(mResult
, place
);
716 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
718 const nsresult mResult
;
723 * Notifies a callback object when the operation is complete.
725 class NotifyCompletion
: public Runnable
{
727 explicit NotifyCompletion(
728 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
729 uint32_t aUpdatedCount
= 0)
730 : Runnable("places::NotifyCompletion"),
731 mCallback(aCallback
),
732 mUpdatedCount(aUpdatedCount
) {
733 MOZ_ASSERT(aCallback
, "Must pass a non-null callback!");
736 NS_IMETHOD
Run() override
{
737 if (NS_IsMainThread()) {
738 (void)mCallback
->HandleCompletion(mUpdatedCount
);
740 (void)NS_DispatchToMainThread(this);
746 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
747 uint32_t mUpdatedCount
;
751 * Checks to see if we can add aURI to history, and dispatches an error to
752 * aCallback (if provided) if we cannot.
756 * @param [optional] aGUID
757 * The guid of the URI to check. This is passed back to the callback.
758 * @param [optional] aCallback
759 * The callback to notify if the URI cannot be added to history.
760 * @return true if the URI can be added to history, false otherwise.
762 bool CanAddURI(nsIURI
* aURI
, const nsCString
& aGUID
= ""_ns
,
763 mozIVisitInfoCallback
* aCallback
= nullptr) {
764 MOZ_ASSERT(NS_IsMainThread());
765 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
766 NS_ENSURE_TRUE(navHistory
, false);
769 nsresult rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
770 if (NS_SUCCEEDED(rv
) && canAdd
) {
774 // We cannot add the URI. Notify the callback, if we were given one.
776 VisitData
place(aURI
);
778 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
779 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
780 "mozIVisitInfoCallback", aCallback
));
781 nsCOMPtr
<nsIRunnable
> event
= new NotifyPlaceInfoCallback(
782 callback
, place
, true, NS_ERROR_INVALID_ARG
);
783 (void)NS_DispatchToMainThread(event
);
790 * Adds a visit to the database.
792 class InsertVisitedURIs final
: public Runnable
{
795 * Adds a visit to the database asynchronously.
798 * The database connection to use for these operations.
800 * The locations to record visits.
801 * @param [optional] aCallback
802 * The callback to notify about the visit.
804 static nsresult
Start(mozIStorageConnection
* aConnection
,
805 nsTArray
<VisitData
>&& aPlaces
,
806 mozIVisitInfoCallback
* aCallback
= nullptr,
807 uint32_t aInitialUpdatedCount
= 0) {
808 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
809 MOZ_ASSERT(aPlaces
.Length() > 0, "Must pass a non-empty array!");
811 // Make sure nsNavHistory service is up before proceeding:
812 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
813 MOZ_ASSERT(navHistory
, "Could not get nsNavHistory?!");
815 return NS_ERROR_FAILURE
;
818 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
819 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
820 "mozIVisitInfoCallback", aCallback
));
821 bool ignoreErrors
= false, ignoreResults
= false;
823 // We ignore errors from either of these methods in case old JS consumers
824 // don't implement them (in which case they will get error/result
825 // notifications as normal).
826 Unused
<< aCallback
->GetIgnoreErrors(&ignoreErrors
);
827 Unused
<< aCallback
->GetIgnoreResults(&ignoreResults
);
829 RefPtr
<InsertVisitedURIs
> event
= new InsertVisitedURIs(
830 aConnection
, std::move(aPlaces
), callback
, ignoreErrors
, ignoreResults
,
831 aInitialUpdatedCount
);
833 // Get the target thread, and then start the work!
834 nsCOMPtr
<nsIEventTarget
> target
= do_GetInterface(aConnection
);
835 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
836 nsresult rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
837 NS_ENSURE_SUCCESS(rv
, rv
);
842 NS_IMETHOD
Run() override
{
843 MOZ_ASSERT(!NS_IsMainThread(),
844 "This should not be called on the main thread");
846 // The inner run method may bail out at any point, so we ensure we do
847 // whatever we can and then notify the main thread we're done.
848 nsresult rv
= InnerRun();
850 if (mSuccessfulUpdatedCount
> 0) {
851 NS_DispatchToMainThread(new NotifyRankingChanged());
854 NS_DispatchToMainThread(
855 new NotifyCompletion(mCallback
, mSuccessfulUpdatedCount
));
860 nsresult
InnerRun() {
861 MOZ_ASSERT(!NS_IsMainThread());
862 // Prevent Shutdown() from proceeding while this is running.
863 MutexAutoLock
lockedScope(mHistory
->mBlockShutdownMutex
);
864 // Check if we were already shutting down.
865 if (mHistory
->IsShuttingDown()) {
869 mozStorageTransaction
transaction(
870 mDBConn
, false, mozIStorageConnection::TRANSACTION_IMMEDIATE
);
872 // XXX Handle the error, bug 1696133.
873 Unused
<< NS_WARN_IF(NS_FAILED(transaction
.Start()));
875 const VisitData
* lastFetchedPlace
= nullptr;
876 uint32_t lastFetchedVisitCount
= 0;
877 bool shouldChunkNotifications
= mPlaces
.Length() > NOTIFY_VISITS_CHUNK_SIZE
;
878 nsTArray
<VisitData
> notificationChunk
;
879 if (shouldChunkNotifications
) {
880 notificationChunk
.SetCapacity(NOTIFY_VISITS_CHUNK_SIZE
);
882 for (nsTArray
<VisitData
>::size_type i
= 0; i
< mPlaces
.Length(); i
++) {
883 VisitData
& place
= mPlaces
.ElementAt(i
);
885 // Fetching from the database can overwrite this information, so save it
887 bool typed
= place
.typed
;
888 bool hidden
= place
.hidden
;
890 // We can avoid a database lookup if it's the same place as the last
893 lastFetchedPlace
&& lastFetchedPlace
->spec
.Equals(place
.spec
);
895 nsresult rv
= mHistory
->FetchPageInfo(place
, &known
);
897 if (!!mCallback
&& !mIgnoreErrors
) {
898 nsCOMPtr
<nsIRunnable
> event
=
899 new NotifyPlaceInfoCallback(mCallback
, place
, true, rv
);
900 return NS_DispatchToMainThread(event
);
904 lastFetchedPlace
= &mPlaces
.ElementAt(i
);
905 lastFetchedVisitCount
= lastFetchedPlace
->visitCount
;
907 // Copy over the data from the already known place.
908 place
.placeId
= lastFetchedPlace
->placeId
;
909 place
.guid
= lastFetchedPlace
->guid
;
910 place
.lastVisitId
= lastFetchedPlace
->visitId
;
911 place
.lastVisitTime
= lastFetchedPlace
->visitTime
;
912 if (!place
.title
.IsVoid()) {
913 place
.titleChanged
= !lastFetchedPlace
->title
.Equals(place
.title
);
915 place
.frecency
= lastFetchedPlace
->frecency
;
916 // Add one visit for the previous loop.
917 place
.visitCount
= ++lastFetchedVisitCount
;
920 // If any transition is typed, ensure the page is marked as typed.
921 if (typed
!= lastFetchedPlace
->typed
) {
925 // If any transition is visible, ensure the page is marked as visible.
926 if (hidden
!= lastFetchedPlace
->hidden
) {
927 place
.hidden
= false;
930 // If this is a new page, or the existing page was already visible,
931 // there's no need to try to unhide it.
932 if (!known
|| !lastFetchedPlace
->hidden
) {
933 place
.shouldUpdateHidden
= false;
936 FetchReferrerInfo(place
);
937 UpdateVisitSource(place
, mHistory
);
939 nsresult rv
= DoDatabaseInserts(known
, place
);
941 // Check if consumers wanted to be notified about success/failure,
942 // depending on whether this action succeeded or not.
943 if ((NS_SUCCEEDED(rv
) && !mIgnoreResults
) ||
944 (NS_FAILED(rv
) && !mIgnoreErrors
)) {
945 nsCOMPtr
<nsIRunnable
> event
=
946 new NotifyPlaceInfoCallback(mCallback
, place
, true, rv
);
947 nsresult rv2
= NS_DispatchToMainThread(event
);
948 NS_ENSURE_SUCCESS(rv2
, rv2
);
951 NS_ENSURE_SUCCESS(rv
, rv
);
953 if (shouldChunkNotifications
) {
954 int32_t numRemaining
= mPlaces
.Length() - (i
+ 1);
955 notificationChunk
.AppendElement(place
);
956 if (notificationChunk
.Length() == NOTIFY_VISITS_CHUNK_SIZE
||
958 nsCOMPtr
<nsIRunnable
> event
=
959 new NotifyManyVisitsObservers(std::move(notificationChunk
));
960 rv
= NS_DispatchToMainThread(event
);
961 NS_ENSURE_SUCCESS(rv
, rv
);
963 int32_t nextCapacity
=
964 std::min(NOTIFY_VISITS_CHUNK_SIZE
, numRemaining
);
965 notificationChunk
.SetCapacity(nextCapacity
);
969 // If we get here, we must have been successful adding/updating this
970 // visit/place, so update the count:
971 mSuccessfulUpdatedCount
++;
975 // Trigger insertions for all the new origins of the places we inserted.
976 nsAutoCString
query("DELETE FROM moz_updateoriginsinsert_temp");
977 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(query
);
978 NS_ENSURE_STATE(stmt
);
979 mozStorageStatementScoper
scoper(stmt
);
980 nsresult rv
= stmt
->Execute();
981 NS_ENSURE_SUCCESS(rv
, rv
);
985 // Trigger frecency updates for all those origins.
986 nsAutoCString
query("DELETE FROM moz_updateoriginsupdate_temp");
987 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(query
);
988 NS_ENSURE_STATE(stmt
);
989 mozStorageStatementScoper
scoper(stmt
);
990 nsresult rv
= stmt
->Execute();
991 NS_ENSURE_SUCCESS(rv
, rv
);
994 nsresult rv
= transaction
.Commit();
995 NS_ENSURE_SUCCESS(rv
, rv
);
997 // If we don't need to chunk the notifications, just notify using the
998 // original mPlaces array.
999 if (!shouldChunkNotifications
) {
1000 nsCOMPtr
<nsIRunnable
> event
=
1001 new NotifyManyVisitsObservers(std::move(mPlaces
));
1002 rv
= NS_DispatchToMainThread(event
);
1003 NS_ENSURE_SUCCESS(rv
, rv
);
1011 mozIStorageConnection
* aConnection
, nsTArray
<VisitData
>&& aPlaces
,
1012 const nsMainThreadPtrHandle
<mozIVisitInfoCallback
>& aCallback
,
1013 bool aIgnoreErrors
, bool aIgnoreResults
, uint32_t aInitialUpdatedCount
)
1014 : Runnable("places::InsertVisitedURIs"),
1015 mDBConn(aConnection
),
1016 mPlaces(std::move(aPlaces
)),
1017 mCallback(aCallback
),
1018 mIgnoreErrors(aIgnoreErrors
),
1019 mIgnoreResults(aIgnoreResults
),
1020 mSuccessfulUpdatedCount(aInitialUpdatedCount
),
1021 mHistory(History::GetService()) {
1022 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1025 for (nsTArray
<VisitData
>::size_type i
= 0; i
< mPlaces
.Length(); i
++) {
1026 nsCOMPtr
<nsIURI
> uri
;
1027 MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri
), mPlaces
[i
].spec
)));
1028 MOZ_ASSERT(CanAddURI(uri
),
1029 "Passed a VisitData with a URI we cannot add to history!");
1035 * Inserts or updates the entry in moz_places for this visit, adds the visit,
1036 * and updates the frecency of the place.
1039 * True if we already have an entry for this place in moz_places, false
1042 * The place we are adding a visit for.
1044 nsresult
DoDatabaseInserts(bool aKnown
, VisitData
& aPlace
) {
1045 MOZ_ASSERT(!NS_IsMainThread(),
1046 "This should not be called on the main thread");
1048 // If the page was in moz_places, we need to update the entry.
1051 rv
= mHistory
->UpdatePlace(aPlace
);
1052 NS_ENSURE_SUCCESS(rv
, rv
);
1054 // Otherwise, the page was not in moz_places, so now we have to add it.
1056 rv
= mHistory
->InsertPlace(aPlace
);
1057 NS_ENSURE_SUCCESS(rv
, rv
);
1058 aPlace
.placeId
= nsNavHistory::sLastInsertedPlaceId
;
1060 MOZ_ASSERT(aPlace
.placeId
> 0);
1062 rv
= AddVisit(aPlace
);
1063 NS_ENSURE_SUCCESS(rv
, rv
);
1065 // TODO (bug 623969) we shouldn't update this after each visit, but
1066 // rather only for each unique place to save disk I/O.
1068 // Don't update frecency if the page should not appear in autocomplete.
1069 if (aPlace
.shouldUpdateFrecency
) {
1070 rv
= UpdateFrecency(aPlace
);
1071 NS_ENSURE_SUCCESS(rv
, rv
);
1078 * Fetches information about a referrer for aPlace if it was a recent
1082 * The VisitData for the visit we will eventually add.
1085 void FetchReferrerInfo(VisitData
& aPlace
) {
1086 if (aPlace
.referrerSpec
.IsEmpty()) {
1091 referrer
.spec
= aPlace
.referrerSpec
;
1092 // If the referrer is the same as the page, we don't need to fetch it.
1093 if (aPlace
.referrerSpec
.Equals(aPlace
.spec
)) {
1095 // The page last visit id is also the referrer visit id.
1096 aPlace
.referrerVisitId
= aPlace
.lastVisitId
;
1098 bool exists
= false;
1099 if (NS_SUCCEEDED(mHistory
->FetchPageInfo(referrer
, &exists
)) && exists
) {
1100 // Copy the referrer last visit id.
1101 aPlace
.referrerVisitId
= referrer
.lastVisitId
;
1105 // Check if the page has effectively been visited recently, otherwise
1106 // discard the referrer info.
1107 if (!aPlace
.referrerVisitId
|| !referrer
.lastVisitTime
||
1108 aPlace
.visitTime
- referrer
.lastVisitTime
> RECENT_EVENT_THRESHOLD
) {
1109 // We will not be using the referrer data.
1110 aPlace
.referrerSpec
.Truncate();
1111 aPlace
.referrerVisitId
= 0;
1116 * Adds a visit for _place and updates it with the right visit id.
1119 * The VisitData for the place we need to know visit information about.
1121 nsresult
AddVisit(VisitData
& _place
) {
1122 MOZ_ASSERT(_place
.placeId
> 0);
1125 nsCOMPtr
<mozIStorageStatement
> stmt
;
1126 stmt
= mHistory
->GetStatement(
1127 "INSERT INTO moz_historyvisits "
1128 "(from_visit, place_id, visit_date, visit_type, session, source, "
1129 "triggeringPlaceId) "
1130 "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0, :source, "
1131 ":triggeringPlaceId) ");
1132 NS_ENSURE_STATE(stmt
);
1133 mozStorageStatementScoper
scoper(stmt
);
1135 rv
= stmt
->BindInt64ByName("page_id"_ns
, _place
.placeId
);
1136 NS_ENSURE_SUCCESS(rv
, rv
);
1137 rv
= stmt
->BindInt64ByName("from_visit"_ns
, _place
.referrerVisitId
);
1138 NS_ENSURE_SUCCESS(rv
, rv
);
1139 rv
= stmt
->BindInt64ByName("visit_date"_ns
, _place
.visitTime
);
1140 NS_ENSURE_SUCCESS(rv
, rv
);
1141 uint32_t transitionType
= _place
.transitionType
;
1142 MOZ_ASSERT(transitionType
>= nsINavHistoryService::TRANSITION_LINK
&&
1143 transitionType
<= nsINavHistoryService::TRANSITION_RELOAD
,
1144 "Invalid transition type!");
1145 rv
= stmt
->BindInt32ByName("visit_type"_ns
, transitionType
);
1146 NS_ENSURE_SUCCESS(rv
, rv
);
1147 rv
= stmt
->BindInt32ByName("source"_ns
, _place
.source
);
1148 NS_ENSURE_SUCCESS(rv
, rv
);
1149 if (_place
.triggeringPlaceId
!= 0) {
1150 rv
= stmt
->BindInt64ByName("triggeringPlaceId"_ns
,
1151 _place
.triggeringPlaceId
);
1153 rv
= stmt
->BindNullByName("triggeringPlaceId"_ns
);
1155 NS_ENSURE_SUCCESS(rv
, rv
);
1157 rv
= stmt
->Execute();
1158 NS_ENSURE_SUCCESS(rv
, rv
);
1160 _place
.visitId
= nsNavHistory::sLastInsertedVisitId
;
1161 MOZ_ASSERT(_place
.visitId
> 0);
1167 * Updates the frecency, and possibly the hidden-ness of aPlace.
1170 * The VisitData for the place we want to update.
1172 nsresult
UpdateFrecency(const VisitData
& aPlace
) {
1173 MOZ_ASSERT(aPlace
.shouldUpdateFrecency
);
1174 MOZ_ASSERT(aPlace
.placeId
> 0);
1177 { // First, set our frecency to the proper value.
1178 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(
1179 "UPDATE moz_places "
1180 "SET frecency = CALCULATE_FRECENCY(:page_id, :redirect) "
1181 "WHERE id = :page_id");
1182 NS_ENSURE_STATE(stmt
);
1183 mozStorageStatementScoper
scoper(stmt
);
1185 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1186 NS_ENSURE_SUCCESS(rv
, rv
);
1188 stmt
->BindInt32ByName("redirect"_ns
, aPlace
.useFrecencyRedirectBonus
);
1189 NS_ENSURE_SUCCESS(rv
, rv
);
1191 rv
= stmt
->Execute();
1192 NS_ENSURE_SUCCESS(rv
, rv
);
1195 if (!aPlace
.hidden
&& aPlace
.shouldUpdateHidden
) {
1196 // Mark the page as not hidden if the frecency is now nonzero.
1197 nsCOMPtr
<mozIStorageStatement
> stmt
;
1198 stmt
= mHistory
->GetStatement(
1199 "UPDATE moz_places "
1201 "WHERE id = :page_id AND frecency <> 0");
1202 NS_ENSURE_STATE(stmt
);
1203 mozStorageStatementScoper
scoper(stmt
);
1205 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1206 NS_ENSURE_SUCCESS(rv
, rv
);
1208 rv
= stmt
->Execute();
1209 NS_ENSURE_SUCCESS(rv
, rv
);
1215 nsresult
UpdateVisitSource(VisitData
& aPlace
, History
* aHistory
) {
1216 if (aPlace
.bookmarked
) {
1217 aPlace
.source
= nsINavHistoryService::VISIT_SOURCE_BOOKMARKED
;
1218 } else if (!aPlace
.triggeringSearchEngine
.IsEmpty()) {
1219 aPlace
.source
= nsINavHistoryService::VISIT_SOURCE_SEARCHED
;
1221 aPlace
.source
= nsINavHistoryService::VISIT_SOURCE_ORGANIC
;
1224 if (aPlace
.triggeringSponsoredURL
.IsEmpty()) {
1225 // No triggeringSponsoredURL.
1229 if ((aPlace
.visitTime
-
1230 aPlace
.triggeringSponsoredURLVisitTimeMS
* PR_USEC_PER_MSEC
) >
1231 StaticPrefs::browser_places_sponsoredSession_timeoutSecs() *
1233 // Sponsored session timeout.
1237 if (aPlace
.spec
.Equals(aPlace
.triggeringSponsoredURL
)) {
1238 // This place is the triggeringSponsoredURL.
1239 aPlace
.source
= nsINavHistoryService::VISIT_SOURCE_SPONSORED
;
1243 if (!aPlace
.baseDomain
.Equals(aPlace
.triggeringSponsoredURLBaseDomain
)) {
1244 // The base domain is not same.
1248 nsCOMPtr
<mozIStorageStatement
> stmt
;
1249 stmt
= aHistory
->GetStatement(
1250 "SELECT id FROM moz_places h "
1251 "WHERE url_hash = hash(:url) AND url = :url");
1252 NS_ENSURE_STATE(stmt
);
1254 URIBinder::Bind(stmt
, "url"_ns
, aPlace
.triggeringSponsoredURL
);
1255 NS_ENSURE_SUCCESS(rv
, rv
);
1257 mozStorageStatementScoper
scoper(stmt
);
1260 rv
= stmt
->ExecuteStep(&exists
);
1261 NS_ENSURE_SUCCESS(rv
, rv
);
1264 rv
= stmt
->GetInt64(0, &aPlace
.triggeringPlaceId
);
1265 NS_ENSURE_SUCCESS(rv
, rv
);
1267 Telemetry::ScalarAdd(
1268 Telemetry::ScalarID::PLACES_SPONSORED_VISIT_NO_TRIGGERING_URL
, 1);
1271 aPlace
.source
= nsINavHistoryService::VISIT_SOURCE_SPONSORED
;
1276 mozIStorageConnection
* mDBConn
;
1278 nsTArray
<VisitData
> mPlaces
;
1280 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> mCallback
;
1284 bool mIgnoreResults
;
1286 uint32_t mSuccessfulUpdatedCount
;
1289 * Strong reference to the History object because we do not want it to
1290 * disappear out from under us.
1292 RefPtr
<History
> mHistory
;
1296 * Sets the page title for a page in moz_places (if necessary).
1298 class SetPageTitle
: public Runnable
{
1301 * Sets a pages title in the database asynchronously.
1303 * @param aConnection
1304 * The database connection to use for this operation.
1306 * The URI to set the page title on.
1308 * The title to set for the page, if the page exists.
1310 static nsresult
Start(mozIStorageConnection
* aConnection
, nsIURI
* aURI
,
1311 const nsAString
& aTitle
) {
1312 MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
1313 MOZ_ASSERT(aURI
, "Must pass a non-null URI object!");
1316 nsresult rv
= aURI
->GetSpec(spec
);
1317 NS_ENSURE_SUCCESS(rv
, rv
);
1319 RefPtr
<SetPageTitle
> event
= new SetPageTitle(spec
, aTitle
);
1321 // Get the target thread, and then start the work!
1322 nsCOMPtr
<nsIEventTarget
> target
= do_GetInterface(aConnection
);
1323 NS_ENSURE_TRUE(target
, NS_ERROR_UNEXPECTED
);
1324 rv
= target
->Dispatch(event
, NS_DISPATCH_NORMAL
);
1325 NS_ENSURE_SUCCESS(rv
, rv
);
1330 NS_IMETHOD
Run() override
{
1331 MOZ_ASSERT(!NS_IsMainThread(),
1332 "This should not be called on the main thread");
1334 // First, see if the page exists in the database (we'll need its id later).
1336 nsresult rv
= mHistory
->FetchPageInfo(mPlace
, &exists
);
1337 NS_ENSURE_SUCCESS(rv
, rv
);
1339 if (!exists
|| !mPlace
.titleChanged
) {
1340 // We have no record of this page, or we have no title change, so there
1341 // is no need to do any further work.
1345 MOZ_ASSERT(mPlace
.placeId
> 0, "We somehow have an invalid place id here!");
1347 // Now we can update our database record.
1348 nsCOMPtr
<mozIStorageStatement
> stmt
= mHistory
->GetStatement(
1349 "UPDATE moz_places "
1350 "SET title = :page_title "
1351 "WHERE id = :page_id ");
1352 NS_ENSURE_STATE(stmt
);
1355 mozStorageStatementScoper
scoper(stmt
);
1356 rv
= stmt
->BindInt64ByName("page_id"_ns
, mPlace
.placeId
);
1357 NS_ENSURE_SUCCESS(rv
, rv
);
1358 // Empty strings should clear the title, just like
1359 // nsNavHistory::SetPageTitle.
1360 if (mPlace
.title
.IsEmpty()) {
1361 rv
= stmt
->BindNullByName("page_title"_ns
);
1363 rv
= stmt
->BindStringByName("page_title"_ns
,
1364 StringHead(mPlace
.title
, TITLE_LENGTH_MAX
));
1366 NS_ENSURE_SUCCESS(rv
, rv
);
1367 rv
= stmt
->Execute();
1368 NS_ENSURE_SUCCESS(rv
, rv
);
1371 nsCOMPtr
<nsIRunnable
> event
=
1372 new NotifyTitleObservers(mPlace
.spec
, mPlace
.title
, mPlace
.guid
);
1373 rv
= NS_DispatchToMainThread(event
);
1374 NS_ENSURE_SUCCESS(rv
, rv
);
1380 SetPageTitle(const nsCString
& aSpec
, const nsAString
& aTitle
)
1381 : Runnable("places::SetPageTitle"), mHistory(History::GetService()) {
1382 mPlace
.spec
= aSpec
;
1383 mPlace
.title
= aTitle
;
1389 * Strong reference to the History object because we do not want it to
1390 * disappear out from under us.
1392 RefPtr
<History
> mHistory
;
1396 * Stores an embed visit, and notifies observers.
1399 * The VisitData of the visit to store as an embed visit.
1400 * @param [optional] aCallback
1401 * The mozIVisitInfoCallback to notify, if provided.
1403 * FIXME(emilio, bug 1595484): We should get rid of EMBED visits completely.
1405 void NotifyEmbedVisit(VisitData
& aPlace
,
1406 mozIVisitInfoCallback
* aCallback
= nullptr) {
1407 MOZ_ASSERT(aPlace
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
,
1408 "Must only pass TRANSITION_EMBED visits to this!");
1409 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
1411 nsCOMPtr
<nsIURI
> uri
;
1412 MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri
), aPlace
.spec
));
1419 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
1420 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>(
1421 "mozIVisitInfoCallback", aCallback
));
1422 bool ignoreResults
= false;
1423 Unused
<< aCallback
->GetIgnoreResults(&ignoreResults
);
1424 if (!ignoreResults
) {
1425 nsCOMPtr
<nsIRunnable
> event
=
1426 new NotifyPlaceInfoCallback(callback
, aPlace
, true, NS_OK
);
1427 (void)NS_DispatchToMainThread(event
);
1431 nsCOMPtr
<nsIRunnable
> event
= new NotifyManyVisitsObservers(aPlace
);
1432 (void)NS_DispatchToMainThread(event
);
1435 ////////////////////////////////////////////////////////////////////////////////
1438 History
* History::gService
= nullptr;
1441 : mShuttingDown(false),
1442 mShuttingDownMutex("History::mShuttingDownMutex"),
1443 mBlockShutdownMutex("History::mBlockShutdownMutex"),
1444 mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE
) {
1445 NS_ASSERTION(!gService
, "Ruh-roh! This service has already been created!");
1446 if (XRE_IsParentProcess()) {
1447 nsCOMPtr
<nsIProperties
> dirsvc
= components::Directory::Service();
1448 bool haveProfile
= false;
1452 dirsvc
->Has(NS_APP_USER_PROFILE_50_DIR
, &haveProfile
)) &&
1454 "Can't construct history service if there is no profile.");
1458 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
1459 NS_WARNING_ASSERTION(os
, "Observer service was not found!");
1461 (void)os
->AddObserver(this, TOPIC_PLACES_SHUTDOWN
, false);
1465 History::~History() {
1466 UnregisterWeakMemoryReporter(this);
1468 MOZ_ASSERT(gService
== this);
1472 void History::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
1474 class ConcurrentStatementsHolder final
: public mozIStorageCompletionCallback
{
1478 ConcurrentStatementsHolder() : mShutdownWasInvoked(false) {}
1480 static RefPtr
<ConcurrentStatementsHolder
> Create(
1481 mozIStorageConnection
* aDBConn
) {
1482 RefPtr
<ConcurrentStatementsHolder
> holder
=
1483 new ConcurrentStatementsHolder();
1484 nsresult rv
= aDBConn
->AsyncClone(true, holder
);
1485 if (NS_FAILED(rv
)) {
1491 NS_IMETHOD
Complete(nsresult aStatus
, nsISupports
* aConnection
) override
{
1492 if (NS_FAILED(aStatus
)) {
1495 mReadOnlyDBConn
= do_QueryInterface(aConnection
);
1496 // It's possible Shutdown was invoked before we were handed back the
1497 // cloned connection handle.
1498 if (mShutdownWasInvoked
) {
1503 // Now we can create our cached statements.
1505 if (!mIsVisitedStatement
) {
1506 (void)mReadOnlyDBConn
->CreateAsyncStatement(
1507 nsLiteralCString("SELECT 1 FROM moz_places h "
1508 "WHERE url_hash = hash(?1) AND url = ?1 AND "
1509 "last_visit_date NOTNULL "),
1510 getter_AddRefs(mIsVisitedStatement
));
1511 MOZ_ASSERT(mIsVisitedStatement
);
1512 auto queries
= std::move(mVisitedQueries
);
1513 if (mIsVisitedStatement
) {
1514 for (auto& query
: queries
) {
1515 query
->Execute(*mIsVisitedStatement
);
1523 void QueueVisitedStatement(RefPtr
<VisitedQuery
> aCallback
) {
1524 if (mIsVisitedStatement
) {
1525 aCallback
->Execute(*mIsVisitedStatement
);
1527 mVisitedQueries
.AppendElement(std::move(aCallback
));
1532 mShutdownWasInvoked
= true;
1533 if (mReadOnlyDBConn
) {
1534 mVisitedQueries
.Clear();
1535 DebugOnly
<nsresult
> rv
;
1536 if (mIsVisitedStatement
) {
1537 rv
= mIsVisitedStatement
->Finalize();
1538 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1540 rv
= mReadOnlyDBConn
->AsyncClose(nullptr);
1541 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1542 mReadOnlyDBConn
= nullptr;
1547 ~ConcurrentStatementsHolder() = default;
1549 nsCOMPtr
<mozIStorageAsyncConnection
> mReadOnlyDBConn
;
1550 nsCOMPtr
<mozIStorageAsyncStatement
> mIsVisitedStatement
;
1551 nsTArray
<RefPtr
<VisitedQuery
>> mVisitedQueries
;
1552 bool mShutdownWasInvoked
;
1555 NS_IMPL_ISUPPORTS(ConcurrentStatementsHolder
, mozIStorageCompletionCallback
)
1557 nsresult
History::QueueVisitedStatement(RefPtr
<VisitedQuery
> aQuery
) {
1558 MOZ_ASSERT(NS_IsMainThread());
1559 if (IsShuttingDown()) {
1560 return NS_ERROR_NOT_AVAILABLE
;
1563 if (!mConcurrentStatementsHolder
) {
1564 mozIStorageConnection
* dbConn
= GetDBConn();
1565 NS_ENSURE_STATE(dbConn
);
1566 mConcurrentStatementsHolder
= ConcurrentStatementsHolder::Create(dbConn
);
1567 if (!mConcurrentStatementsHolder
) {
1568 return NS_ERROR_NOT_AVAILABLE
;
1571 mConcurrentStatementsHolder
->QueueVisitedStatement(std::move(aQuery
));
1575 nsresult
History::InsertPlace(VisitData
& aPlace
) {
1576 MOZ_ASSERT(aPlace
.placeId
== 0, "should not have a valid place id!");
1577 MOZ_ASSERT(!aPlace
.shouldUpdateHidden
, "We should not need to update hidden");
1578 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1580 nsCOMPtr
<mozIStorageStatement
> stmt
= GetStatement(
1581 "INSERT INTO moz_places "
1582 "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
1583 "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, "
1584 ":frecency, :guid) ");
1585 NS_ENSURE_STATE(stmt
);
1586 mozStorageStatementScoper
scoper(stmt
);
1588 nsresult rv
= stmt
->BindStringByName("rev_host"_ns
, aPlace
.revHost
);
1589 NS_ENSURE_SUCCESS(rv
, rv
);
1590 rv
= URIBinder::Bind(stmt
, "url"_ns
, aPlace
.spec
);
1591 NS_ENSURE_SUCCESS(rv
, rv
);
1592 nsString title
= aPlace
.title
;
1593 // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
1594 if (title
.IsEmpty()) {
1595 rv
= stmt
->BindNullByName("title"_ns
);
1597 title
.Assign(StringHead(aPlace
.title
, TITLE_LENGTH_MAX
));
1598 rv
= stmt
->BindStringByName("title"_ns
, title
);
1600 NS_ENSURE_SUCCESS(rv
, rv
);
1601 rv
= stmt
->BindInt32ByName("typed"_ns
, aPlace
.typed
);
1602 NS_ENSURE_SUCCESS(rv
, rv
);
1603 // When inserting a page for a first visit that should not appear in
1604 // autocomplete, for example an error page, use a zero frecency.
1605 int32_t frecency
= aPlace
.shouldUpdateFrecency
? aPlace
.frecency
: 0;
1606 rv
= stmt
->BindInt32ByName("frecency"_ns
, frecency
);
1607 NS_ENSURE_SUCCESS(rv
, rv
);
1608 rv
= stmt
->BindInt32ByName("hidden"_ns
, aPlace
.hidden
);
1609 NS_ENSURE_SUCCESS(rv
, rv
);
1610 if (aPlace
.guid
.IsVoid()) {
1611 rv
= GenerateGUID(aPlace
.guid
);
1612 NS_ENSURE_SUCCESS(rv
, rv
);
1614 rv
= stmt
->BindUTF8StringByName("guid"_ns
, aPlace
.guid
);
1615 NS_ENSURE_SUCCESS(rv
, rv
);
1616 rv
= stmt
->Execute();
1617 NS_ENSURE_SUCCESS(rv
, rv
);
1622 nsresult
History::UpdatePlace(const VisitData
& aPlace
) {
1623 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1624 MOZ_ASSERT(aPlace
.placeId
> 0, "must have a valid place id!");
1625 MOZ_ASSERT(!aPlace
.guid
.IsVoid(), "must have a guid!");
1627 nsCOMPtr
<mozIStorageStatement
> stmt
;
1628 bool titleIsVoid
= aPlace
.title
.IsVoid();
1630 // Don't change the title.
1631 stmt
= GetStatement(
1632 "UPDATE moz_places "
1633 "SET hidden = :hidden, "
1636 "WHERE id = :page_id ");
1638 stmt
= GetStatement(
1639 "UPDATE moz_places "
1640 "SET title = :title, "
1641 "hidden = :hidden, "
1644 "WHERE id = :page_id ");
1646 NS_ENSURE_STATE(stmt
);
1647 mozStorageStatementScoper
scoper(stmt
);
1651 // An empty string clears the title.
1652 if (aPlace
.title
.IsEmpty()) {
1653 rv
= stmt
->BindNullByName("title"_ns
);
1655 rv
= stmt
->BindStringByName("title"_ns
,
1656 StringHead(aPlace
.title
, TITLE_LENGTH_MAX
));
1658 NS_ENSURE_SUCCESS(rv
, rv
);
1660 rv
= stmt
->BindInt32ByName("typed"_ns
, aPlace
.typed
);
1661 NS_ENSURE_SUCCESS(rv
, rv
);
1662 rv
= stmt
->BindInt32ByName("hidden"_ns
, aPlace
.hidden
);
1663 NS_ENSURE_SUCCESS(rv
, rv
);
1664 rv
= stmt
->BindUTF8StringByName("guid"_ns
, aPlace
.guid
);
1665 NS_ENSURE_SUCCESS(rv
, rv
);
1666 rv
= stmt
->BindInt64ByName("page_id"_ns
, aPlace
.placeId
);
1667 NS_ENSURE_SUCCESS(rv
, rv
);
1668 rv
= stmt
->Execute();
1669 NS_ENSURE_SUCCESS(rv
, rv
);
1674 nsresult
History::FetchPageInfo(VisitData
& _place
, bool* _exists
) {
1675 MOZ_ASSERT(!_place
.spec
.IsEmpty() || !_place
.guid
.IsEmpty(),
1676 "must have either a non-empty spec or guid!");
1677 MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
1681 // URI takes precedence.
1682 nsCOMPtr
<mozIStorageStatement
> stmt
;
1683 bool selectByURI
= !_place
.spec
.IsEmpty();
1685 stmt
= GetStatement(
1686 "SELECT guid, id, title, hidden, typed, frecency, visit_count, "
1688 "(SELECT id FROM moz_historyvisits "
1689 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1691 "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked "
1692 "FROM moz_places h "
1693 "WHERE url_hash = hash(:page_url) AND url = :page_url ");
1694 NS_ENSURE_STATE(stmt
);
1696 rv
= URIBinder::Bind(stmt
, "page_url"_ns
, _place
.spec
);
1697 NS_ENSURE_SUCCESS(rv
, rv
);
1699 stmt
= GetStatement(
1700 "SELECT url, id, title, hidden, typed, frecency, visit_count, "
1702 "(SELECT id FROM moz_historyvisits "
1703 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1705 "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked "
1706 "FROM moz_places h "
1707 "WHERE guid = :guid ");
1708 NS_ENSURE_STATE(stmt
);
1710 rv
= stmt
->BindUTF8StringByName("guid"_ns
, _place
.guid
);
1711 NS_ENSURE_SUCCESS(rv
, rv
);
1714 mozStorageStatementScoper
scoper(stmt
);
1716 rv
= stmt
->ExecuteStep(_exists
);
1717 NS_ENSURE_SUCCESS(rv
, rv
);
1724 if (_place
.guid
.IsEmpty()) {
1725 rv
= stmt
->GetUTF8String(0, _place
.guid
);
1726 NS_ENSURE_SUCCESS(rv
, rv
);
1730 rv
= stmt
->GetUTF8String(0, spec
);
1731 NS_ENSURE_SUCCESS(rv
, rv
);
1735 rv
= stmt
->GetInt64(1, &_place
.placeId
);
1736 NS_ENSURE_SUCCESS(rv
, rv
);
1739 rv
= stmt
->GetString(2, title
);
1740 NS_ENSURE_SUCCESS(rv
, rv
);
1742 // If the title we were given was void, that means we did not bother to set
1743 // it to anything. As a result, ignore the fact that we may have changed the
1744 // title (because we don't want to, that would be empty), and set the title
1745 // to what is currently stored in the datbase.
1746 if (_place
.title
.IsVoid()) {
1747 _place
.title
= title
;
1749 // Otherwise, just indicate if the title has changed.
1751 _place
.titleChanged
= !(_place
.title
.Equals(title
)) &&
1752 !(_place
.title
.IsEmpty() && title
.IsVoid());
1756 rv
= stmt
->GetInt32(3, &hidden
);
1757 NS_ENSURE_SUCCESS(rv
, rv
);
1758 _place
.hidden
= !!hidden
;
1761 rv
= stmt
->GetInt32(4, &typed
);
1762 NS_ENSURE_SUCCESS(rv
, rv
);
1763 _place
.typed
= !!typed
;
1765 rv
= stmt
->GetInt32(5, &_place
.frecency
);
1766 NS_ENSURE_SUCCESS(rv
, rv
);
1768 rv
= stmt
->GetInt32(6, &visitCount
);
1769 NS_ENSURE_SUCCESS(rv
, rv
);
1770 _place
.visitCount
= visitCount
;
1771 rv
= stmt
->GetInt64(7, &_place
.lastVisitTime
);
1772 NS_ENSURE_SUCCESS(rv
, rv
);
1773 rv
= stmt
->GetInt64(8, &_place
.lastVisitId
);
1774 NS_ENSURE_SUCCESS(rv
, rv
);
1776 rv
= stmt
->GetInt32(9, &bookmarked
);
1777 NS_ENSURE_SUCCESS(rv
, rv
);
1778 _place
.bookmarked
= bookmarked
== 1;
1783 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf
)
1786 History::CollectReports(nsIHandleReportCallback
* aHandleReport
,
1787 nsISupports
* aData
, bool aAnonymize
) {
1789 "explicit/history-links-hashtable", KIND_HEAP
, UNITS_BYTES
,
1790 SizeOfIncludingThis(HistoryMallocSizeOf
),
1791 "Memory used by the hashtable that records changes to the visited state "
1797 size_t History::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) {
1798 size_t size
= aMallocSizeOf(this);
1799 size
+= mTrackedURIs
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
1800 for (const auto& entry
: mTrackedURIs
.Values()) {
1801 size
+= entry
.SizeOfExcludingThis(aMallocSizeOf
);
1807 History
* History::GetService() {
1812 nsCOMPtr
<IHistory
> service
= components::History::Service();
1814 NS_ASSERTION(gService
, "Our constructor was not run?!");
1821 already_AddRefed
<History
> History::GetSingleton() {
1823 RefPtr
<History
> svc
= new History();
1824 MOZ_ASSERT(gService
== svc
.get());
1825 svc
->InitMemoryReporter();
1826 return svc
.forget();
1829 return do_AddRef(gService
);
1832 mozIStorageConnection
* History::GetDBConn() {
1833 MOZ_ASSERT(NS_IsMainThread());
1834 if (IsShuttingDown()) {
1838 mDB
= Database::GetDatabase();
1839 NS_ENSURE_TRUE(mDB
, nullptr);
1840 // This must happen on the main-thread, so when we try to use the connection
1841 // later it's initialized.
1842 mDB
->EnsureConnection();
1843 NS_ENSURE_TRUE(mDB
, nullptr);
1845 return mDB
->MainConn();
1848 const mozIStorageConnection
* History::GetConstDBConn() {
1849 MOZ_ASSERT(!NS_IsMainThread());
1851 MOZ_ASSERT(mDB
|| IsShuttingDown());
1852 if (IsShuttingDown() || !mDB
) {
1856 return mDB
->MainConn();
1859 void History::Shutdown() {
1860 MOZ_ASSERT(NS_IsMainThread());
1861 MutexAutoLock
lockedScope(mBlockShutdownMutex
);
1863 MutexAutoLock
lockedScope(mShuttingDownMutex
);
1864 MOZ_ASSERT(!mShuttingDown
&& "Shutdown was called more than once!");
1865 mShuttingDown
= true;
1867 if (mConcurrentStatementsHolder
) {
1868 mConcurrentStatementsHolder
->Shutdown();
1872 void History::AppendToRecentlyVisitedURIs(nsIURI
* aURI
, bool aHidden
) {
1873 PRTime now
= PR_Now();
1875 mRecentlyVisitedURIs
.InsertOrUpdate(aURI
, RecentURIVisit
{now
, aHidden
});
1877 // Remove entries older than RECENTLY_VISITED_URIS_MAX_AGE.
1878 for (auto iter
= mRecentlyVisitedURIs
.Iter(); !iter
.Done(); iter
.Next()) {
1879 if ((now
- iter
.Data().mTime
) > RECENTLY_VISITED_URIS_MAX_AGE
) {
1885 ////////////////////////////////////////////////////////////////////////////////
1889 History::VisitURI(nsIWidget
* aWidget
, nsIURI
* aURI
, nsIURI
* aLastVisitedURI
,
1890 uint32_t aFlags
, uint64_t aBrowserId
) {
1891 MOZ_ASSERT(NS_IsMainThread());
1892 NS_ENSURE_ARG(aURI
);
1894 if (IsShuttingDown()) {
1898 if (XRE_IsContentProcess()) {
1899 if (!BaseHistory::CanStore(aURI
)) {
1903 NS_ENSURE_ARG(aWidget
);
1904 BrowserChild
* browserChild
= aWidget
->GetOwningBrowserChild();
1905 NS_ENSURE_TRUE(browserChild
, NS_ERROR_FAILURE
);
1906 (void)browserChild
->SendVisitURI(aURI
, aLastVisitedURI
, aFlags
, aBrowserId
);
1910 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
1911 NS_ENSURE_TRUE(navHistory
, NS_ERROR_OUT_OF_MEMORY
);
1913 // Silently return if URI is something we shouldn't add to DB.
1915 nsresult rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
1916 NS_ENSURE_SUCCESS(rv
, rv
);
1921 bool reload
= false;
1922 if (aLastVisitedURI
) {
1923 rv
= aURI
->Equals(aLastVisitedURI
, &reload
);
1924 NS_ENSURE_SUCCESS(rv
, rv
);
1927 nsTArray
<VisitData
> placeArray(1);
1928 placeArray
.AppendElement(VisitData(aURI
, aLastVisitedURI
));
1929 VisitData
& place
= placeArray
.ElementAt(0);
1930 NS_ENSURE_FALSE(place
.spec
.IsEmpty(), NS_ERROR_INVALID_ARG
);
1932 place
.visitTime
= PR_Now();
1934 // Assigns a type to the edge in the visit linked list. Each type will be
1935 // considered differently when weighting the frecency of a location.
1936 uint32_t recentFlags
= navHistory
->GetRecentFlags(aURI
);
1937 bool isFollowedLink
= recentFlags
& nsNavHistory::RECENT_ACTIVATED
;
1939 // Embed visits should never be added to the database, and the same is valid
1940 // for redirects across frames.
1941 // For the above reasoning non-toplevel transitions are handled at first.
1942 // if the visit is toplevel or a non-toplevel followed link, then it can be
1943 // handled as usual and stored on disk.
1945 uint32_t transitionType
= nsINavHistoryService::TRANSITION_LINK
;
1947 if (!(aFlags
& IHistory::TOP_LEVEL
) && !isFollowedLink
) {
1948 // A frame redirected to a new site without user interaction.
1949 transitionType
= nsINavHistoryService::TRANSITION_EMBED
;
1950 } else if (aFlags
& IHistory::REDIRECT_TEMPORARY
) {
1951 transitionType
= nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
;
1952 } else if (aFlags
& IHistory::REDIRECT_PERMANENT
) {
1953 transitionType
= nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT
;
1954 } else if (reload
) {
1955 transitionType
= nsINavHistoryService::TRANSITION_RELOAD
;
1956 } else if ((recentFlags
& nsNavHistory::RECENT_TYPED
) &&
1957 !(aFlags
& IHistory::UNRECOVERABLE_ERROR
)) {
1958 // Don't mark error pages as typed, even if they were actually typed by
1959 // the user. This is useful to limit their score in autocomplete.
1960 transitionType
= nsINavHistoryService::TRANSITION_TYPED
;
1961 } else if (recentFlags
& nsNavHistory::RECENT_BOOKMARKED
) {
1962 transitionType
= nsINavHistoryService::TRANSITION_BOOKMARK
;
1963 } else if (!(aFlags
& IHistory::TOP_LEVEL
) && isFollowedLink
) {
1964 // User activated a link in a frame.
1965 transitionType
= nsINavHistoryService::TRANSITION_FRAMED_LINK
;
1968 place
.SetTransitionType(transitionType
);
1969 bool isRedirect
= aFlags
& IHistory::REDIRECT_SOURCE
;
1971 place
.useFrecencyRedirectBonus
=
1972 (aFlags
& IHistory::REDIRECT_SOURCE_PERMANENT
) ||
1973 transitionType
!= nsINavHistoryService::TRANSITION_TYPED
;
1975 place
.hidden
= GetHiddenState(isRedirect
, place
.transitionType
);
1977 // Error pages should never be autocompleted.
1978 if (aFlags
& IHistory::UNRECOVERABLE_ERROR
) {
1979 place
.shouldUpdateFrecency
= false;
1982 // Do not save a reloaded uri if we have visited the same URI recently.
1984 auto entry
= mRecentlyVisitedURIs
.Lookup(aURI
);
1985 // Check if the entry exists and is younger than
1986 // RECENTLY_VISITED_URIS_MAX_AGE.
1987 if (entry
&& (PR_Now() - entry
->mTime
) < RECENTLY_VISITED_URIS_MAX_AGE
) {
1988 bool wasHidden
= entry
->mHidden
;
1989 // Regardless of whether we store the visit or not, we must update the
1990 // stored visit time.
1991 AppendToRecentlyVisitedURIs(aURI
, place
.hidden
);
1992 // We always want to store an unhidden visit, if the previous visits were
1993 // hidden, because otherwise the page may not appear in the history UI.
1994 // This can happen for example at a page redirecting to itself.
1995 if (!wasHidden
|| place
.hidden
) {
1996 // We can skip this visit.
2002 nsCOMPtr
<nsIBrowserWindowTracker
> bwt
=
2003 do_ImportModule("resource:///modules/BrowserWindowTracker.jsm",
2004 "BrowserWindowTracker", &rv
);
2005 if (NS_SUCCEEDED(rv
)) {
2006 // Only if it is running on Firefox, continue to process the followings.
2007 nsCOMPtr
<nsISupports
> browser
;
2008 rv
= bwt
->GetBrowserById(aBrowserId
, getter_AddRefs(browser
));
2009 NS_ENSURE_SUCCESS(rv
, rv
);
2011 RefPtr
<Element
> browserElement
= static_cast<Element
*>(browser
.get());
2013 nsAutoString triggeringSearchEngineURL
;
2014 browserElement
->GetAttribute(u
"triggeringSearchEngineURL"_ns
,
2015 triggeringSearchEngineURL
);
2016 if (!triggeringSearchEngineURL
.IsEmpty() &&
2017 place
.spec
.Equals(NS_ConvertUTF16toUTF8(triggeringSearchEngineURL
))) {
2018 nsAutoString triggeringSearchEngine
;
2019 browserElement
->GetAttribute(u
"triggeringSearchEngine"_ns
,
2020 triggeringSearchEngine
);
2021 place
.triggeringSearchEngine
.Assign(
2022 NS_ConvertUTF16toUTF8(triggeringSearchEngine
));
2025 nsAutoString triggeringSponsoredURL
;
2026 browserElement
->GetAttribute(u
"triggeringSponsoredURL"_ns
,
2027 triggeringSponsoredURL
);
2028 if (!triggeringSponsoredURL
.IsEmpty()) {
2029 place
.triggeringSponsoredURL
.Assign(
2030 NS_ConvertUTF16toUTF8(triggeringSponsoredURL
));
2032 nsAutoString triggeringSponsoredURLVisitTimeMS
;
2033 browserElement
->GetAttribute(u
"triggeringSponsoredURLVisitTimeMS"_ns
,
2034 triggeringSponsoredURLVisitTimeMS
);
2035 place
.triggeringSponsoredURLVisitTimeMS
=
2036 triggeringSponsoredURLVisitTimeMS
.ToInteger64(&rv
);
2037 NS_ENSURE_SUCCESS(rv
, rv
);
2039 // Get base domain. We need to get it here since nsIEffectiveTLDService
2040 // referred in DomainNameFromURI should access on main thread.
2041 nsCOMPtr
<nsIURI
> currentURL
;
2042 rv
= NS_MutateURI(new net::nsStandardURL::Mutator())
2043 .SetSpec(place
.spec
)
2044 .Finalize(currentURL
);
2045 NS_ENSURE_SUCCESS(rv
, rv
);
2046 nsCOMPtr
<nsIURI
> sponsoredURL
;
2047 rv
= NS_MutateURI(new net::nsStandardURL::Mutator())
2048 .SetSpec(place
.triggeringSponsoredURL
)
2049 .Finalize(sponsoredURL
);
2050 NS_ENSURE_SUCCESS(rv
, rv
);
2051 navHistory
->DomainNameFromURI(currentURL
, place
.baseDomain
);
2052 navHistory
->DomainNameFromURI(sponsoredURL
,
2053 place
.triggeringSponsoredURLBaseDomain
);
2058 // EMBED visits should not go through the database.
2059 // They exist only to keep track of isVisited status during the session.
2060 if (place
.transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
2061 NotifyEmbedVisit(place
);
2063 mozIStorageConnection
* dbConn
= GetDBConn();
2064 NS_ENSURE_STATE(dbConn
);
2066 rv
= InsertVisitedURIs::Start(dbConn
, std::move(placeArray
));
2067 NS_ENSURE_SUCCESS(rv
, rv
);
2074 History::SetURITitle(nsIURI
* aURI
, const nsAString
& aTitle
) {
2075 MOZ_ASSERT(NS_IsMainThread());
2076 NS_ENSURE_ARG(aURI
);
2078 if (IsShuttingDown()) {
2082 if (XRE_IsContentProcess()) {
2083 auto* cpc
= dom::ContentChild::GetSingleton();
2084 MOZ_ASSERT(cpc
, "Content Protocol is NULL!");
2085 Unused
<< cpc
->SendSetURITitle(aURI
, PromiseFlatString(aTitle
));
2089 nsNavHistory
* navHistory
= nsNavHistory::GetHistoryService();
2091 // At first, it seems like nav history should always be available here, no
2094 // nsNavHistory fails to register as a service if there is no profile in
2095 // place (for instance, if user is choosing a profile).
2097 // Maybe the correct thing to do is to not register this service if no
2098 // profile has been selected?
2100 NS_ENSURE_TRUE(navHistory
, NS_ERROR_FAILURE
);
2103 nsresult rv
= navHistory
->CanAddURI(aURI
, &canAdd
);
2104 NS_ENSURE_SUCCESS(rv
, rv
);
2109 mozIStorageConnection
* dbConn
= GetDBConn();
2110 NS_ENSURE_STATE(dbConn
);
2112 return SetPageTitle::Start(dbConn
, aURI
, aTitle
);
2115 ////////////////////////////////////////////////////////////////////////////////
2116 //// mozIAsyncHistory
2119 History::UpdatePlaces(JS::Handle
<JS::Value
> aPlaceInfos
,
2120 mozIVisitInfoCallback
* aCallback
, JSContext
* aCtx
) {
2121 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED
);
2122 NS_ENSURE_TRUE(!aPlaceInfos
.isPrimitive(), NS_ERROR_INVALID_ARG
);
2124 uint32_t infosLength
;
2125 JS::Rooted
<JSObject
*> infos(aCtx
);
2126 nsresult rv
= GetJSArrayFromJSValue(aPlaceInfos
, aCtx
, &infos
, &infosLength
);
2127 NS_ENSURE_SUCCESS(rv
, rv
);
2129 uint32_t initialUpdatedCount
= 0;
2131 nsTArray
<VisitData
> visitData
;
2132 for (uint32_t i
= 0; i
< infosLength
; i
++) {
2133 JS::Rooted
<JSObject
*> info(aCtx
);
2134 nsresult rv
= GetJSObjectFromArray(aCtx
, infos
, i
, &info
);
2135 NS_ENSURE_SUCCESS(rv
, rv
);
2137 nsCOMPtr
<nsIURI
> uri
= GetURIFromJSObject(aCtx
, info
, "uri");
2141 GetStringFromJSObject(aCtx
, info
, "guid", fatGUID
);
2142 if (fatGUID
.IsVoid()) {
2143 guid
.SetIsVoid(true);
2145 CopyUTF16toUTF8(fatGUID
, guid
);
2149 // Make sure that any uri we are given can be added to history, and if not,
2150 // skip it (CanAddURI will notify our callback for us).
2151 if (uri
&& !CanAddURI(uri
, guid
, aCallback
)) {
2155 // We must have at least one of uri or guid.
2156 NS_ENSURE_ARG(uri
|| !guid
.IsVoid());
2158 // If we were given a guid, make sure it is valid.
2159 bool isValidGUID
= IsValidGUID(guid
);
2160 NS_ENSURE_ARG(guid
.IsVoid() || isValidGUID
);
2163 GetStringFromJSObject(aCtx
, info
, "title", title
);
2165 JS::Rooted
<JSObject
*> visits(aCtx
, nullptr);
2167 JS::Rooted
<JS::Value
> visitsVal(aCtx
);
2168 bool rc
= JS_GetProperty(aCtx
, info
, "visits", &visitsVal
);
2169 NS_ENSURE_TRUE(rc
, NS_ERROR_UNEXPECTED
);
2170 if (!visitsVal
.isPrimitive()) {
2171 visits
= visitsVal
.toObjectOrNull();
2173 if (!JS::IsArrayObject(aCtx
, visits
, &isArray
)) {
2174 return NS_ERROR_UNEXPECTED
;
2177 return NS_ERROR_INVALID_ARG
;
2181 NS_ENSURE_ARG(visits
);
2183 uint32_t visitsLength
= 0;
2185 (void)JS::GetArrayLength(aCtx
, visits
, &visitsLength
);
2187 NS_ENSURE_ARG(visitsLength
> 0);
2189 // Check each visit, and build our array of VisitData objects.
2190 visitData
.SetCapacity(visitData
.Length() + visitsLength
);
2191 for (uint32_t j
= 0; j
< visitsLength
; j
++) {
2192 JS::Rooted
<JSObject
*> visit(aCtx
);
2193 rv
= GetJSObjectFromArray(aCtx
, visits
, j
, &visit
);
2194 NS_ENSURE_SUCCESS(rv
, rv
);
2196 VisitData
& data
= *visitData
.AppendElement(VisitData(uri
));
2197 if (!title
.IsEmpty()) {
2199 } else if (!title
.IsVoid()) {
2200 // Setting data.title to an empty string wouldn't make it non-void.
2201 data
.title
.SetIsVoid(false);
2205 // We must have a date and a transaction type!
2206 rv
= GetIntFromJSObject(aCtx
, visit
, "visitDate", &data
.visitTime
);
2207 NS_ENSURE_SUCCESS(rv
, rv
);
2208 // visitDate should be in microseconds. It's easy to do the wrong thing
2209 // and pass milliseconds to updatePlaces, so we lazily check for that.
2210 // While it's not easily distinguishable, since both are integers, we can
2211 // check if the value is very far in the past, and assume it's probably
2213 if (data
.visitTime
< (PR_Now() / 1000)) {
2215 nsCOMPtr
<nsIXPConnect
> xpc
= nsIXPConnect::XPConnect();
2216 Unused
<< xpc
->DebugDumpJSStack(false, false, false);
2217 MOZ_CRASH("invalid time format passed to updatePlaces");
2219 return NS_ERROR_INVALID_ARG
;
2221 uint32_t transitionType
= 0;
2222 rv
= GetIntFromJSObject(aCtx
, visit
, "transitionType", &transitionType
);
2223 NS_ENSURE_SUCCESS(rv
, rv
);
2224 NS_ENSURE_ARG_RANGE(transitionType
, nsINavHistoryService::TRANSITION_LINK
,
2225 nsINavHistoryService::TRANSITION_RELOAD
);
2226 data
.SetTransitionType(transitionType
);
2227 data
.hidden
= GetHiddenState(false, transitionType
);
2229 // If the visit is an embed visit, we do not actually add it to the
2231 if (transitionType
== nsINavHistoryService::TRANSITION_EMBED
) {
2232 NotifyEmbedVisit(data
, aCallback
);
2233 visitData
.RemoveLastElement();
2234 initialUpdatedCount
++;
2238 // The referrer is optional.
2239 nsCOMPtr
<nsIURI
> referrer
=
2240 GetURIFromJSObject(aCtx
, visit
, "referrerURI");
2242 (void)referrer
->GetSpec(data
.referrerSpec
);
2247 mozIStorageConnection
* dbConn
= GetDBConn();
2248 NS_ENSURE_STATE(dbConn
);
2250 nsMainThreadPtrHandle
<mozIVisitInfoCallback
> callback(
2251 new nsMainThreadPtrHolder
<mozIVisitInfoCallback
>("mozIVisitInfoCallback",
2254 // It is possible that all of the visits we were passed were dissallowed by
2255 // CanAddURI, which isn't an error. If we have no visits to add, however,
2256 // we should not call InsertVisitedURIs::Start.
2257 if (visitData
.Length()) {
2258 nsresult rv
= InsertVisitedURIs::Start(dbConn
, std::move(visitData
),
2259 callback
, initialUpdatedCount
);
2260 NS_ENSURE_SUCCESS(rv
, rv
);
2261 } else if (aCallback
) {
2262 // Be sure to notify that all of our operations are complete. This
2263 // is dispatched to the background thread first and redirected to the
2264 // main thread from there to make sure that all database notifications
2265 // and all embed or canAddURI notifications have finished.
2267 // Note: if we're inserting anything, it's the responsibility of
2268 // InsertVisitedURIs to call the completion callback, as here we won't
2269 // know how yet many items we will successfully insert/update.
2270 nsCOMPtr
<nsIEventTarget
> backgroundThread
= do_GetInterface(dbConn
);
2271 NS_ENSURE_TRUE(backgroundThread
, NS_ERROR_UNEXPECTED
);
2272 nsCOMPtr
<nsIRunnable
> event
=
2273 new NotifyCompletion(callback
, initialUpdatedCount
);
2274 return backgroundThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
2281 History::IsURIVisited(nsIURI
* aURI
, mozIVisitedStatusCallback
* aCallback
) {
2282 NS_ENSURE_STATE(NS_IsMainThread());
2283 NS_ENSURE_ARG(aURI
);
2284 NS_ENSURE_ARG(aCallback
);
2286 return VisitedQuery::Start(aURI
, aCallback
);
2289 void History::StartPendingVisitedQueries(PendingVisitedQueries
&& aQueries
) {
2290 if (XRE_IsContentProcess()) {
2291 auto* cpc
= dom::ContentChild::GetSingleton();
2292 MOZ_ASSERT(cpc
, "Content Protocol is NULL!");
2294 // Fairly arbitrary limit on the number of URLs we send at a time, to avoid
2295 // going over the IPC message size limit... Note that this is imperfect (we
2296 // could have very long URIs), so this is a best-effort kind of thing. See
2298 constexpr size_t kBatchLimit
= 4000;
2300 nsTArray
<RefPtr
<nsIURI
>> uris(aQueries
.Count());
2301 for (const auto& entry
: aQueries
) {
2302 uris
.AppendElement(entry
.GetKey());
2303 MOZ_ASSERT(entry
.GetData().IsEmpty(),
2304 "Child process shouldn't have parent requests");
2305 if (uris
.Length() == kBatchLimit
) {
2306 Unused
<< cpc
->SendStartVisitedQueries(uris
);
2307 uris
.ClearAndRetainStorage();
2311 if (!uris
.IsEmpty()) {
2312 Unused
<< cpc
->SendStartVisitedQueries(uris
);
2315 // TODO(bug 1594368): We could do a single query, as long as we can
2316 // then notify each URI individually.
2317 for (auto& entry
: aQueries
) {
2318 nsresult queryStatus
= VisitedQuery::Start(
2319 entry
.GetKey(), std::move(*entry
.GetModifiableData()));
2320 Unused
<< NS_WARN_IF(NS_FAILED(queryStatus
));
2325 ////////////////////////////////////////////////////////////////////////////////
2329 History::Observe(nsISupports
* aSubject
, const char* aTopic
,
2330 const char16_t
* aData
) {
2331 if (strcmp(aTopic
, TOPIC_PLACES_SHUTDOWN
) == 0) {
2334 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
2336 (void)os
->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN
);
2343 ////////////////////////////////////////////////////////////////////////////////
2346 NS_IMPL_ISUPPORTS(History
, IHistory
, mozIAsyncHistory
, nsIObserver
,
2349 } // namespace places
2350 } // namespace mozilla