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