Bug 1826753 - Make toolkit/components/places buildable outside of a unified build...
[gecko.git] / toolkit / components / places / History.cpp
blob019e072a05a367f4a5a1a654ffbb9a965b2a5520
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"
16 #include "History.h"
17 #include "nsNavHistory.h"
18 #include "nsNavBookmarks.h"
19 #include "Helpers.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"
42 #include "jsapi.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;
61 namespace mozilla {
62 namespace places {
64 ////////////////////////////////////////////////////////////////////////////////
65 //// Global Defines
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 ////////////////////////////////////////////////////////////////////////////////
73 //// VisitData
75 struct VisitData {
76 VisitData()
77 : placeId(0),
78 visitId(0),
79 hidden(true),
80 shouldUpdateHidden(true),
81 typed(false),
82 transitionType(UINT32_MAX),
83 visitTime(0),
84 frecency(-1),
85 lastVisitId(0),
86 lastVisitTime(0),
87 visitCount(0),
88 referrerVisitId(0),
89 titleChanged(false),
90 shouldUpdateFrecency(true),
91 useFrecencyRedirectBonus(false),
92 source(nsINavHistoryService::VISIT_SOURCE_ORGANIC),
93 triggeringPlaceId(0),
94 triggeringSponsoredURLVisitTimeMS(0),
95 bookmarked(false) {
96 guid.SetIsVoid(true);
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)
105 : placeId(0),
106 visitId(0),
107 hidden(true),
108 shouldUpdateHidden(true),
109 typed(false),
110 transitionType(UINT32_MAX),
111 visitTime(0),
112 frecency(-1),
113 lastVisitId(0),
114 lastVisitTime(0),
115 visitCount(0),
116 referrerVisitId(0),
117 titleChanged(false),
118 shouldUpdateFrecency(true),
119 useFrecencyRedirectBonus(false),
120 source(nsINavHistoryService::VISIT_SOURCE_ORGANIC),
121 triggeringPlaceId(0),
122 triggeringSponsoredURLVisitTimeMS(0),
123 bookmarked(false) {
124 MOZ_ASSERT(aURI);
125 if (aURI) {
126 (void)aURI->GetSpec(spec);
127 (void)GetReversedHostname(aURI, revHost);
129 if (aReferrer) {
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;
152 int64_t placeId;
153 nsCString guid;
154 int64_t visitId;
155 nsCString spec;
156 nsCString baseDomain;
157 nsString revHost;
158 bool hidden;
159 bool shouldUpdateHidden;
160 bool typed;
161 uint32_t transitionType;
162 PRTime visitTime;
163 int32_t frecency;
164 int64_t lastVisitId;
165 PRTime lastVisitTime;
166 uint32_t visitCount;
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.
174 nsString title;
176 nsCString referrerSpec;
177 int64_t referrerVisitId;
179 // TODO bug 626836 hook up hidden and typed change tracking too!
180 bool titleChanged;
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;
189 uint16_t source;
190 nsCString triggeringSearchEngine;
191 int64_t triggeringPlaceId;
192 nsCString triggeringSponsoredURL;
193 nsCString triggeringSponsoredURLBaseDomain;
194 int64_t triggeringSponsoredURLVisitTimeMS;
195 bool bookmarked;
198 ////////////////////////////////////////////////////////////////////////////////
199 //// Anonymous Helpers
201 namespace {
204 * Convert the given js value to a js array.
206 * @param [in] aValue
207 * the JS value to convert.
208 * @param [in] aCtx
209 * The JSContext for aValue.
210 * @param [out] _array
211 * the JS array.
212 * @param [out] _arrayLength
213 * _array's length.
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());
220 bool isArray;
221 if (!JS::IsArrayObject(aCtx, val, &isArray)) {
222 return NS_ERROR_UNEXPECTED;
224 if (isArray) {
225 _array.set(val);
226 (void)JS::GetArrayLength(aCtx, _array, _arrayLength);
227 NS_ENSURE_ARG(*_arrayLength > 0);
228 return NS_OK;
232 // Build a temporary array to store this one item so the code below can
233 // just loop.
234 *_arrayLength = 1;
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);
240 return NS_OK;
244 * Attemps to convert a given js value to a nsIURI object.
245 * @param aCtx
246 * The JSContext for aValue.
247 * @param 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());
258 nsresult rv =
259 xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrappedObj));
260 NS_ENSURE_SUCCESS(rv, nullptr);
261 nsCOMPtr<nsIURI> uri = do_QueryInterface(wrappedObj->Native());
262 return uri.forget();
264 return nullptr;
268 * Obtains an nsIURI from the "uri" property of a JSObject.
270 * @param aCtx
271 * The JSContext for aObject.
272 * @param aObject
273 * The JSObject to get the URI from.
274 * @param aProperty
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.
289 * @param aCtx
290 * The JSContext for aObject.
291 * @param aValue
292 * The JS value to convert.
293 * @param _string
294 * The string to populate with the value, or set it to void.
296 void GetJSValueAsString(JSContext* aCtx, const JS::Value& aValue,
297 nsString& _string) {
298 if (aValue.isUndefined() || !(aValue.isNull() || aValue.isString())) {
299 _string.SetIsVoid(true);
300 return;
303 // |null| in JS maps to the empty string.
304 if (aValue.isNull()) {
305 _string.Truncate();
306 return;
309 if (!AssignJSString(aCtx, _string, aValue.toString())) {
310 _string.SetIsVoid(true);
315 * Obtains the specified property of a JSObject.
317 * @param aCtx
318 * The JSContext for aObject.
319 * @param aObject
320 * The JSObject to get the string from.
321 * @param aProperty
322 * The property to get the value from.
323 * @param _string
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);
330 if (!rc) {
331 _string.SetIsVoid(true);
332 return;
333 } else {
334 GetJSValueAsString(aCtx, val, _string);
339 * Obtains the specified property of a JSObject.
341 * @param aCtx
342 * The JSContext for aObject.
343 * @param aObject
344 * The JSObject to get the int from.
345 * @param aProperty
346 * The property to get the value from.
347 * @param _int
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());
362 double num;
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);
368 return NS_OK;
372 * Obtains the specified property of a JSObject.
374 * @pre aArray must be an Array object.
376 * @param aCtx
377 * The JSContext for aArray.
378 * @param aArray
379 * The JSObject to get the object from.
380 * @param aIndex
381 * The index to get the object from.
382 * @param objOut
383 * Set to the JSObject pointer on success.
385 nsresult GetJSObjectFromArray(JSContext* aCtx, JS::Handle<JSObject*> aArray,
386 uint32_t aIndex,
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());
393 return NS_OK;
396 } // namespace
398 class VisitedQuery final : public AsyncStatementCallback {
399 public:
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))) {
433 return;
436 nsCOMPtr<mozIStoragePendingStatement> handle;
437 rv = aStatement.ExecuteAsync(this, getter_AddRefs(handle));
438 MOZ_ASSERT(NS_SUCCEEDED(rv));
439 Unused << rv;
442 NS_IMETHOD HandleResult(mozIStorageResultSet* aResults) override {
443 // If this method is called, we've gotten results, which means we have a
444 // visit.
445 mIsVisited = true;
446 return NS_OK;
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.
452 return NS_OK;
455 NS_IMETHOD HandleCompletion(uint16_t aReason) override {
456 if (aReason == mozIStorageStatementCallback::REASON_FINISHED) {
457 NotifyVisitedStatus();
459 return NS_OK;
462 void NotifyVisitedStatus() {
463 // If an external handling callback is provided, just notify through it.
464 if (mCallback) {
465 mCallback->IsVisited(mURI, mIsVisited);
466 return;
469 if (History* history = History::GetService()) {
470 auto status = mIsVisited ? IHistory::VisitedStatus::Visited
471 : IHistory::VisitedStatus::Unvisited;
472 history->NotifyVisited(mURI, status, &mContentProcessesToNotify);
476 private:
477 explicit VisitedQuery(
478 nsIURI* aURI,
479 const nsMainThreadPtrHandle<mozIVisitedStatusCallback>& aCallback)
480 : mURI(aURI), mCallback(aCallback) {}
482 explicit VisitedQuery(nsIURI* aURI,
483 History::ContentParentSet&& aContentProcessesToNotify)
484 : mURI(aURI),
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 {
501 public:
502 explicit NotifyManyVisitsObservers(const VisitData& aPlace)
503 : Runnable("places::NotifyManyVisitsObservers"),
504 mPlaces({aPlace}),
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) {
515 if (aObsService) {
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);
528 return NS_OK;
531 void AddPlaceForNotify(const VisitData& aPlace,
532 Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
533 if (aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
534 return;
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.
570 return NS_OK;
573 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
574 if (!navHistory) {
575 NS_WARNING(
576 "Trying to notify visits observers but cannot get the history "
577 "service!");
578 return NS_OK;
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));
589 if (!uri) {
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);
602 return NS_OK;
605 private:
606 AutoTArray<VisitData, 1> mPlaces;
607 RefPtr<History> mHistory;
611 * Notifies observers about a pages title changing.
613 class NotifyTitleObservers : public Runnable {
614 public:
616 * Notifies observers on the main thread.
618 * @param aSpec
619 * The spec of the URI to notify about.
620 * @param aTitle
621 * The new title to notify about.
623 NotifyTitleObservers(const nsCString& aSpec, const nsString& aTitle,
624 const nsCString& aGUID)
625 : Runnable("places::NotifyTitleObservers"),
626 mSpec(aSpec),
627 mTitle(aTitle),
628 mGUID(aGUID) {}
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);
647 return NS_OK;
650 private:
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 {
661 public:
662 NotifyPlaceInfoCallback(
663 const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
664 const VisitData& aPlace, bool aIsSingleVisit, nsresult aResult)
665 : Runnable("places::NotifyPlaceInfoCallback"),
666 mCallback(aCallback),
667 mPlace(aPlace),
668 mResult(aResult),
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()) {
679 MOZ_ALWAYS_SUCCEEDS(
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);
700 } else {
701 // Same as above.
702 place = new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(),
703 mPlace.title, -1);
706 if (NS_SUCCEEDED(mResult) && hasValidURIs) {
707 (void)mCallback->HandleResult(place);
708 } else {
709 (void)mCallback->HandleError(mResult, place);
712 return NS_OK;
715 private:
716 nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
717 VisitData mPlace;
718 const nsresult mResult;
719 bool mIsSingleVisit;
723 * Notifies a callback object when the operation is complete.
725 class NotifyCompletion : public Runnable {
726 public:
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);
739 } else {
740 (void)NS_DispatchToMainThread(this);
742 return NS_OK;
745 private:
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.
754 * @param aURI
755 * The URI to check.
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);
768 bool canAdd;
769 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
770 if (NS_SUCCEEDED(rv) && canAdd) {
771 return true;
774 // We cannot add the URI. Notify the callback, if we were given one.
775 if (aCallback) {
776 VisitData place(aURI);
777 place.guid = aGUID;
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);
786 return false;
790 * Adds a visit to the database.
792 class InsertVisitedURIs final : public Runnable {
793 public:
795 * Adds a visit to the database asynchronously.
797 * @param aConnection
798 * The database connection to use for these operations.
799 * @param aPlaces
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?!");
814 if (!navHistory) {
815 return NS_ERROR_FAILURE;
818 nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
819 new nsMainThreadPtrHolder<mozIVisitInfoCallback>(
820 "mozIVisitInfoCallback", aCallback));
821 bool ignoreErrors = false, ignoreResults = false;
822 if (aCallback) {
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);
839 return NS_OK;
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());
853 if (!!mCallback) {
854 NS_DispatchToMainThread(
855 new NotifyCompletion(mCallback, mSuccessfulUpdatedCount));
857 return rv;
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()) {
866 return NS_OK;
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
886 // apart.
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
891 // visit we added.
892 bool known =
893 lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
894 if (!known) {
895 nsresult rv = mHistory->FetchPageInfo(place, &known);
896 if (NS_FAILED(rv)) {
897 if (!!mCallback && !mIgnoreErrors) {
898 nsCOMPtr<nsIRunnable> event =
899 new NotifyPlaceInfoCallback(mCallback, place, true, rv);
900 return NS_DispatchToMainThread(event);
902 return NS_OK;
904 lastFetchedPlace = &mPlaces.ElementAt(i);
905 lastFetchedVisitCount = lastFetchedPlace->visitCount;
906 } else {
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) {
922 place.typed = true;
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);
940 if (!!mCallback) {
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 ||
957 numRemaining == 0) {
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);
1006 return NS_OK;
1009 private:
1010 InsertVisitedURIs(
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");
1024 #ifdef DEBUG
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!");
1031 #endif
1035 * Inserts or updates the entry in moz_places for this visit, adds the visit,
1036 * and updates the frecency of the place.
1038 * @param aKnown
1039 * True if we already have an entry for this place in moz_places, false
1040 * otherwise.
1041 * @param aPlace
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.
1049 nsresult rv;
1050 if (aKnown) {
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.
1055 else {
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);
1074 return NS_OK;
1078 * Fetches information about a referrer for aPlace if it was a recent
1079 * visit or not.
1081 * @param aPlace
1082 * The VisitData for the visit we will eventually add.
1085 void FetchReferrerInfo(VisitData& aPlace) {
1086 if (aPlace.referrerSpec.IsEmpty()) {
1087 return;
1090 VisitData referrer;
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)) {
1094 referrer = aPlace;
1095 // The page last visit id is also the referrer visit id.
1096 aPlace.referrerVisitId = aPlace.lastVisitId;
1097 } else {
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.
1118 * @param _place
1119 * The VisitData for the place we need to know visit information about.
1121 nsresult AddVisit(VisitData& _place) {
1122 MOZ_ASSERT(_place.placeId > 0);
1124 nsresult rv;
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);
1152 } else {
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);
1163 return NS_OK;
1167 * Updates the frecency, and possibly the hidden-ness of aPlace.
1169 * @param 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);
1176 nsresult rv;
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);
1187 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 "
1200 "SET hidden = 0 "
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);
1212 return NS_OK;
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;
1220 } else {
1221 aPlace.source = nsINavHistoryService::VISIT_SOURCE_ORGANIC;
1224 if (aPlace.triggeringSponsoredURL.IsEmpty()) {
1225 // No triggeringSponsoredURL.
1226 return NS_OK;
1229 if ((aPlace.visitTime -
1230 aPlace.triggeringSponsoredURLVisitTimeMS * PR_USEC_PER_MSEC) >
1231 StaticPrefs::browser_places_sponsoredSession_timeoutSecs() *
1232 PR_USEC_PER_SEC) {
1233 // Sponsored session timeout.
1234 return NS_OK;
1237 if (aPlace.spec.Equals(aPlace.triggeringSponsoredURL)) {
1238 // This place is the triggeringSponsoredURL.
1239 aPlace.source = nsINavHistoryService::VISIT_SOURCE_SPONSORED;
1240 return NS_OK;
1243 if (!aPlace.baseDomain.Equals(aPlace.triggeringSponsoredURLBaseDomain)) {
1244 // The base domain is not same.
1245 return NS_OK;
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);
1253 nsresult rv =
1254 URIBinder::Bind(stmt, "url"_ns, aPlace.triggeringSponsoredURL);
1255 NS_ENSURE_SUCCESS(rv, rv);
1257 mozStorageStatementScoper scoper(stmt);
1259 bool exists;
1260 rv = stmt->ExecuteStep(&exists);
1261 NS_ENSURE_SUCCESS(rv, rv);
1263 if (exists) {
1264 rv = stmt->GetInt64(0, &aPlace.triggeringPlaceId);
1265 NS_ENSURE_SUCCESS(rv, rv);
1266 } else {
1267 Telemetry::ScalarAdd(
1268 Telemetry::ScalarID::PLACES_SPONSORED_VISIT_NO_TRIGGERING_URL, 1);
1271 aPlace.source = nsINavHistoryService::VISIT_SOURCE_SPONSORED;
1273 return NS_OK;
1276 mozIStorageConnection* mDBConn;
1278 nsTArray<VisitData> mPlaces;
1280 nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
1282 bool mIgnoreErrors;
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 {
1299 public:
1301 * Sets a pages title in the database asynchronously.
1303 * @param aConnection
1304 * The database connection to use for this operation.
1305 * @param aURI
1306 * The URI to set the page title on.
1307 * @param aTitle
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!");
1315 nsCString spec;
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);
1327 return NS_OK;
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).
1335 bool exists;
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.
1342 return NS_OK;
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);
1362 } else {
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);
1376 return NS_OK;
1379 private:
1380 SetPageTitle(const nsCString& aSpec, const nsAString& aTitle)
1381 : Runnable("places::SetPageTitle"), mHistory(History::GetService()) {
1382 mPlace.spec = aSpec;
1383 mPlace.title = aTitle;
1386 VisitData mPlace;
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.
1398 * @param aPlace
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));
1414 if (!uri) {
1415 return;
1418 if (!!aCallback) {
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 ////////////////////////////////////////////////////////////////////////////////
1436 //// History
1438 History* History::gService = nullptr;
1440 History::History()
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;
1449 MOZ_RELEASE_ASSERT(
1450 dirsvc &&
1451 NS_SUCCEEDED(
1452 dirsvc->Has(NS_APP_USER_PROFILE_50_DIR, &haveProfile)) &&
1453 haveProfile,
1454 "Can't construct history service if there is no profile.");
1456 gService = this;
1458 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1459 NS_WARNING_ASSERTION(os, "Observer service was not found!");
1460 if (os) {
1461 (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
1465 History::~History() {
1466 UnregisterWeakMemoryReporter(this);
1468 MOZ_ASSERT(gService == this);
1469 gService = nullptr;
1472 void History::InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
1474 class ConcurrentStatementsHolder final : public mozIStorageCompletionCallback {
1475 public:
1476 NS_DECL_ISUPPORTS
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)) {
1486 return nullptr;
1488 return holder;
1491 NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) override {
1492 if (NS_FAILED(aStatus)) {
1493 return NS_OK;
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) {
1499 Shutdown();
1500 return NS_OK;
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);
1520 return NS_OK;
1523 void QueueVisitedStatement(RefPtr<VisitedQuery> aCallback) {
1524 if (mIsVisitedStatement) {
1525 aCallback->Execute(*mIsVisitedStatement);
1526 } else {
1527 mVisitedQueries.AppendElement(std::move(aCallback));
1531 void Shutdown() {
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;
1546 private:
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));
1572 return NS_OK;
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);
1596 } else {
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);
1619 return NS_OK;
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();
1629 if (titleIsVoid) {
1630 // Don't change the title.
1631 stmt = GetStatement(
1632 "UPDATE moz_places "
1633 "SET hidden = :hidden, "
1634 "typed = :typed, "
1635 "guid = :guid "
1636 "WHERE id = :page_id ");
1637 } else {
1638 stmt = GetStatement(
1639 "UPDATE moz_places "
1640 "SET title = :title, "
1641 "hidden = :hidden, "
1642 "typed = :typed, "
1643 "guid = :guid "
1644 "WHERE id = :page_id ");
1646 NS_ENSURE_STATE(stmt);
1647 mozStorageStatementScoper scoper(stmt);
1649 nsresult rv;
1650 if (!titleIsVoid) {
1651 // An empty string clears the title.
1652 if (aPlace.title.IsEmpty()) {
1653 rv = stmt->BindNullByName("title"_ns);
1654 } else {
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);
1671 return NS_OK;
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!");
1679 nsresult rv;
1681 // URI takes precedence.
1682 nsCOMPtr<mozIStorageStatement> stmt;
1683 bool selectByURI = !_place.spec.IsEmpty();
1684 if (selectByURI) {
1685 stmt = GetStatement(
1686 "SELECT guid, id, title, hidden, typed, frecency, visit_count, "
1687 "last_visit_date, "
1688 "(SELECT id FROM moz_historyvisits "
1689 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1690 "last_visit_id, "
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);
1698 } else {
1699 stmt = GetStatement(
1700 "SELECT url, id, title, hidden, typed, frecency, visit_count, "
1701 "last_visit_date, "
1702 "(SELECT id FROM moz_historyvisits "
1703 "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS "
1704 "last_visit_id, "
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);
1719 if (!*_exists) {
1720 return NS_OK;
1723 if (selectByURI) {
1724 if (_place.guid.IsEmpty()) {
1725 rv = stmt->GetUTF8String(0, _place.guid);
1726 NS_ENSURE_SUCCESS(rv, rv);
1728 } else {
1729 nsAutoCString spec;
1730 rv = stmt->GetUTF8String(0, spec);
1731 NS_ENSURE_SUCCESS(rv, rv);
1732 _place.spec = spec;
1735 rv = stmt->GetInt64(1, &_place.placeId);
1736 NS_ENSURE_SUCCESS(rv, rv);
1738 nsAutoString title;
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.
1750 else {
1751 _place.titleChanged = !(_place.title.Equals(title)) &&
1752 !(_place.title.IsEmpty() && title.IsVoid());
1755 int32_t hidden;
1756 rv = stmt->GetInt32(3, &hidden);
1757 NS_ENSURE_SUCCESS(rv, rv);
1758 _place.hidden = !!hidden;
1760 int32_t typed;
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);
1767 int32_t visitCount;
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);
1775 int32_t bookmarked;
1776 rv = stmt->GetInt32(9, &bookmarked);
1777 NS_ENSURE_SUCCESS(rv, rv);
1778 _place.bookmarked = bookmarked == 1;
1780 return NS_OK;
1783 MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
1785 NS_IMETHODIMP
1786 History::CollectReports(nsIHandleReportCallback* aHandleReport,
1787 nsISupports* aData, bool aAnonymize) {
1788 MOZ_COLLECT_REPORT(
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 "
1792 "of links.");
1794 return NS_OK;
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);
1803 return size;
1806 /* static */
1807 History* History::GetService() {
1808 if (gService) {
1809 return gService;
1812 nsCOMPtr<IHistory> service = components::History::Service();
1813 if (service) {
1814 NS_ASSERTION(gService, "Our constructor was not run?!");
1817 return gService;
1820 /* static */
1821 already_AddRefed<History> History::GetSingleton() {
1822 if (!gService) {
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()) {
1835 return nullptr;
1837 if (!mDB) {
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) {
1853 return nullptr;
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) {
1880 iter.Remove();
1885 ////////////////////////////////////////////////////////////////////////////////
1886 //// IHistory
1888 NS_IMETHODIMP
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()) {
1895 return NS_OK;
1898 if (XRE_IsContentProcess()) {
1899 if (!BaseHistory::CanStore(aURI)) {
1900 return NS_OK;
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);
1907 return NS_OK;
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.
1914 bool canAdd;
1915 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
1916 NS_ENSURE_SUCCESS(rv, rv);
1917 if (!canAdd) {
1918 return NS_OK;
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;
1970 if (isRedirect) {
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.
1983 if (reload) {
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.
1997 return NS_OK;
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);
2010 if (browser) {
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);
2062 } else {
2063 mozIStorageConnection* dbConn = GetDBConn();
2064 NS_ENSURE_STATE(dbConn);
2066 rv = InsertVisitedURIs::Start(dbConn, std::move(placeArray));
2067 NS_ENSURE_SUCCESS(rv, rv);
2070 return NS_OK;
2073 NS_IMETHODIMP
2074 History::SetURITitle(nsIURI* aURI, const nsAString& aTitle) {
2075 MOZ_ASSERT(NS_IsMainThread());
2076 NS_ENSURE_ARG(aURI);
2078 if (IsShuttingDown()) {
2079 return NS_OK;
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));
2086 return NS_OK;
2089 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
2091 // At first, it seems like nav history should always be available here, no
2092 // matter what.
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);
2102 bool canAdd;
2103 nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
2104 NS_ENSURE_SUCCESS(rv, rv);
2105 if (!canAdd) {
2106 return NS_OK;
2109 mozIStorageConnection* dbConn = GetDBConn();
2110 NS_ENSURE_STATE(dbConn);
2112 return SetPageTitle::Start(dbConn, aURI, aTitle);
2115 ////////////////////////////////////////////////////////////////////////////////
2116 //// mozIAsyncHistory
2118 NS_IMETHODIMP
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");
2138 nsCString guid;
2140 nsString fatGUID;
2141 GetStringFromJSObject(aCtx, info, "guid", fatGUID);
2142 if (fatGUID.IsVoid()) {
2143 guid.SetIsVoid(true);
2144 } else {
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)) {
2152 continue;
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);
2162 nsString title;
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();
2172 bool isArray;
2173 if (!JS::IsArrayObject(aCtx, visits, &isArray)) {
2174 return NS_ERROR_UNEXPECTED;
2176 if (!isArray) {
2177 return NS_ERROR_INVALID_ARG;
2181 NS_ENSURE_ARG(visits);
2183 uint32_t visitsLength = 0;
2184 if (visits) {
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()) {
2198 data.title = title;
2199 } else if (!title.IsVoid()) {
2200 // Setting data.title to an empty string wouldn't make it non-void.
2201 data.title.SetIsVoid(false);
2203 data.guid = guid;
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
2212 // a mistake.
2213 if (data.visitTime < (PR_Now() / 1000)) {
2214 #ifdef DEBUG
2215 nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
2216 Unused << xpc->DebugDumpJSStack(false, false, false);
2217 MOZ_CRASH("invalid time format passed to updatePlaces");
2218 #endif
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
2230 // database.
2231 if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
2232 NotifyEmbedVisit(data, aCallback);
2233 visitData.RemoveLastElement();
2234 initialUpdatedCount++;
2235 continue;
2238 // The referrer is optional.
2239 nsCOMPtr<nsIURI> referrer =
2240 GetURIFromJSObject(aCtx, visit, "referrerURI");
2241 if (referrer) {
2242 (void)referrer->GetSpec(data.referrerSpec);
2247 mozIStorageConnection* dbConn = GetDBConn();
2248 NS_ENSURE_STATE(dbConn);
2250 nsMainThreadPtrHandle<mozIVisitInfoCallback> callback(
2251 new nsMainThreadPtrHolder<mozIVisitInfoCallback>("mozIVisitInfoCallback",
2252 aCallback));
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);
2277 return NS_OK;
2280 NS_IMETHODIMP
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
2297 // bug 1775265.
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);
2314 } else {
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 ////////////////////////////////////////////////////////////////////////////////
2326 //// nsIObserver
2328 NS_IMETHODIMP
2329 History::Observe(nsISupports* aSubject, const char* aTopic,
2330 const char16_t* aData) {
2331 if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
2332 Shutdown();
2334 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2335 if (os) {
2336 (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
2340 return NS_OK;
2343 ////////////////////////////////////////////////////////////////////////////////
2344 //// nsISupports
2346 NS_IMPL_ISUPPORTS(History, IHistory, mozIAsyncHistory, nsIObserver,
2347 nsIMemoryReporter)
2349 } // namespace places
2350 } // namespace mozilla