1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Places code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * the Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2009
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Shawn Wilsher <me@shawnwilsher.com> (Original Author)
25 : * Allison Naaktgeboren <ally@mozilla.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "mozilla/dom/ContentChild.h"
42 : #include "mozilla/dom/ContentParent.h"
43 : #include "nsXULAppAPI.h"
44 :
45 : #include "History.h"
46 : #include "nsNavHistory.h"
47 : #include "nsNavBookmarks.h"
48 : #include "nsAnnotationService.h"
49 : #include "Helpers.h"
50 : #include "PlaceInfo.h"
51 : #include "VisitInfo.h"
52 :
53 : #include "mozilla/storage.h"
54 : #include "mozilla/dom/Link.h"
55 : #include "nsDocShellCID.h"
56 : #include "mozilla/Services.h"
57 : #include "nsThreadUtils.h"
58 : #include "nsNetUtil.h"
59 : #include "nsIXPConnect.h"
60 : #include "mozilla/unused.h"
61 : #include "mozilla/Util.h"
62 : #include "nsContentUtils.h"
63 : #include "nsIMemoryReporter.h"
64 :
65 : // Initial size for the cache holding visited status observers.
66 : #define VISIT_OBSERVERS_INITIAL_CACHE_SIZE 128
67 :
68 : using namespace mozilla::dom;
69 : using mozilla::unused;
70 :
71 : namespace mozilla {
72 : namespace places {
73 :
74 : ////////////////////////////////////////////////////////////////////////////////
75 : //// Global Defines
76 :
77 : #define URI_VISITED "visited"
78 : #define URI_NOT_VISITED "not visited"
79 : #define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
80 : // Observer event fired after a visit has been registered in the DB.
81 : #define URI_VISIT_SAVED "uri-visit-saved"
82 :
83 : #define DESTINATIONFILEURI_ANNO \
84 : NS_LITERAL_CSTRING("downloads/destinationFileURI")
85 : #define DESTINATIONFILENAME_ANNO \
86 : NS_LITERAL_CSTRING("downloads/destinationFileName")
87 :
88 : ////////////////////////////////////////////////////////////////////////////////
89 : //// VisitData
90 :
91 4865 : struct VisitData {
92 500 : VisitData()
93 : : placeId(0)
94 : , visitId(0)
95 : , sessionId(0)
96 : , hidden(true)
97 : , typed(false)
98 : , transitionType(PR_UINT32_MAX)
99 : , visitTime(0)
100 500 : , titleChanged(false)
101 : {
102 500 : guid.SetIsVoid(true);
103 500 : title.SetIsVoid(true);
104 500 : }
105 :
106 581 : VisitData(nsIURI* aURI,
107 : nsIURI* aReferrer = NULL)
108 : : placeId(0)
109 : , visitId(0)
110 : , sessionId(0)
111 : , hidden(true)
112 : , typed(false)
113 : , transitionType(PR_UINT32_MAX)
114 : , visitTime(0)
115 581 : , titleChanged(false)
116 : {
117 581 : (void)aURI->GetSpec(spec);
118 581 : (void)GetReversedHostname(aURI, revHost);
119 581 : if (aReferrer) {
120 12 : (void)aReferrer->GetSpec(referrerSpec);
121 : }
122 581 : guid.SetIsVoid(true);
123 581 : title.SetIsVoid(true);
124 581 : }
125 :
126 : /**
127 : * Sets the transition type of the visit, as well as if it was typed and
128 : * should be hidden (based on the transition type specified).
129 : *
130 : * @param aTransitionType
131 : * The transition type constant to set. Must be one of the
132 : * TRANSITION_ constants on nsINavHistoryService.
133 : */
134 484 : void SetTransitionType(PRUint32 aTransitionType)
135 : {
136 484 : typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
137 : bool redirected =
138 : aTransitionType == nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY ||
139 484 : aTransitionType == nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
140 484 : hidden = GetHiddenState(redirected, aTransitionType);
141 484 : transitionType = aTransitionType;
142 484 : }
143 :
144 : /**
145 : * Determines if this refers to the same url as aOther, and updates aOther
146 : * with missing information if so.
147 : *
148 : * @param aOther
149 : * The other place to check against.
150 : * @return true if this is a visit for the same place as aOther, false
151 : * otherwise.
152 : */
153 353 : bool IsSamePlaceAs(VisitData& aOther)
154 : {
155 353 : if (!spec.Equals(aOther.spec)) {
156 346 : return false;
157 : }
158 :
159 7 : aOther.placeId = placeId;
160 7 : aOther.guid = guid;
161 7 : return true;
162 : }
163 :
164 : PRInt64 placeId;
165 : nsCString guid;
166 : PRInt64 visitId;
167 : PRInt64 sessionId;
168 : nsCString spec;
169 : nsString revHost;
170 : bool hidden;
171 : bool typed;
172 : PRUint32 transitionType;
173 : PRTime visitTime;
174 :
175 : /**
176 : * Stores the title. If this is empty (IsEmpty() returns true), then the
177 : * title should be removed from the Place. If the title is void (IsVoid()
178 : * returns true), then no title has been set on this object, and titleChanged
179 : * should remain false.
180 : */
181 : nsString title;
182 :
183 : nsCString referrerSpec;
184 :
185 : // TODO bug 626836 hook up hidden and typed change tracking too!
186 : bool titleChanged;
187 : };
188 :
189 : ////////////////////////////////////////////////////////////////////////////////
190 : //// Anonymous Helpers
191 :
192 : namespace {
193 :
194 : /**
195 : * Obtains an nsIURI from the "uri" property of a JSObject.
196 : *
197 : * @param aCtx
198 : * The JSContext for aObject.
199 : * @param aObject
200 : * The JSObject to get the URI from.
201 : * @param aProperty
202 : * The name of the property to get the URI from.
203 : * @return the URI if it exists.
204 : */
205 : already_AddRefed<nsIURI>
206 997 : GetURIFromJSObject(JSContext* aCtx,
207 : JSObject* aObject,
208 : const char* aProperty)
209 : {
210 : jsval uriVal;
211 997 : JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
212 997 : NS_ENSURE_TRUE(rc, nsnull);
213 :
214 997 : if (!JSVAL_IS_PRIMITIVE(uriVal)) {
215 1100 : nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
216 :
217 1100 : nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
218 550 : nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, JSVAL_TO_OBJECT(uriVal),
219 550 : getter_AddRefs(wrappedObj));
220 550 : NS_ENSURE_SUCCESS(rv, nsnull);
221 1096 : nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
222 548 : return uri.forget();
223 : }
224 447 : return nsnull;
225 : }
226 :
227 : /**
228 : * Obtains the specified property of a JSObject.
229 : *
230 : * @param aCtx
231 : * The JSContext for aObject.
232 : * @param aObject
233 : * The JSObject to get the string from.
234 : * @param aProperty
235 : * The property to get the value from.
236 : * @param _string
237 : * The string to populate with the value, or set it to void.
238 : */
239 : void
240 1006 : GetStringFromJSObject(JSContext* aCtx,
241 : JSObject* aObject,
242 : const char* aProperty,
243 : nsString& _string)
244 : {
245 : jsval val;
246 1006 : JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
247 2012 : if (!rc || JSVAL_IS_VOID(val) ||
248 1276 : !(JSVAL_IS_NULL(val) || JSVAL_IS_STRING(val))) {
249 367 : _string.SetIsVoid(true);
250 367 : return;
251 : }
252 : // |null| in JS maps to the empty string.
253 639 : if (JSVAL_IS_NULL(val)) {
254 2 : _string.Truncate();
255 2 : return;
256 : }
257 : size_t length;
258 : const jschar* chars =
259 637 : JS_GetStringCharsZAndLength(aCtx, JSVAL_TO_STRING(val), &length);
260 637 : if (!chars) {
261 0 : _string.SetIsVoid(true);
262 0 : return;
263 : }
264 637 : _string.Assign(static_cast<const PRUnichar*>(chars), length);
265 : }
266 :
267 : /**
268 : * Obtains the specified property of a JSObject.
269 : *
270 : * @param aCtx
271 : * The JSContext for aObject.
272 : * @param aObject
273 : * The JSObject to get the int from.
274 : * @param aProperty
275 : * The property to get the value from.
276 : * @param _int
277 : * The integer to populate with the value on success.
278 : */
279 : template <typename IntType>
280 : nsresult
281 1349 : GetIntFromJSObject(JSContext* aCtx,
282 : JSObject* aObject,
283 : const char* aProperty,
284 : IntType* _int)
285 : {
286 : jsval value;
287 1349 : JSBool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
288 1349 : NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
289 1349 : if (JSVAL_IS_VOID(value)) {
290 441 : return NS_ERROR_INVALID_ARG;
291 : }
292 908 : NS_ENSURE_ARG(JSVAL_IS_PRIMITIVE(value));
293 908 : NS_ENSURE_ARG(JSVAL_IS_NUMBER(value));
294 :
295 : double num;
296 908 : rc = JS_ValueToNumber(aCtx, value, &num);
297 908 : NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
298 908 : NS_ENSURE_ARG(IntType(num) == num);
299 :
300 908 : *_int = IntType(num);
301 908 : return NS_OK;
302 : }
303 :
304 : /**
305 : * Obtains the specified property of a JSObject.
306 : *
307 : * @pre aArray must be an Array object.
308 : *
309 : * @param aCtx
310 : * The JSContext for aArray.
311 : * @param aArray
312 : * The JSObject to get the object from.
313 : * @param aIndex
314 : * The index to get the object from.
315 : * @param _object
316 : * The JSObject pointer on success.
317 : */
318 : nsresult
319 1009 : GetJSObjectFromArray(JSContext* aCtx,
320 : JSObject* aArray,
321 : uint32_t aIndex,
322 : JSObject** _rooter)
323 : {
324 1009 : NS_PRECONDITION(JS_IsArrayObject(aCtx, aArray),
325 : "Must provide an object that is an array!");
326 :
327 : jsval value;
328 1009 : JSBool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
329 1009 : NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
330 1009 : NS_ENSURE_ARG(!JSVAL_IS_PRIMITIVE(value));
331 1009 : *_rooter = JSVAL_TO_OBJECT(value);
332 1009 : return NS_OK;
333 : }
334 :
335 : class VisitedQuery : public AsyncStatementCallback
336 1384 : {
337 : public:
338 346 : static nsresult Start(nsIURI* aURI,
339 : mozIVisitedStatusCallback* aCallback=nsnull)
340 : {
341 346 : NS_PRECONDITION(aURI, "Null URI");
342 :
343 : // If we are a content process, always remote the request to the
344 : // parent process.
345 346 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
346 : mozilla::dom::ContentChild* cpc =
347 0 : mozilla::dom::ContentChild::GetSingleton();
348 0 : NS_ASSERTION(cpc, "Content Protocol is NULL!");
349 0 : (void)cpc->SendStartVisitedQuery(aURI);
350 0 : return NS_OK;
351 : }
352 :
353 346 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
354 346 : NS_ENSURE_STATE(navHistory);
355 346 : if (navHistory->hasEmbedVisit(aURI)) {
356 8 : nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback, true);
357 4 : NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
358 : // As per IHistory contract, we must notify asynchronously.
359 : nsCOMPtr<nsIRunnable> event =
360 8 : NS_NewRunnableMethod(callback, &VisitedQuery::NotifyVisitedStatus);
361 4 : NS_DispatchToMainThread(event);
362 :
363 4 : return NS_OK;
364 : }
365 :
366 342 : History* history = History::GetService();
367 342 : NS_ENSURE_STATE(history);
368 342 : mozIStorageAsyncStatement* stmt = history->GetIsVisitedStatement();
369 342 : NS_ENSURE_STATE(stmt);
370 :
371 : // Bind by index for performance.
372 342 : nsresult rv = URIBinder::Bind(stmt, 0, aURI);
373 342 : NS_ENSURE_SUCCESS(rv, rv);
374 :
375 684 : nsRefPtr<VisitedQuery> callback = new VisitedQuery(aURI, aCallback);
376 342 : NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY);
377 :
378 684 : nsCOMPtr<mozIStoragePendingStatement> handle;
379 342 : return stmt->ExecuteAsync(callback, getter_AddRefs(handle));
380 : }
381 :
382 32 : NS_IMETHOD HandleResult(mozIStorageResultSet* aResults)
383 : {
384 : // If this method is called, we've gotten results, which means we have a
385 : // visit.
386 32 : mIsVisited = true;
387 32 : return NS_OK;
388 : }
389 :
390 0 : NS_IMETHOD HandleError(mozIStorageError* aError)
391 : {
392 : // mIsVisited is already set to false, and that's the assumption we will
393 : // make if an error occurred.
394 0 : return NS_OK;
395 : }
396 :
397 342 : NS_IMETHOD HandleCompletion(PRUint16 aReason)
398 : {
399 342 : if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
400 0 : return NS_OK;
401 : }
402 :
403 342 : nsresult rv = NotifyVisitedStatus();
404 342 : NS_ENSURE_SUCCESS(rv, rv);
405 342 : return NS_OK;
406 : }
407 :
408 346 : nsresult NotifyVisitedStatus()
409 : {
410 : // If an external handling callback is provided, just notify through it.
411 346 : if (mCallback) {
412 336 : mCallback->IsVisited(mURI, mIsVisited);
413 336 : return NS_OK;
414 : }
415 :
416 10 : if (mIsVisited) {
417 4 : History* history = History::GetService();
418 4 : NS_ENSURE_STATE(history);
419 4 : history->NotifyVisited(mURI);
420 : }
421 :
422 : nsCOMPtr<nsIObserverService> observerService =
423 20 : mozilla::services::GetObserverService();
424 10 : if (observerService) {
425 20 : nsAutoString status;
426 10 : if (mIsVisited) {
427 4 : status.AssignLiteral(URI_VISITED);
428 : }
429 : else {
430 6 : status.AssignLiteral(URI_NOT_VISITED);
431 : }
432 10 : (void)observerService->NotifyObservers(mURI,
433 : URI_VISITED_RESOLUTION_TOPIC,
434 10 : status.get());
435 : }
436 :
437 10 : return NS_OK;
438 : }
439 :
440 : private:
441 346 : VisitedQuery(nsIURI* aURI,
442 : mozIVisitedStatusCallback *aCallback=nsnull,
443 : bool aIsVisited=false)
444 : : mURI(aURI)
445 : , mCallback(aCallback)
446 346 : , mIsVisited(aIsVisited)
447 : {
448 346 : }
449 :
450 : nsCOMPtr<nsIURI> mURI;
451 : nsCOMPtr<mozIVisitedStatusCallback> mCallback;
452 : bool mIsVisited;
453 : };
454 :
455 : /**
456 : * Notifies observers about a visit.
457 : */
458 : class NotifyVisitObservers : public nsRunnable
459 1932 : {
460 : public:
461 483 : NotifyVisitObservers(VisitData& aPlace,
462 : VisitData& aReferrer)
463 : : mPlace(aPlace)
464 : , mReferrer(aReferrer)
465 483 : , mHistory(History::GetService())
466 : {
467 483 : }
468 :
469 483 : NS_IMETHOD Run()
470 : {
471 483 : NS_PRECONDITION(NS_IsMainThread(),
472 : "This should be called on the main thread");
473 : // We are in the main thread, no need to lock.
474 483 : if (mHistory->IsShuttingDown()) {
475 : // If we are shutting down, we cannot notify the observers.
476 0 : return NS_OK;
477 : }
478 :
479 483 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
480 483 : if (!navHistory) {
481 0 : NS_WARNING("Trying to notify about a visit but cannot get the history service!");
482 0 : return NS_OK;
483 : }
484 :
485 966 : nsCOMPtr<nsIURI> uri;
486 483 : (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
487 :
488 : // Notify nsNavHistory observers of visit, but only for certain types of
489 : // visits to maintain consistency with nsNavHistory::GetQueryResults.
490 483 : if (!mPlace.hidden &&
491 : mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED &&
492 : mPlace.transitionType != nsINavHistoryService::TRANSITION_FRAMED_LINK) {
493 : navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
494 : mPlace.sessionId, mReferrer.visitId,
495 455 : mPlace.transitionType, mPlace.guid);
496 : }
497 :
498 : nsCOMPtr<nsIObserverService> obsService =
499 966 : mozilla::services::GetObserverService();
500 483 : if (obsService) {
501 : DebugOnly<nsresult> rv =
502 966 : obsService->NotifyObservers(uri, URI_VISIT_SAVED, nsnull);
503 483 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Could not notify observers");
504 : }
505 :
506 483 : History* history = History::GetService();
507 483 : NS_ENSURE_STATE(history);
508 483 : history->NotifyVisited(uri);
509 :
510 483 : return NS_OK;
511 : }
512 : private:
513 : VisitData mPlace;
514 : VisitData mReferrer;
515 : nsRefPtr<History> mHistory;
516 : };
517 :
518 : /**
519 : * Notifies observers about a pages title changing.
520 : */
521 : class NotifyTitleObservers : public nsRunnable
522 1600 : {
523 : public:
524 : /**
525 : * Notifies observers on the main thread.
526 : *
527 : * @param aSpec
528 : * The spec of the URI to notify about.
529 : * @param aTitle
530 : * The new title to notify about.
531 : */
532 400 : NotifyTitleObservers(const nsCString& aSpec,
533 : const nsString& aTitle,
534 : const nsCString& aGUID)
535 : : mSpec(aSpec)
536 : , mTitle(aTitle)
537 400 : , mGUID(aGUID)
538 : {
539 400 : }
540 :
541 400 : NS_IMETHOD Run()
542 : {
543 400 : NS_PRECONDITION(NS_IsMainThread(),
544 : "This should be called on the main thread");
545 :
546 400 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
547 400 : NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
548 800 : nsCOMPtr<nsIURI> uri;
549 400 : (void)NS_NewURI(getter_AddRefs(uri), mSpec);
550 400 : navHistory->NotifyTitleChange(uri, mTitle, mGUID);
551 :
552 400 : return NS_OK;
553 : }
554 : private:
555 : const nsCString mSpec;
556 : const nsString mTitle;
557 : const nsCString mGUID;
558 : };
559 :
560 : /**
561 : * Notifies a callback object when a visit has been handled.
562 : */
563 : class NotifyVisitInfoCallback : public nsRunnable
564 1752 : {
565 : public:
566 438 : NotifyVisitInfoCallback(mozIVisitInfoCallback* aCallback,
567 : const VisitData& aPlace,
568 : nsresult aResult)
569 : : mCallback(aCallback)
570 : , mPlace(aPlace)
571 438 : , mResult(aResult)
572 : {
573 438 : NS_PRECONDITION(aCallback, "Must pass a non-null callback!");
574 438 : }
575 :
576 438 : NS_IMETHOD Run()
577 : {
578 438 : NS_PRECONDITION(NS_IsMainThread(),
579 : "This should be called on the main thread");
580 :
581 876 : nsCOMPtr<nsIURI> referrerURI;
582 438 : if (!mPlace.referrerSpec.IsEmpty()) {
583 2 : (void)NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec);
584 : }
585 :
586 : nsCOMPtr<mozIVisitInfo> visit =
587 : new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
588 1314 : referrerURI.forget(), mPlace.sessionId);
589 876 : PlaceInfo::VisitsArray visits;
590 438 : (void)visits.AppendElement(visit);
591 :
592 876 : nsCOMPtr<nsIURI> uri;
593 438 : (void)NS_NewURI(getter_AddRefs(uri), mPlace.spec);
594 :
595 : // We do not notify about the frecency of the place.
596 : nsCOMPtr<mozIPlaceInfo> place =
597 : new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
598 1314 : -1, visits);
599 438 : if (NS_SUCCEEDED(mResult)) {
600 344 : (void)mCallback->HandleResult(place);
601 : }
602 : else {
603 94 : (void)mCallback->HandleError(mResult, place);
604 : }
605 :
606 438 : return NS_OK;
607 : }
608 :
609 : private:
610 : /**
611 : * Callers MUST hold a strong reference to this that outlives us because we
612 : * may be created off of the main thread, and therefore cannot call AddRef on
613 : * this object (and therefore cannot hold a strong reference to it).
614 : */
615 : mozIVisitInfoCallback* mCallback;
616 : VisitData mPlace;
617 : const nsresult mResult;
618 : };
619 :
620 : /**
621 : * Notifies a callback object when the operation is complete.
622 : */
623 : class NotifyCompletion : public nsRunnable
624 616 : {
625 : public:
626 154 : NotifyCompletion(mozIVisitInfoCallback* aCallback)
627 154 : : mCallback(aCallback)
628 : {
629 154 : NS_PRECONDITION(aCallback, "Must pass a non-null callback!");
630 154 : }
631 :
632 308 : NS_IMETHOD Run()
633 : {
634 308 : if (NS_IsMainThread()) {
635 154 : (void)mCallback->HandleCompletion();
636 : }
637 : else {
638 154 : (void)NS_DispatchToMainThread(this);
639 :
640 : // Also dispatch an event to release the reference to the callback after
641 : // we have run.
642 308 : nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
643 154 : (void)NS_ProxyRelease(mainThread, mCallback, true);
644 : }
645 308 : return NS_OK;
646 : }
647 :
648 : private:
649 : /**
650 : * Callers MUST hold a strong reference to this because we may be created
651 : * off of the main thread, and therefore cannot call AddRef on this object
652 : * (and therefore cannot hold a strong reference to it). If invoked from a
653 : * background thread, NotifyCompletion will release the reference to this.
654 : */
655 : mozIVisitInfoCallback* mCallback;
656 : };
657 :
658 : /**
659 : * Checks to see if we can add aURI to history, and dispatches an error to
660 : * aCallback (if provided) if we cannot.
661 : *
662 : * @param aURI
663 : * The URI to check.
664 : * @param [optional] aGUID
665 : * The guid of the URI to check. This is passed back to the callback.
666 : * @param [optional] aCallback
667 : * The callback to notify if the URI cannot be added to history.
668 : * @return true if the URI can be added to history, false otherwise.
669 : */
670 : bool
671 1018 : CanAddURI(nsIURI* aURI,
672 474 : const nsCString& aGUID = EmptyCString(),
673 : mozIVisitInfoCallback* aCallback = NULL)
674 : {
675 1018 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
676 1018 : NS_ENSURE_TRUE(navHistory, false);
677 :
678 : bool canAdd;
679 1018 : nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
680 1018 : if (NS_SUCCEEDED(rv) && canAdd) {
681 925 : return true;
682 : };
683 :
684 : // We cannot add the URI. Notify the callback, if we were given one.
685 93 : if (aCallback) {
686 : // NotifyVisitInfoCallback does not hold a strong reference to the callback, so we
687 : // have to manage it by AddRefing now and then releasing it after the event
688 : // has run.
689 93 : NS_ADDREF(aCallback);
690 :
691 186 : VisitData place(aURI);
692 93 : place.guid = aGUID;
693 : nsCOMPtr<nsIRunnable> event =
694 186 : new NotifyVisitInfoCallback(aCallback, place, NS_ERROR_INVALID_ARG);
695 93 : (void)NS_DispatchToMainThread(event);
696 :
697 : // Also dispatch an event to release our reference to the callback after
698 : // NotifyVisitInfoCallback has run.
699 186 : nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
700 93 : (void)NS_ProxyRelease(mainThread, aCallback, true);
701 : }
702 :
703 93 : return false;
704 : }
705 :
706 : /**
707 : * Adds a visit to the database.
708 : */
709 : class InsertVisitedURIs : public nsRunnable
710 : {
711 : public:
712 : /**
713 : * Adds a visit to the database asynchronously.
714 : *
715 : * @param aConnection
716 : * The database connection to use for these operations.
717 : * @param aPlaces
718 : * The locations to record visits.
719 : * @param [optional] aCallback
720 : * The callback to notify about the visit.
721 : */
722 121 : static nsresult Start(mozIStorageConnection* aConnection,
723 : nsTArray<VisitData>& aPlaces,
724 : mozIVisitInfoCallback* aCallback = NULL)
725 : {
726 121 : NS_PRECONDITION(NS_IsMainThread(),
727 : "This should be called on the main thread");
728 121 : NS_PRECONDITION(aPlaces.Length() > 0, "Must pass a non-empty array!");
729 :
730 : nsRefPtr<InsertVisitedURIs> event =
731 242 : new InsertVisitedURIs(aConnection, aPlaces, aCallback);
732 :
733 : // Get the target thread, and then start the work!
734 242 : nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
735 121 : NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
736 121 : nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
737 121 : NS_ENSURE_SUCCESS(rv, rv);
738 :
739 121 : return NS_OK;
740 : }
741 :
742 121 : NS_IMETHOD Run()
743 : {
744 121 : NS_PRECONDITION(!NS_IsMainThread(),
745 : "This should not be called on the main thread");
746 :
747 : // Prevent the main thread from shutting down while this is running.
748 242 : MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
749 121 : if(mHistory->IsShuttingDown()) {
750 : // If we were already shutting down, we cannot insert the URIs.
751 0 : return NS_OK;
752 : }
753 :
754 : mozStorageTransaction transaction(mDBConn, false,
755 242 : mozIStorageConnection::TRANSACTION_IMMEDIATE);
756 :
757 121 : VisitData* lastPlace = NULL;
758 594 : for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
759 474 : VisitData& place = mPlaces.ElementAt(i);
760 474 : VisitData& referrer = mReferrers.ElementAt(i);
761 :
762 : // We can avoid a database lookup if it's the same place as the last
763 : // visit we added.
764 353 : bool known = (lastPlace && lastPlace->IsSamePlaceAs(place)) ||
765 827 : mHistory->FetchPageInfo(place);
766 :
767 474 : FetchReferrerInfo(referrer, place);
768 :
769 474 : nsresult rv = DoDatabaseInserts(known, place, referrer);
770 474 : if (mCallback) {
771 : nsCOMPtr<nsIRunnable> event =
772 674 : new NotifyVisitInfoCallback(mCallback, place, rv);
773 337 : nsresult rv2 = NS_DispatchToMainThread(event);
774 337 : NS_ENSURE_SUCCESS(rv2, rv2);
775 : }
776 474 : NS_ENSURE_SUCCESS(rv, rv);
777 :
778 946 : nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place, referrer);
779 473 : rv = NS_DispatchToMainThread(event);
780 473 : NS_ENSURE_SUCCESS(rv, rv);
781 :
782 : // Notify about title change if needed.
783 473 : if ((!known && !place.title.IsVoid()) || place.titleChanged) {
784 384 : event = new NotifyTitleObservers(place.spec, place.title, place.guid);
785 384 : rv = NS_DispatchToMainThread(event);
786 384 : NS_ENSURE_SUCCESS(rv, rv);
787 : }
788 :
789 946 : lastPlace = &mPlaces.ElementAt(i);
790 : }
791 :
792 120 : nsresult rv = transaction.Commit();
793 120 : NS_ENSURE_SUCCESS(rv, rv);
794 :
795 120 : return NS_OK;
796 : }
797 : private:
798 121 : InsertVisitedURIs(mozIStorageConnection* aConnection,
799 : nsTArray<VisitData>& aPlaces,
800 : mozIVisitInfoCallback* aCallback)
801 : : mDBConn(aConnection)
802 : , mCallback(aCallback)
803 121 : , mHistory(History::GetService())
804 : {
805 121 : NS_PRECONDITION(NS_IsMainThread(),
806 : "This should be called on the main thread");
807 :
808 121 : (void)mPlaces.SwapElements(aPlaces);
809 121 : (void)mReferrers.SetLength(mPlaces.Length());
810 :
811 121 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
812 121 : NS_ABORT_IF_FALSE(navHistory, "Could not get nsNavHistory?!");
813 :
814 595 : for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
815 474 : mReferrers[i].spec = mPlaces[i].referrerSpec;
816 :
817 : // If we are inserting a place into an empty mPlaces array, we need to
818 : // check to make sure we do not store a bogus session id that is higher
819 : // than the current maximum session id.
820 474 : if (i == 0) {
821 121 : PRInt64 newSessionId = navHistory->GetNewSessionID();
822 121 : if (mPlaces[0].sessionId > newSessionId) {
823 1 : mPlaces[0].sessionId = newSessionId;
824 : }
825 : }
826 :
827 : // Speculatively get a new session id for our visit if the current session
828 : // id is non-valid or if it is larger than the current largest session id.
829 : // While it is true that we will use the session id from the referrer if
830 : // the visit was "recent" enough, we cannot call this method off of the
831 : // main thread, so we have to consume an id now.
832 474 : if (mPlaces[i].sessionId <= 0 ||
833 0 : (i > 0 && mPlaces[i].sessionId >= mPlaces[0].sessionId)) {
834 472 : mPlaces[i].sessionId = navHistory->GetNewSessionID();
835 : }
836 :
837 : #ifdef DEBUG
838 948 : nsCOMPtr<nsIURI> uri;
839 474 : (void)NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec);
840 474 : NS_ASSERTION(CanAddURI(uri),
841 : "Passed a VisitData with a URI we cannot add to history!");
842 : #endif
843 : }
844 :
845 : // We AddRef on the main thread, and release it when we are destroyed.
846 121 : NS_IF_ADDREF(mCallback);
847 121 : }
848 :
849 242 : virtual ~InsertVisitedURIs()
850 242 : {
851 121 : if (mCallback) {
852 172 : nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
853 86 : (void)NS_ProxyRelease(mainThread, mCallback, true);
854 : }
855 484 : }
856 :
857 : /**
858 : * Inserts or updates the entry in moz_places for this visit, adds the visit,
859 : * and updates the frecency of the place.
860 : *
861 : * @param aKnown
862 : * True if we already have an entry for this place in moz_places, false
863 : * otherwise.
864 : * @param aPlace
865 : * The place we are adding a visit for.
866 : * @param aReferrer
867 : * The referrer for aPlace.
868 : */
869 474 : nsresult DoDatabaseInserts(bool aKnown,
870 : VisitData& aPlace,
871 : VisitData& aReferrer)
872 : {
873 474 : NS_PRECONDITION(!NS_IsMainThread(),
874 : "This should not be called on the main thread");
875 :
876 : // If the page was in moz_places, we need to update the entry.
877 : nsresult rv;
878 474 : if (aKnown) {
879 18 : rv = mHistory->UpdatePlace(aPlace);
880 18 : NS_ENSURE_SUCCESS(rv, rv);
881 : }
882 : // Otherwise, the page was not in moz_places, so now we have to add it.
883 : else {
884 456 : rv = mHistory->InsertPlace(aPlace);
885 456 : NS_ENSURE_SUCCESS(rv, rv);
886 :
887 : // We need the place id and guid of the page we just inserted when we
888 : // have a callback or when the GUID isn't known. No point in doing the
889 : // disk I/O if we do not need it.
890 455 : if (mCallback || aPlace.guid.IsEmpty()) {
891 454 : bool exists = mHistory->FetchPageInfo(aPlace);
892 454 : if (!exists) {
893 0 : NS_NOTREACHED("should have an entry in moz_places");
894 : }
895 : }
896 : }
897 :
898 473 : rv = AddVisit(aPlace, aReferrer);
899 473 : NS_ENSURE_SUCCESS(rv, rv);
900 :
901 : // TODO (bug 623969) we shouldn't update this after each visit, but
902 : // rather only for each unique place to save disk I/O.
903 473 : rv = UpdateFrecency(aPlace);
904 473 : NS_ENSURE_SUCCESS(rv, rv);
905 :
906 473 : return NS_OK;
907 : }
908 :
909 : /**
910 : * Loads visit information about the page into _place.
911 : *
912 : * @param _place
913 : * The VisitData for the place we need to know visit information about.
914 : * @param [optional] aThresholdStart
915 : * The timestamp of a new visit (not represented by _place) used to
916 : * determine if the page was recently visited or not.
917 : * @return true if the page was recently (determined with aThresholdStart)
918 : * visited, false otherwise.
919 : */
920 487 : bool FetchVisitInfo(VisitData& _place,
921 : PRTime aThresholdStart = 0)
922 : {
923 487 : NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
924 :
925 974 : nsCOMPtr<mozIStorageStatement> stmt;
926 : // If we have a visitTime, we want information on that specific visit.
927 487 : if (_place.visitTime) {
928 : stmt = mHistory->GetStatement(
929 : "SELECT id, session, visit_date "
930 : "FROM moz_historyvisits "
931 : "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
932 : "AND visit_date = :visit_date "
933 473 : );
934 473 : NS_ENSURE_TRUE(stmt, false);
935 :
936 946 : mozStorageStatementScoper scoper(stmt);
937 946 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
938 473 : _place.visitTime);
939 473 : NS_ENSURE_SUCCESS(rv, rv);
940 :
941 946 : scoper.Abandon();
942 : }
943 : // Otherwise, we want information about the most recent visit.
944 : else {
945 : stmt = mHistory->GetStatement(
946 : "SELECT id, session, visit_date "
947 : "FROM moz_historyvisits "
948 : "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
949 : "ORDER BY visit_date DESC "
950 14 : );
951 14 : NS_ENSURE_TRUE(stmt, false);
952 : }
953 974 : mozStorageStatementScoper scoper(stmt);
954 :
955 487 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
956 487 : _place.spec);
957 487 : NS_ENSURE_SUCCESS(rv, false);
958 :
959 : bool hasResult;
960 487 : rv = stmt->ExecuteStep(&hasResult);
961 487 : NS_ENSURE_SUCCESS(rv, false);
962 487 : if (!hasResult) {
963 10 : return false;
964 : }
965 :
966 477 : rv = stmt->GetInt64(0, &_place.visitId);
967 477 : NS_ENSURE_SUCCESS(rv, false);
968 477 : rv = stmt->GetInt64(1, &_place.sessionId);
969 477 : NS_ENSURE_SUCCESS(rv, false);
970 477 : rv = stmt->GetInt64(2, &_place.visitTime);
971 477 : NS_ENSURE_SUCCESS(rv, false);
972 :
973 : // If we have been given a visit threshold start time, go ahead and
974 : // calculate if we have been recently visited.
975 477 : if (aThresholdStart &&
976 : aThresholdStart - _place.visitTime <= RECENT_EVENT_THRESHOLD) {
977 3 : return true;
978 : }
979 :
980 474 : return false;
981 : }
982 :
983 : /**
984 : * Fetches information about a referrer and sets the session id for aPlace if
985 : * it was a recent visit or not.
986 : *
987 : * @param aReferrer
988 : * The VisitData for the referrer. This will be populated with
989 : * FetchVisitInfo.
990 : * @param aPlace
991 : * The VisitData for the visit we will eventually add.
992 : *
993 : */
994 474 : void FetchReferrerInfo(VisitData& aReferrer,
995 : VisitData& aPlace)
996 : {
997 474 : if (aReferrer.spec.IsEmpty()) {
998 460 : return;
999 : }
1000 :
1001 : // If we had a referrer, we want to know about its last visit to put this
1002 : // new visit into the same session.
1003 14 : bool recentVisit = FetchVisitInfo(aReferrer, aPlace.visitTime);
1004 : // At this point, we know the referrer's session id, which this new visit
1005 : // should also share.
1006 14 : if (recentVisit) {
1007 3 : aPlace.sessionId = aReferrer.sessionId;
1008 : }
1009 : // However, if it isn't recent enough, we don't care to log anything about
1010 : // the referrer and we'll start a new session.
1011 : else {
1012 : // We must change both the place and referrer to indicate that we will
1013 : // not be using the referrer's data. This behavior has test coverage, so
1014 : // if this invariant changes, we'll know.
1015 11 : aPlace.referrerSpec.Truncate();
1016 11 : aReferrer.visitId = 0;
1017 : }
1018 : }
1019 :
1020 : /**
1021 : * Adds a visit for _place and updates it with the right visit id.
1022 : *
1023 : * @param _place
1024 : * The VisitData for the place we need to know visit information about.
1025 : * @param aReferrer
1026 : * A reference to the referrer's visit data.
1027 : */
1028 473 : nsresult AddVisit(VisitData& _place,
1029 : const VisitData& aReferrer)
1030 : {
1031 : nsresult rv;
1032 946 : nsCOMPtr<mozIStorageStatement> stmt;
1033 473 : if (_place.placeId) {
1034 : stmt = mHistory->GetStatement(
1035 : "INSERT INTO moz_historyvisits "
1036 : "(from_visit, place_id, visit_date, visit_type, session) "
1037 : "VALUES (:from_visit, :page_id, :visit_date, :visit_type, :session) "
1038 472 : );
1039 472 : NS_ENSURE_STATE(stmt);
1040 472 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
1041 472 : NS_ENSURE_SUCCESS(rv, rv);
1042 : }
1043 : else {
1044 : stmt = mHistory->GetStatement(
1045 : "INSERT INTO moz_historyvisits "
1046 : "(from_visit, place_id, visit_date, visit_type, session) "
1047 : "VALUES (:from_visit, (SELECT id FROM moz_places WHERE url = :page_url), :visit_date, :visit_type, :session) "
1048 1 : );
1049 1 : NS_ENSURE_STATE(stmt);
1050 1 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
1051 1 : NS_ENSURE_SUCCESS(rv, rv);
1052 : }
1053 946 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
1054 473 : aReferrer.visitId);
1055 473 : NS_ENSURE_SUCCESS(rv, rv);
1056 946 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
1057 473 : _place.visitTime);
1058 473 : NS_ENSURE_SUCCESS(rv, rv);
1059 473 : PRInt32 transitionType = _place.transitionType;
1060 473 : NS_ASSERTION(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
1061 : transitionType <= nsINavHistoryService::TRANSITION_FRAMED_LINK,
1062 : "Invalid transition type!");
1063 946 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
1064 473 : transitionType);
1065 473 : NS_ENSURE_SUCCESS(rv, rv);
1066 946 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("session"),
1067 473 : _place.sessionId);
1068 473 : NS_ENSURE_SUCCESS(rv, rv);
1069 :
1070 946 : mozStorageStatementScoper scoper(stmt);
1071 473 : rv = stmt->Execute();
1072 473 : NS_ENSURE_SUCCESS(rv, rv);
1073 :
1074 : // Now that it should be in the database, we need to obtain the id of the
1075 : // visit we just added.
1076 473 : (void)FetchVisitInfo(_place);
1077 :
1078 473 : return NS_OK;
1079 : }
1080 :
1081 : /**
1082 : * Updates the frecency, and possibly the hidden-ness of aPlace.
1083 : *
1084 : * @param aPlace
1085 : * The VisitData for the place we want to update.
1086 : */
1087 473 : nsresult UpdateFrecency(const VisitData& aPlace)
1088 : {
1089 : nsresult rv;
1090 : { // First, set our frecency to the proper value.
1091 946 : nsCOMPtr<mozIStorageStatement> stmt;
1092 473 : if (aPlace.placeId) {
1093 : stmt = mHistory->GetStatement(
1094 : "UPDATE moz_places "
1095 : "SET frecency = CALCULATE_FRECENCY(:page_id) "
1096 : "WHERE id = :page_id"
1097 472 : );
1098 472 : NS_ENSURE_STATE(stmt);
1099 472 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1100 472 : NS_ENSURE_SUCCESS(rv, rv);
1101 : }
1102 : else {
1103 : stmt = mHistory->GetStatement(
1104 : "UPDATE moz_places "
1105 : "SET frecency = CALCULATE_FRECENCY(id) "
1106 : "WHERE url = :page_url"
1107 1 : );
1108 1 : NS_ENSURE_STATE(stmt);
1109 1 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
1110 1 : NS_ENSURE_SUCCESS(rv, rv);
1111 : }
1112 946 : mozStorageStatementScoper scoper(stmt);
1113 :
1114 473 : rv = stmt->Execute();
1115 473 : NS_ENSURE_SUCCESS(rv, rv);
1116 : }
1117 :
1118 : { // Now, we need to mark the page as not hidden if the frecency is now
1119 : // nonzero.
1120 946 : nsCOMPtr<mozIStorageStatement> stmt;
1121 473 : if (aPlace.placeId) {
1122 : stmt = mHistory->GetStatement(
1123 : "UPDATE moz_places "
1124 : "SET hidden = 0 "
1125 : "WHERE id = :page_id AND frecency <> 0"
1126 472 : );
1127 472 : NS_ENSURE_STATE(stmt);
1128 472 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
1129 472 : NS_ENSURE_SUCCESS(rv, rv);
1130 : }
1131 : else {
1132 : stmt = mHistory->GetStatement(
1133 : "UPDATE moz_places "
1134 : "SET hidden = 0 "
1135 : "WHERE url = :page_url AND frecency <> 0"
1136 1 : );
1137 1 : NS_ENSURE_STATE(stmt);
1138 1 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aPlace.spec);
1139 1 : NS_ENSURE_SUCCESS(rv, rv);
1140 : }
1141 :
1142 946 : mozStorageStatementScoper scoper(stmt);
1143 473 : rv = stmt->Execute();
1144 473 : NS_ENSURE_SUCCESS(rv, rv);
1145 : }
1146 :
1147 473 : return NS_OK;
1148 : }
1149 :
1150 : mozIStorageConnection* mDBConn;
1151 :
1152 : nsTArray<VisitData> mPlaces;
1153 : nsTArray<VisitData> mReferrers;
1154 :
1155 : /**
1156 : * We own a strong reference to this, but in an indirect way. We call AddRef
1157 : * in our constructor, which happens on the main thread, and proxy the relase
1158 : * of the object to the main thread in our destructor.
1159 : */
1160 : mozIVisitInfoCallback* mCallback;
1161 :
1162 : /**
1163 : * Strong reference to the History object because we do not want it to
1164 : * disappear out from under us.
1165 : */
1166 : nsRefPtr<History> mHistory;
1167 : };
1168 :
1169 : /**
1170 : * Sets the page title for a page in moz_places (if necessary).
1171 : */
1172 : class SetPageTitle : public nsRunnable
1173 64 : {
1174 : public:
1175 : /**
1176 : * Sets a pages title in the database asynchronously.
1177 : *
1178 : * @param aConnection
1179 : * The database connection to use for this operation.
1180 : * @param aURI
1181 : * The URI to set the page title on.
1182 : * @param aTitle
1183 : * The title to set for the page, if the page exists.
1184 : */
1185 16 : static nsresult Start(mozIStorageConnection* aConnection,
1186 : nsIURI* aURI,
1187 : const nsAString& aTitle)
1188 : {
1189 16 : NS_PRECONDITION(NS_IsMainThread(),
1190 : "This should be called on the main thread");
1191 16 : NS_PRECONDITION(aURI, "Must pass a non-null URI object!");
1192 :
1193 32 : nsCString spec;
1194 16 : nsresult rv = aURI->GetSpec(spec);
1195 16 : NS_ENSURE_SUCCESS(rv, rv);
1196 :
1197 32 : nsRefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
1198 :
1199 : // Get the target thread, and then start the work!
1200 32 : nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
1201 16 : NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
1202 16 : rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
1203 16 : NS_ENSURE_SUCCESS(rv, rv);
1204 :
1205 16 : return NS_OK;
1206 : }
1207 :
1208 16 : NS_IMETHOD Run()
1209 : {
1210 16 : NS_PRECONDITION(!NS_IsMainThread(),
1211 : "This should not be called on the main thread");
1212 :
1213 : // First, see if the page exists in the database (we'll need its id later).
1214 16 : bool exists = mHistory->FetchPageInfo(mPlace);
1215 16 : if (!exists || !mPlace.titleChanged) {
1216 : // We have no record of this page, or we have no title change, so there
1217 : // is no need to do any further work.
1218 0 : return NS_OK;
1219 : }
1220 :
1221 16 : NS_ASSERTION(mPlace.placeId > 0,
1222 : "We somehow have an invalid place id here!");
1223 :
1224 : // Now we can update our database record.
1225 : nsCOMPtr<mozIStorageStatement> stmt =
1226 : mHistory->GetStatement(
1227 : "UPDATE moz_places "
1228 : "SET title = :page_title "
1229 : "WHERE id = :page_id "
1230 32 : );
1231 16 : NS_ENSURE_STATE(stmt);
1232 :
1233 : {
1234 32 : mozStorageStatementScoper scoper(stmt);
1235 32 : nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
1236 16 : mPlace.placeId);
1237 16 : NS_ENSURE_SUCCESS(rv, rv);
1238 : // Empty strings should clear the title, just like
1239 : // nsNavHistory::SetPageTitle.
1240 16 : if (mPlace.title.IsEmpty()) {
1241 0 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
1242 : }
1243 : else {
1244 32 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
1245 32 : StringHead(mPlace.title, TITLE_LENGTH_MAX));
1246 : }
1247 16 : NS_ENSURE_SUCCESS(rv, rv);
1248 16 : rv = stmt->Execute();
1249 16 : NS_ENSURE_SUCCESS(rv, rv);
1250 : }
1251 :
1252 : nsCOMPtr<nsIRunnable> event =
1253 32 : new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
1254 16 : nsresult rv = NS_DispatchToMainThread(event);
1255 16 : NS_ENSURE_SUCCESS(rv, rv);
1256 :
1257 16 : return NS_OK;
1258 : }
1259 :
1260 : private:
1261 16 : SetPageTitle(const nsCString& aSpec,
1262 : const nsAString& aTitle)
1263 16 : : mHistory(History::GetService())
1264 : {
1265 16 : mPlace.spec = aSpec;
1266 16 : mPlace.title = aTitle;
1267 16 : }
1268 :
1269 : VisitData mPlace;
1270 :
1271 : /**
1272 : * Strong reference to the History object because we do not want it to
1273 : * disappear out from under us.
1274 : */
1275 : nsRefPtr<History> mHistory;
1276 : };
1277 :
1278 : /**
1279 : * Adds download-specific annotations to a download page.
1280 : */
1281 : class SetDownloadAnnotations : public mozIVisitInfoCallback
1282 18 : {
1283 : public:
1284 : NS_DECL_ISUPPORTS
1285 :
1286 18 : SetDownloadAnnotations(nsIURI* aDestination)
1287 : : mDestination(aDestination)
1288 18 : , mHistory(History::GetService())
1289 : {
1290 18 : MOZ_ASSERT(mDestination);
1291 18 : MOZ_ASSERT(NS_IsMainThread());
1292 18 : }
1293 :
1294 0 : NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo)
1295 : {
1296 : // Just don't add the annotations in case the visit isn't added.
1297 0 : return NS_OK;
1298 : }
1299 :
1300 18 : NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo)
1301 : {
1302 : // Exit silently if the download destination is not a local file.
1303 36 : nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination);
1304 18 : if (!destinationFileURL) {
1305 1 : return NS_OK;
1306 : }
1307 :
1308 34 : nsCOMPtr<nsIURI> source;
1309 17 : nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source));
1310 17 : NS_ENSURE_SUCCESS(rv, rv);
1311 :
1312 34 : nsCOMPtr<nsIFile> destinationFile;
1313 17 : rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile));
1314 17 : NS_ENSURE_SUCCESS(rv, rv);
1315 :
1316 34 : nsAutoString destinationFileName;
1317 17 : rv = destinationFile->GetLeafName(destinationFileName);
1318 17 : NS_ENSURE_SUCCESS(rv, rv);
1319 :
1320 34 : nsCAutoString destinationURISpec;
1321 17 : rv = destinationFileURL->GetSpec(destinationURISpec);
1322 17 : NS_ENSURE_SUCCESS(rv, rv);
1323 :
1324 : // Use annotations for storing the additional download metadata.
1325 17 : nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
1326 17 : NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
1327 :
1328 : rv = annosvc->SetPageAnnotationString(
1329 : source,
1330 17 : DESTINATIONFILEURI_ANNO,
1331 17 : NS_ConvertUTF8toUTF16(destinationURISpec),
1332 : 0,
1333 : nsIAnnotationService::EXPIRE_WITH_HISTORY
1334 17 : );
1335 17 : NS_ENSURE_SUCCESS(rv, rv);
1336 :
1337 : rv = annosvc->SetPageAnnotationString(
1338 : source,
1339 17 : DESTINATIONFILENAME_ANNO,
1340 : destinationFileName,
1341 : 0,
1342 : nsIAnnotationService::EXPIRE_WITH_HISTORY
1343 17 : );
1344 17 : NS_ENSURE_SUCCESS(rv, rv);
1345 :
1346 34 : nsAutoString title;
1347 17 : rv = aPlaceInfo->GetTitle(title);
1348 17 : NS_ENSURE_SUCCESS(rv, rv);
1349 :
1350 : // In case we are downloading a file that does not correspond to a web
1351 : // page for which the title is present, we populate the otherwise empty
1352 : // history title with the name of the destination file, to allow it to be
1353 : // visible and searchable in history results.
1354 17 : if (title.IsEmpty()) {
1355 17 : rv = mHistory->SetURITitle(source, destinationFileName);
1356 17 : NS_ENSURE_SUCCESS(rv, rv);
1357 : }
1358 :
1359 17 : return NS_OK;
1360 : }
1361 :
1362 0 : NS_IMETHOD HandleCompletion()
1363 : {
1364 0 : return NS_OK;
1365 : }
1366 :
1367 : private:
1368 : nsCOMPtr<nsIURI> mDestination;
1369 :
1370 : /**
1371 : * Strong reference to the History object because we do not want it to
1372 : * disappear out from under us.
1373 : */
1374 : nsRefPtr<History> mHistory;
1375 : };
1376 144 : NS_IMPL_ISUPPORTS1(
1377 : SetDownloadAnnotations,
1378 : mozIVisitInfoCallback
1379 : )
1380 :
1381 : /**
1382 : * Stores an embed visit, and notifies observers.
1383 : *
1384 : * @param aPlace
1385 : * The VisitData of the visit to store as an embed visit.
1386 : * @param [optional] aCallback
1387 : * The mozIVisitInfoCallback to notify, if provided.
1388 : */
1389 : void
1390 10 : StoreAndNotifyEmbedVisit(VisitData& aPlace,
1391 : mozIVisitInfoCallback* aCallback = NULL)
1392 : {
1393 10 : NS_PRECONDITION(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
1394 : "Must only pass TRANSITION_EMBED visits to this!");
1395 10 : NS_PRECONDITION(NS_IsMainThread(), "Must be called on the main thread!");
1396 :
1397 20 : nsCOMPtr<nsIURI> uri;
1398 10 : (void)NS_NewURI(getter_AddRefs(uri), aPlace.spec);
1399 :
1400 10 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
1401 10 : if (!navHistory || !uri) {
1402 : return;
1403 : }
1404 :
1405 10 : navHistory->registerEmbedVisit(uri, aPlace.visitTime);
1406 :
1407 10 : if (aCallback) {
1408 : // NotifyVisitInfoCallback does not hold a strong reference to the callback,
1409 : // so we have to manage it by AddRefing now and then releasing it after the
1410 : // event has run.
1411 8 : NS_ADDREF(aCallback);
1412 : nsCOMPtr<nsIRunnable> event =
1413 16 : new NotifyVisitInfoCallback(aCallback, aPlace, NS_OK);
1414 8 : (void)NS_DispatchToMainThread(event);
1415 :
1416 : // Also dispatch an event to release our reference to the callback after
1417 : // NotifyVisitInfoCallback has run.
1418 16 : nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
1419 8 : (void)NS_ProxyRelease(mainThread, aCallback, true);
1420 : }
1421 :
1422 20 : VisitData noReferrer;
1423 20 : nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
1424 10 : (void)NS_DispatchToMainThread(event);
1425 : }
1426 :
1427 0 : NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(HistoryLinksHashtableMallocSizeOf,
1428 : "history-links-hashtable")
1429 :
1430 0 : PRInt64 GetHistoryObserversSize()
1431 : {
1432 0 : History* history = History::GetService();
1433 0 : if (!history)
1434 0 : return 0;
1435 0 : return history->SizeOfIncludingThis(HistoryLinksHashtableMallocSizeOf);
1436 : }
1437 :
1438 123 : NS_MEMORY_REPORTER_IMPLEMENT(HistoryService,
1439 : "explicit/history-links-hashtable",
1440 : KIND_HEAP,
1441 : UNITS_BYTES,
1442 : GetHistoryObserversSize,
1443 : "Memory used by the hashtable of observers Places uses to notify objects of "
1444 369 : "changes to links' visited state.")
1445 :
1446 : } // anonymous namespace
1447 :
1448 : ////////////////////////////////////////////////////////////////////////////////
1449 : //// History
1450 :
1451 : History* History::gService = NULL;
1452 :
1453 123 : History::History()
1454 : : mShuttingDown(false)
1455 123 : , mShutdownMutex("History::mShutdownMutex")
1456 : {
1457 123 : NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!");
1458 123 : gService = this;
1459 :
1460 246 : nsCOMPtr<nsIObserverService> os = services::GetObserverService();
1461 123 : NS_WARN_IF_FALSE(os, "Observer service was not found!");
1462 123 : if (os) {
1463 123 : (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
1464 : }
1465 :
1466 123 : NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(HistoryService));
1467 123 : }
1468 :
1469 369 : History::~History()
1470 : {
1471 123 : gService = NULL;
1472 :
1473 : #ifdef DEBUG
1474 123 : if (mObservers.IsInitialized()) {
1475 1 : NS_ASSERTION(mObservers.Count() == 0,
1476 : "Not all Links were removed before we disappear!");
1477 : }
1478 : #endif
1479 492 : }
1480 :
1481 : void
1482 2323 : History::NotifyVisited(nsIURI* aURI)
1483 : {
1484 2323 : NS_ASSERTION(aURI, "Ruh-roh! A NULL URI was passed to us!");
1485 :
1486 4646 : nsAutoScriptBlocker scriptBlocker;
1487 :
1488 2323 : if (XRE_GetProcessType() == GeckoProcessType_Default) {
1489 4646 : nsTArray<ContentParent*> cplist;
1490 2323 : ContentParent::GetAll(cplist);
1491 2323 : for (PRUint32 i = 0; i < cplist.Length(); ++i) {
1492 0 : unused << cplist[i]->SendNotifyVisited(aURI);
1493 : }
1494 : }
1495 :
1496 : // If the hash table has not been initialized, then we have nothing to notify
1497 : // about.
1498 2323 : if (!mObservers.IsInitialized()) {
1499 : return;
1500 : }
1501 :
1502 : // Additionally, if we have no observers for this URI, we have nothing to
1503 : // notify about.
1504 20 : KeyClass* key = mObservers.GetEntry(aURI);
1505 20 : if (!key) {
1506 : return;
1507 : }
1508 :
1509 : // Update status of each Link node.
1510 : {
1511 : // RemoveEntry will destroy the array, this iterator should not survive it.
1512 8 : ObserverArray::ForwardIterator iter(key->array);
1513 13 : while (iter.HasMore()) {
1514 5 : Link* link = iter.GetNext();
1515 5 : link->SetLinkState(eLinkState_Visited);
1516 : // Verify that the observers hash doesn't mutate while looping through
1517 : // the links associated with this URI.
1518 5 : NS_ABORT_IF_FALSE(key == mObservers.GetEntry(aURI),
1519 : "The URIs hash mutated!");
1520 : }
1521 : }
1522 :
1523 : // All the registered nodes can now be removed for this URI.
1524 4 : mObservers.RemoveEntry(aURI);
1525 : }
1526 :
1527 : mozIStorageAsyncStatement*
1528 342 : History::GetIsVisitedStatement()
1529 : {
1530 342 : if (mIsVisitedStatement) {
1531 340 : return mIsVisitedStatement;
1532 : }
1533 :
1534 : // If we don't yet have a database connection, go ahead and clone it now.
1535 2 : if (!mReadOnlyDBConn) {
1536 2 : mozIStorageConnection* dbConn = GetDBConn();
1537 2 : NS_ENSURE_TRUE(dbConn, nsnull);
1538 :
1539 2 : (void)dbConn->Clone(true, getter_AddRefs(mReadOnlyDBConn));
1540 2 : NS_ENSURE_TRUE(mReadOnlyDBConn, nsnull);
1541 : }
1542 :
1543 : // Now we can create our cached statement.
1544 4 : nsresult rv = mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
1545 : "SELECT 1 "
1546 : "FROM moz_places h "
1547 : "WHERE url = ?1 "
1548 : "AND last_visit_date NOTNULL "
1549 4 : ), getter_AddRefs(mIsVisitedStatement));
1550 2 : NS_ENSURE_SUCCESS(rv, nsnull);
1551 2 : return mIsVisitedStatement;
1552 : }
1553 :
1554 : nsresult
1555 456 : History::InsertPlace(const VisitData& aPlace)
1556 : {
1557 456 : NS_PRECONDITION(aPlace.placeId == 0, "should not have a valid place id!");
1558 456 : NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
1559 :
1560 : nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
1561 : "INSERT INTO moz_places "
1562 : "(url, title, rev_host, hidden, typed, guid) "
1563 : "VALUES (:url, :title, :rev_host, :hidden, :typed, :guid) "
1564 912 : );
1565 456 : NS_ENSURE_STATE(stmt);
1566 912 : mozStorageStatementScoper scoper(stmt);
1567 :
1568 912 : nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
1569 456 : aPlace.revHost);
1570 456 : NS_ENSURE_SUCCESS(rv, rv);
1571 456 : rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
1572 456 : NS_ENSURE_SUCCESS(rv, rv);
1573 : // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
1574 456 : if (aPlace.title.IsEmpty()) {
1575 78 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1576 : }
1577 : else {
1578 756 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
1579 756 : StringHead(aPlace.title, TITLE_LENGTH_MAX));
1580 : }
1581 456 : NS_ENSURE_SUCCESS(rv, rv);
1582 456 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1583 456 : NS_ENSURE_SUCCESS(rv, rv);
1584 456 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1585 456 : NS_ENSURE_SUCCESS(rv, rv);
1586 912 : nsCAutoString guid(aPlace.guid);
1587 456 : if (aPlace.guid.IsVoid()) {
1588 212 : rv = GenerateGUID(guid);
1589 212 : NS_ENSURE_SUCCESS(rv, rv);
1590 : }
1591 456 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
1592 456 : NS_ENSURE_SUCCESS(rv, rv);
1593 456 : rv = stmt->Execute();
1594 456 : NS_ENSURE_SUCCESS(rv, rv);
1595 :
1596 455 : return NS_OK;
1597 : }
1598 :
1599 : nsresult
1600 18 : History::UpdatePlace(const VisitData& aPlace)
1601 : {
1602 18 : NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
1603 18 : NS_PRECONDITION(aPlace.placeId > 0, "must have a valid place id!");
1604 18 : NS_PRECONDITION(!aPlace.guid.IsVoid(), "must have a guid!");
1605 :
1606 : nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
1607 : "UPDATE moz_places "
1608 : "SET title = :title, "
1609 : "hidden = :hidden, "
1610 : "typed = :typed, "
1611 : "guid = :guid "
1612 : "WHERE id = :page_id "
1613 36 : );
1614 18 : NS_ENSURE_STATE(stmt);
1615 36 : mozStorageStatementScoper scoper(stmt);
1616 :
1617 : nsresult rv;
1618 : // Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
1619 18 : if (aPlace.title.IsEmpty()) {
1620 7 : rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
1621 : }
1622 : else {
1623 22 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
1624 22 : StringHead(aPlace.title, TITLE_LENGTH_MAX));
1625 : }
1626 18 : NS_ENSURE_SUCCESS(rv, rv);
1627 18 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
1628 18 : NS_ENSURE_SUCCESS(rv, rv);
1629 18 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
1630 18 : NS_ENSURE_SUCCESS(rv, rv);
1631 18 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
1632 18 : NS_ENSURE_SUCCESS(rv, rv);
1633 36 : rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
1634 18 : aPlace.placeId);
1635 18 : NS_ENSURE_SUCCESS(rv, rv);
1636 18 : rv = stmt->Execute();
1637 18 : NS_ENSURE_SUCCESS(rv, rv);
1638 :
1639 18 : return NS_OK;
1640 : }
1641 :
1642 : bool
1643 937 : History::FetchPageInfo(VisitData& _place)
1644 : {
1645 937 : NS_PRECONDITION(!_place.spec.IsEmpty(), "must have a non-empty spec!");
1646 937 : NS_PRECONDITION(!NS_IsMainThread(), "must be called off of the main thread!");
1647 :
1648 : nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
1649 : "SELECT id, title, hidden, typed, guid "
1650 : "FROM moz_places "
1651 : "WHERE url = :page_url "
1652 1874 : );
1653 937 : NS_ENSURE_TRUE(stmt, false);
1654 1874 : mozStorageStatementScoper scoper(stmt);
1655 :
1656 937 : nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
1657 937 : _place.spec);
1658 937 : NS_ENSURE_SUCCESS(rv, false);
1659 :
1660 : bool hasResult;
1661 937 : rv = stmt->ExecuteStep(&hasResult);
1662 937 : NS_ENSURE_SUCCESS(rv, false);
1663 937 : if (!hasResult) {
1664 456 : return false;
1665 : }
1666 :
1667 481 : rv = stmt->GetInt64(0, &_place.placeId);
1668 481 : NS_ENSURE_SUCCESS(rv, false);
1669 :
1670 962 : nsAutoString title;
1671 481 : rv = stmt->GetString(1, title);
1672 481 : NS_ENSURE_SUCCESS(rv, true);
1673 :
1674 : // If the title we were given was void, that means we did not bother to set
1675 : // it to anything. As a result, ignore the fact that we may have changed the
1676 : // title (because we don't want to, that would be empty), and set the title
1677 : // to what is currently stored in the datbase.
1678 481 : if (_place.title.IsVoid()) {
1679 81 : _place.title = title;
1680 : }
1681 : // Otherwise, just indicate if the title has changed.
1682 : else {
1683 400 : _place.titleChanged = !(_place.title.Equals(title) ||
1684 400 : (_place.title.IsEmpty() && title.IsVoid()));
1685 : }
1686 :
1687 481 : if (_place.hidden) {
1688 : // If this transition was hidden, it is possible that others were not.
1689 : // Any one visible transition makes this location visible. If database
1690 : // has location as visible, reflect that in our data structure.
1691 : PRInt32 hidden;
1692 31 : rv = stmt->GetInt32(2, &hidden);
1693 31 : _place.hidden = !!hidden;
1694 31 : NS_ENSURE_SUCCESS(rv, true);
1695 : }
1696 :
1697 481 : if (!_place.typed) {
1698 : // If this transition wasn't typed, others might have been. If database
1699 : // has location as typed, reflect that in our data structure.
1700 : PRInt32 typed;
1701 467 : rv = stmt->GetInt32(3, &typed);
1702 467 : _place.typed = !!typed;
1703 467 : NS_ENSURE_SUCCESS(rv, true);
1704 : }
1705 :
1706 481 : if (_place.guid.IsVoid()) {
1707 237 : rv = stmt->GetUTF8String(4, _place.guid);
1708 237 : NS_ENSURE_SUCCESS(rv, true);
1709 : }
1710 :
1711 481 : return true;
1712 : }
1713 :
1714 : /* static */ size_t
1715 0 : History::SizeOfEntryExcludingThis(KeyClass* aEntry, nsMallocSizeOfFun aMallocSizeOf, void *)
1716 : {
1717 0 : return aEntry->array.SizeOfExcludingThis(aMallocSizeOf);
1718 : }
1719 :
1720 : size_t
1721 0 : History::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOfThis)
1722 : {
1723 0 : return aMallocSizeOfThis(this) +
1724 0 : mObservers.SizeOfExcludingThis(SizeOfEntryExcludingThis, aMallocSizeOfThis);
1725 : }
1726 :
1727 : /* static */
1728 : History*
1729 3303 : History::GetService()
1730 : {
1731 3303 : if (gService) {
1732 3201 : return gService;
1733 : }
1734 :
1735 204 : nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
1736 102 : NS_ABORT_IF_FALSE(service, "Cannot obtain IHistory service!");
1737 102 : NS_ASSERTION(gService, "Our constructor was not run?!");
1738 :
1739 102 : return gService;
1740 : }
1741 :
1742 : /* static */
1743 : History*
1744 123 : History::GetSingleton()
1745 : {
1746 123 : if (!gService) {
1747 123 : gService = new History();
1748 123 : NS_ENSURE_TRUE(gService, nsnull);
1749 : }
1750 :
1751 123 : NS_ADDREF(gService);
1752 123 : return gService;
1753 : }
1754 :
1755 : mozIStorageConnection*
1756 3558 : History::GetDBConn()
1757 : {
1758 3558 : if (!mDB) {
1759 22 : mDB = Database::GetDatabase();
1760 22 : NS_ENSURE_TRUE(mDB, nsnull);
1761 : }
1762 3558 : return mDB->MainConn();
1763 : }
1764 :
1765 : void
1766 123 : History::Shutdown()
1767 : {
1768 123 : MOZ_ASSERT(NS_IsMainThread());
1769 :
1770 : // Prevent other threads from scheduling uses of the DB while we mark
1771 : // ourselves as shutting down.
1772 246 : MutexAutoLock lockedScope(mShutdownMutex);
1773 123 : MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
1774 :
1775 123 : mShuttingDown = true;
1776 :
1777 123 : if (mReadOnlyDBConn) {
1778 2 : if (mIsVisitedStatement) {
1779 2 : (void)mIsVisitedStatement->Finalize();
1780 : }
1781 2 : (void)mReadOnlyDBConn->AsyncClose(nsnull);
1782 : }
1783 123 : }
1784 :
1785 : ////////////////////////////////////////////////////////////////////////////////
1786 : //// IHistory
1787 :
1788 : NS_IMETHODIMP
1789 10 : History::VisitURI(nsIURI* aURI,
1790 : nsIURI* aLastVisitedURI,
1791 : PRUint32 aFlags)
1792 : {
1793 10 : NS_PRECONDITION(aURI, "URI should not be NULL.");
1794 10 : if (mShuttingDown) {
1795 0 : return NS_OK;
1796 : }
1797 :
1798 10 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
1799 : mozilla::dom::ContentChild* cpc =
1800 0 : mozilla::dom::ContentChild::GetSingleton();
1801 0 : NS_ASSERTION(cpc, "Content Protocol is NULL!");
1802 0 : (void)cpc->SendVisitURI(aURI, aLastVisitedURI, aFlags);
1803 0 : return NS_OK;
1804 : }
1805 :
1806 10 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
1807 10 : NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
1808 :
1809 : // Silently return if URI is something we shouldn't add to DB.
1810 : bool canAdd;
1811 10 : nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
1812 10 : NS_ENSURE_SUCCESS(rv, rv);
1813 10 : if (!canAdd) {
1814 0 : return NS_OK;
1815 : }
1816 :
1817 10 : if (aLastVisitedURI) {
1818 : bool same;
1819 8 : rv = aURI->Equals(aLastVisitedURI, &same);
1820 8 : NS_ENSURE_SUCCESS(rv, rv);
1821 8 : if (same) {
1822 : // Do not save refresh-page visits.
1823 0 : return NS_OK;
1824 : }
1825 : }
1826 :
1827 20 : nsTArray<VisitData> placeArray(1);
1828 10 : NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
1829 : NS_ERROR_OUT_OF_MEMORY);
1830 10 : VisitData& place = placeArray.ElementAt(0);
1831 10 : NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
1832 :
1833 10 : place.visitTime = PR_Now();
1834 :
1835 : // Assigns a type to the edge in the visit linked list. Each type will be
1836 : // considered differently when weighting the frecency of a location.
1837 10 : PRUint32 recentFlags = navHistory->GetRecentFlags(aURI);
1838 10 : bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
1839 :
1840 : // Embed visits should never be added to the database, and the same is valid
1841 : // for redirects across frames.
1842 : // For the above reasoning non-toplevel transitions are handled at first.
1843 : // if the visit is toplevel or a non-toplevel followed link, then it can be
1844 : // handled as usual and stored on disk.
1845 :
1846 10 : if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
1847 : // A frame redirected to a new site without user interaction.
1848 2 : place.SetTransitionType(nsINavHistoryService::TRANSITION_EMBED);
1849 : }
1850 8 : else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
1851 0 : place.SetTransitionType(nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY);
1852 : }
1853 8 : else if (aFlags & IHistory::REDIRECT_PERMANENT) {
1854 0 : place.SetTransitionType(nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT);
1855 : }
1856 8 : else if (recentFlags & nsNavHistory::RECENT_TYPED) {
1857 1 : place.SetTransitionType(nsINavHistoryService::TRANSITION_TYPED);
1858 : }
1859 7 : else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
1860 0 : place.SetTransitionType(nsINavHistoryService::TRANSITION_BOOKMARK);
1861 : }
1862 7 : else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
1863 : // User activated a link in a frame.
1864 0 : place.SetTransitionType(nsINavHistoryService::TRANSITION_FRAMED_LINK);
1865 : }
1866 : else {
1867 : // User was redirected or link was clicked in the main window.
1868 7 : place.SetTransitionType(nsINavHistoryService::TRANSITION_LINK);
1869 : }
1870 :
1871 : // EMBED visits are session-persistent and should not go through the database.
1872 : // They exist only to keep track of isVisited status during the session.
1873 10 : if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
1874 2 : StoreAndNotifyEmbedVisit(place);
1875 : }
1876 : else {
1877 8 : mozIStorageConnection* dbConn = GetDBConn();
1878 8 : NS_ENSURE_STATE(dbConn);
1879 :
1880 8 : rv = InsertVisitedURIs::Start(dbConn, placeArray);
1881 8 : NS_ENSURE_SUCCESS(rv, rv);
1882 : }
1883 :
1884 : // Finally, notify that we've been visited.
1885 : nsCOMPtr<nsIObserverService> obsService =
1886 20 : mozilla::services::GetObserverService();
1887 10 : if (obsService) {
1888 10 : obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
1889 : }
1890 :
1891 10 : return NS_OK;
1892 : }
1893 :
1894 : NS_IMETHODIMP
1895 11 : History::RegisterVisitedCallback(nsIURI* aURI,
1896 : Link* aLink)
1897 : {
1898 11 : NS_ASSERTION(aURI, "Must pass a non-null URI!");
1899 11 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
1900 0 : NS_PRECONDITION(aLink, "Must pass a non-null Link!");
1901 : }
1902 :
1903 : // First, ensure that our hash table is setup.
1904 11 : if (!mObservers.IsInitialized()) {
1905 1 : NS_ENSURE_TRUE(mObservers.Init(VISIT_OBSERVERS_INITIAL_CACHE_SIZE),
1906 : NS_ERROR_OUT_OF_MEMORY);
1907 : }
1908 :
1909 : // Obtain our array of observers for this URI.
1910 : #ifdef DEBUG
1911 11 : bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
1912 : #endif
1913 11 : KeyClass* key = mObservers.PutEntry(aURI);
1914 11 : NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
1915 11 : ObserverArray& observers = key->array;
1916 :
1917 11 : if (observers.IsEmpty()) {
1918 10 : NS_ASSERTION(!keyAlreadyExists,
1919 : "An empty key was kept around in our hashtable!");
1920 :
1921 : // We are the first Link node to ask about this URI, or there are no pending
1922 : // Links wanting to know about this URI. Therefore, we should query the
1923 : // database now.
1924 10 : nsresult rv = VisitedQuery::Start(aURI);
1925 :
1926 : // In IPC builds, we are passed a NULL Link from
1927 : // ContentParent::RecvStartVisitedQuery. Since we won't be adding a NULL
1928 : // entry to our list of observers, and the code after this point assumes
1929 : // that aLink is non-NULL, we will need to return now.
1930 10 : if (NS_FAILED(rv) || !aLink) {
1931 : // Remove our array from the hashtable so we don't keep it around.
1932 2 : mObservers.RemoveEntry(aURI);
1933 2 : return rv;
1934 : }
1935 : }
1936 : // In IPC builds, we are passed a NULL Link from
1937 : // ContentParent::RecvStartVisitedQuery. All of our code after this point
1938 : // assumes aLink is non-NULL, so we have to return now.
1939 1 : else if (!aLink) {
1940 0 : NS_ASSERTION(XRE_GetProcessType() == GeckoProcessType_Default,
1941 : "We should only ever get a null Link in the default process!");
1942 0 : return NS_OK;
1943 : }
1944 :
1945 : // Sanity check that Links are not registered more than once for a given URI.
1946 : // This will not catch a case where it is registered for two different URIs.
1947 9 : NS_ASSERTION(!observers.Contains(aLink),
1948 : "Already tracking this Link object!");
1949 :
1950 : // Start tracking our Link.
1951 9 : if (!observers.AppendElement(aLink)) {
1952 : // Curses - unregister and return failure.
1953 0 : (void)UnregisterVisitedCallback(aURI, aLink);
1954 0 : return NS_ERROR_OUT_OF_MEMORY;
1955 : }
1956 :
1957 9 : return NS_OK;
1958 : }
1959 :
1960 : NS_IMETHODIMP
1961 4 : History::UnregisterVisitedCallback(nsIURI* aURI,
1962 : Link* aLink)
1963 : {
1964 4 : NS_ASSERTION(aURI, "Must pass a non-null URI!");
1965 4 : NS_ASSERTION(aLink, "Must pass a non-null Link object!");
1966 :
1967 : // Get the array, and remove the item from it.
1968 4 : KeyClass* key = mObservers.GetEntry(aURI);
1969 4 : if (!key) {
1970 0 : NS_ERROR("Trying to unregister for a URI that wasn't registered!");
1971 0 : return NS_ERROR_UNEXPECTED;
1972 : }
1973 4 : ObserverArray& observers = key->array;
1974 4 : if (!observers.RemoveElement(aLink)) {
1975 0 : NS_ERROR("Trying to unregister a node that wasn't registered!");
1976 0 : return NS_ERROR_UNEXPECTED;
1977 : }
1978 :
1979 : // If the array is now empty, we should remove it from the hashtable.
1980 4 : if (observers.IsEmpty()) {
1981 4 : mObservers.RemoveEntry(aURI);
1982 : }
1983 :
1984 4 : return NS_OK;
1985 : }
1986 :
1987 : NS_IMETHODIMP
1988 17 : History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
1989 : {
1990 17 : NS_PRECONDITION(aURI, "Must pass a non-null URI!");
1991 17 : if (mShuttingDown) {
1992 0 : return NS_OK;
1993 : }
1994 :
1995 17 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
1996 : mozilla::dom::ContentChild * cpc =
1997 0 : mozilla::dom::ContentChild::GetSingleton();
1998 0 : NS_ASSERTION(cpc, "Content Protocol is NULL!");
1999 0 : (void)cpc->SendSetURITitle(aURI, nsString(aTitle));
2000 0 : return NS_OK;
2001 : }
2002 :
2003 17 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
2004 :
2005 : // At first, it seems like nav history should always be available here, no
2006 : // matter what.
2007 : //
2008 : // nsNavHistory fails to register as a service if there is no profile in
2009 : // place (for instance, if user is choosing a profile).
2010 : //
2011 : // Maybe the correct thing to do is to not register this service if no
2012 : // profile has been selected?
2013 : //
2014 17 : NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
2015 :
2016 : bool canAdd;
2017 17 : nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
2018 17 : NS_ENSURE_SUCCESS(rv, rv);
2019 17 : if (!canAdd) {
2020 1 : return NS_OK;
2021 : }
2022 :
2023 : // Embed visits don't have a database entry, thus don't set a title on them.
2024 16 : if (navHistory->hasEmbedVisit(aURI)) {
2025 0 : return NS_OK;
2026 : }
2027 :
2028 16 : mozIStorageConnection* dbConn = GetDBConn();
2029 16 : NS_ENSURE_STATE(dbConn);
2030 :
2031 16 : rv = SetPageTitle::Start(dbConn, aURI, aTitle);
2032 16 : NS_ENSURE_SUCCESS(rv, rv);
2033 :
2034 16 : return NS_OK;
2035 : }
2036 :
2037 : ////////////////////////////////////////////////////////////////////////////////
2038 : //// nsIDownloadHistory
2039 :
2040 : NS_IMETHODIMP
2041 27 : History::AddDownload(nsIURI* aSource, nsIURI* aReferrer,
2042 : PRTime aStartTime, nsIURI* aDestination)
2043 : {
2044 27 : MOZ_ASSERT(NS_IsMainThread());
2045 27 : NS_ENSURE_ARG(aSource);
2046 :
2047 27 : if (mShuttingDown) {
2048 0 : return NS_OK;
2049 : }
2050 :
2051 27 : if (XRE_GetProcessType() == GeckoProcessType_Content) {
2052 0 : NS_ERROR("Cannot add downloads to history from content process!");
2053 0 : return NS_ERROR_NOT_AVAILABLE;
2054 : }
2055 :
2056 27 : nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
2057 27 : NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
2058 :
2059 : // Silently return if URI is something we shouldn't add to DB.
2060 : bool canAdd;
2061 27 : nsresult rv = navHistory->CanAddURI(aSource, &canAdd);
2062 27 : NS_ENSURE_SUCCESS(rv, rv);
2063 27 : if (!canAdd) {
2064 3 : return NS_OK;
2065 : }
2066 :
2067 48 : nsTArray<VisitData> placeArray(1);
2068 24 : NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)),
2069 : NS_ERROR_OUT_OF_MEMORY);
2070 24 : VisitData& place = placeArray.ElementAt(0);
2071 24 : NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
2072 :
2073 24 : place.visitTime = aStartTime;
2074 24 : place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD);
2075 :
2076 24 : mozIStorageConnection* dbConn = GetDBConn();
2077 24 : NS_ENSURE_STATE(dbConn);
2078 :
2079 : nsCOMPtr<mozIVisitInfoCallback> callback = aDestination
2080 18 : ? new SetDownloadAnnotations(aDestination)
2081 84 : : nsnull;
2082 :
2083 24 : rv = InsertVisitedURIs::Start(dbConn, placeArray, callback);
2084 24 : NS_ENSURE_SUCCESS(rv, rv);
2085 :
2086 : // Finally, notify that we've been visited.
2087 : nsCOMPtr<nsIObserverService> obsService =
2088 48 : mozilla::services::GetObserverService();
2089 24 : if (obsService) {
2090 24 : obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nsnull);
2091 : }
2092 :
2093 24 : return NS_OK;
2094 : }
2095 :
2096 : ////////////////////////////////////////////////////////////////////////////////
2097 : //// mozIAsyncHistory
2098 :
2099 : NS_IMETHODIMP
2100 200 : History::UpdatePlaces(const jsval& aPlaceInfos,
2101 : mozIVisitInfoCallback* aCallback,
2102 : JSContext* aCtx)
2103 : {
2104 200 : NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
2105 200 : NS_ENSURE_TRUE(!JSVAL_IS_PRIMITIVE(aPlaceInfos), NS_ERROR_INVALID_ARG);
2106 :
2107 197 : uint32_t infosLength = 1;
2108 : JSObject* infos;
2109 197 : if (JS_IsArrayObject(aCtx, JSVAL_TO_OBJECT(aPlaceInfos))) {
2110 36 : infos = JSVAL_TO_OBJECT(aPlaceInfos);
2111 36 : (void)JS_GetArrayLength(aCtx, infos, &infosLength);
2112 36 : NS_ENSURE_ARG(infosLength > 0);
2113 : }
2114 : else {
2115 : // Build a temporary array to store this one item so the code below can
2116 : // just loop.
2117 161 : infos = JS_NewArrayObject(aCtx, 0, NULL);
2118 161 : NS_ENSURE_TRUE(infos, NS_ERROR_OUT_OF_MEMORY);
2119 :
2120 161 : JSBool rc = JS_DefineElement(aCtx, infos, 0, aPlaceInfos, NULL, NULL, 0);
2121 161 : NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
2122 : }
2123 :
2124 392 : nsTArray<VisitData> visitData;
2125 730 : for (uint32_t i = 0; i < infosLength; i++) {
2126 : JSObject* info;
2127 555 : nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
2128 555 : NS_ENSURE_SUCCESS(rv, rv);
2129 :
2130 1110 : nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
2131 1110 : nsCString guid;
2132 : {
2133 1110 : nsString fatGUID;
2134 555 : GetStringFromJSObject(aCtx, info, "guid", fatGUID);
2135 555 : if (fatGUID.IsVoid()) {
2136 302 : guid.SetIsVoid(true);
2137 : }
2138 : else {
2139 253 : guid = NS_ConvertUTF16toUTF8(fatGUID);
2140 : }
2141 : }
2142 :
2143 : // Make sure that any uri we are given can be added to history, and if not,
2144 : // skip it (CanAddURI will notify our callback for us).
2145 555 : if (uri && !CanAddURI(uri, guid, aCallback)) {
2146 93 : continue;
2147 : }
2148 :
2149 : // We must have at least one of uri or guid.
2150 462 : NS_ENSURE_ARG(uri || !guid.IsVoid());
2151 :
2152 : // If we were given a guid, make sure it is valid.
2153 453 : bool isValidGUID = IsValidGUID(guid);
2154 453 : NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
2155 :
2156 902 : nsString title;
2157 451 : GetStringFromJSObject(aCtx, info, "title", title);
2158 :
2159 451 : JSObject* visits = NULL;
2160 : {
2161 : jsval visitsVal;
2162 451 : JSBool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
2163 451 : NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
2164 451 : if (!JSVAL_IS_PRIMITIVE(visitsVal)) {
2165 448 : visits = JSVAL_TO_OBJECT(visitsVal);
2166 448 : NS_ENSURE_ARG(JS_IsArrayObject(aCtx, visits));
2167 : }
2168 : }
2169 451 : NS_ENSURE_ARG(visits);
2170 :
2171 448 : uint32_t visitsLength = 0;
2172 448 : if (visits) {
2173 448 : (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
2174 : }
2175 448 : NS_ENSURE_ARG(visitsLength > 0);
2176 :
2177 : // Check each visit, and build our array of VisitData objects.
2178 445 : visitData.SetCapacity(visitData.Length() + visitsLength);
2179 895 : for (uint32_t j = 0; j < visitsLength; j++) {
2180 : JSObject* visit;
2181 454 : rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
2182 454 : NS_ENSURE_SUCCESS(rv, rv);
2183 :
2184 454 : VisitData& data = *visitData.AppendElement(VisitData(uri));
2185 454 : data.title = title;
2186 454 : data.guid = guid;
2187 :
2188 : // We must have a date and a transaction type!
2189 454 : rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
2190 454 : NS_ENSURE_SUCCESS(rv, rv);
2191 453 : PRUint32 transitionType = 0;
2192 453 : rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
2193 453 : NS_ENSURE_SUCCESS(rv, rv);
2194 452 : NS_ENSURE_ARG_RANGE(transitionType,
2195 : nsINavHistoryService::TRANSITION_LINK,
2196 : nsINavHistoryService::TRANSITION_FRAMED_LINK);
2197 450 : data.SetTransitionType(transitionType);
2198 :
2199 : // If the visit is an embed visit, we do not actually add it to the
2200 : // database.
2201 450 : if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
2202 8 : StoreAndNotifyEmbedVisit(data, aCallback);
2203 8 : visitData.RemoveElementAt(visitData.Length() - 1);
2204 8 : continue;
2205 : }
2206 :
2207 : // The session id is optional.
2208 442 : rv = GetIntFromJSObject(aCtx, visit, "sessionId", &data.sessionId);
2209 442 : if (rv == NS_ERROR_INVALID_ARG) {
2210 439 : data.sessionId = 0;
2211 : }
2212 : else {
2213 3 : NS_ENSURE_SUCCESS(rv, rv);
2214 : }
2215 :
2216 : // The referrer is optional.
2217 : nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
2218 884 : "referrerURI");
2219 442 : if (referrer) {
2220 4 : (void)referrer->GetSpec(data.referrerSpec);
2221 : }
2222 : }
2223 : }
2224 :
2225 175 : mozIStorageConnection* dbConn = GetDBConn();
2226 175 : NS_ENSURE_STATE(dbConn);
2227 :
2228 : // It is possible that all of the visits we were passed were dissallowed by
2229 : // CanAddURI, which isn't an error. If we have no visits to add, however,
2230 : // we should not call InsertVisitedURIs::Start.
2231 175 : if (visitData.Length()) {
2232 89 : nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, aCallback);
2233 89 : NS_ENSURE_SUCCESS(rv, rv);
2234 : }
2235 :
2236 : // Be sure to notify that all of our operations are complete. This
2237 : // is dispatched to the background thread first and redirected to the
2238 : // main thread from there to make sure that all database notifications
2239 : // and all embed or canAddURI notifications have finished.
2240 175 : if (aCallback) {
2241 : // NotifyCompletion does not hold a strong reference to the callback,
2242 : // so we have to manage it by AddRefing now. NotifyCompletion will
2243 : // release it for us once it has dispatched the callback to the main
2244 : // thread.
2245 154 : NS_ADDREF(aCallback);
2246 :
2247 308 : nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
2248 154 : NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
2249 462 : nsCOMPtr<nsIRunnable> event = new NotifyCompletion(aCallback);
2250 154 : (void)backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
2251 : }
2252 :
2253 175 : return NS_OK;
2254 : }
2255 :
2256 : NS_IMETHODIMP
2257 336 : History::IsURIVisited(nsIURI* aURI,
2258 : mozIVisitedStatusCallback* aCallback)
2259 : {
2260 336 : NS_ENSURE_STATE(NS_IsMainThread());
2261 336 : NS_ENSURE_ARG(aURI);
2262 336 : NS_ENSURE_ARG(aCallback);
2263 :
2264 336 : nsresult rv = VisitedQuery::Start(aURI, aCallback);
2265 336 : NS_ENSURE_SUCCESS(rv, rv);
2266 :
2267 336 : return NS_OK;
2268 : }
2269 :
2270 : ////////////////////////////////////////////////////////////////////////////////
2271 : //// nsIObserver
2272 :
2273 : NS_IMETHODIMP
2274 123 : History::Observe(nsISupports* aSubject, const char* aTopic,
2275 : const PRUnichar* aData)
2276 : {
2277 123 : if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
2278 123 : Shutdown();
2279 :
2280 246 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
2281 123 : if (os) {
2282 123 : (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
2283 : }
2284 : }
2285 :
2286 123 : return NS_OK;
2287 : }
2288 :
2289 : ////////////////////////////////////////////////////////////////////////////////
2290 : //// nsISupports
2291 :
2292 4067 : NS_IMPL_THREADSAFE_ISUPPORTS4(
2293 : History
2294 : , IHistory
2295 : , nsIDownloadHistory
2296 : , mozIAsyncHistory
2297 : , nsIObserver
2298 : )
2299 :
2300 : } // namespace places
2301 : } // namespace mozilla
|