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