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