Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AsyncFaviconHelpers.h"
9 #include "nsICacheService.h"
10 #include "nsICacheEntry.h"
11 #include "nsICachingChannel.h"
12 #include "nsIAsyncVerifyRedirectCallback.h"
14 #include "nsNavHistory.h"
15 #include "nsFaviconService.h"
16 #include "mozilla/storage.h"
17 #include "nsNetUtil.h"
18 #include "nsPrintfCString.h"
19 #include "nsStreamUtils.h"
20 #include "nsIPrivateBrowsingChannel.h"
21 #include "nsISupportsPriority.h"
22 #include <algorithm>
24 using namespace mozilla::places;
25 using namespace mozilla::storage;
27 namespace mozilla {
28 namespace places {
30 namespace {
32 /**
33 * Fetches information on a page from the Places database.
34 *
35 * @param aDBConn
36 * Database connection to history tables.
37 * @param _page
38 * Page that should be fetched.
39 */
40 nsresult
41 FetchPageInfo(nsRefPtr<Database>& aDB,
42 PageData& _page)
43 {
44 NS_PRECONDITION(_page.spec.Length(), "Must have a non-empty spec!");
45 NS_PRECONDITION(!NS_IsMainThread(),
46 "This should not be called on the main thread");
48 // This query finds the bookmarked uri we want to set the icon for,
49 // walking up to two redirect levels.
50 nsCString query = nsPrintfCString(
51 "SELECT h.id, h.favicon_id, h.guid, ( "
52 "SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id "
53 "UNION ALL " // Union not directly bookmarked pages.
54 "SELECT url FROM moz_places WHERE id = ( "
55 "SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id "
56 "FROM moz_historyvisits dest "
57 "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
58 "AND dest.visit_type IN (%d, %d) "
59 "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
60 "AND parent.visit_type IN (%d, %d) "
61 "WHERE dest.place_id = h.id "
62 "AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
63 "LIMIT 1 "
64 ") "
65 ") FROM moz_places h WHERE h.url = :page_url",
66 nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
67 nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
68 nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
69 nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
70 );
72 nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
73 NS_ENSURE_STATE(stmt);
74 mozStorageStatementScoper scoper(stmt);
76 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
77 _page.spec);
78 NS_ENSURE_SUCCESS(rv, rv);
80 bool hasResult;
81 rv = stmt->ExecuteStep(&hasResult);
82 NS_ENSURE_SUCCESS(rv, rv);
83 if (!hasResult) {
84 // The page does not exist.
85 return NS_ERROR_NOT_AVAILABLE;
86 }
88 rv = stmt->GetInt64(0, &_page.id);
89 NS_ENSURE_SUCCESS(rv, rv);
90 bool isNull;
91 rv = stmt->GetIsNull(1, &isNull);
92 NS_ENSURE_SUCCESS(rv, rv);
93 // favicon_id can be nullptr.
94 if (!isNull) {
95 rv = stmt->GetInt64(1, &_page.iconId);
96 NS_ENSURE_SUCCESS(rv, rv);
97 }
98 rv = stmt->GetUTF8String(2, _page.guid);
99 NS_ENSURE_SUCCESS(rv, rv);
100 rv = stmt->GetIsNull(3, &isNull);
101 NS_ENSURE_SUCCESS(rv, rv);
102 // The page could not be bookmarked.
103 if (!isNull) {
104 rv = stmt->GetUTF8String(3, _page.bookmarkedSpec);
105 NS_ENSURE_SUCCESS(rv, rv);
106 }
108 if (!_page.canAddToHistory) {
109 // Either history is disabled or the scheme is not supported. In such a
110 // case we want to update the icon only if the page is bookmarked.
112 if (_page.bookmarkedSpec.IsEmpty()) {
113 // The page is not bookmarked. Since updating the icon with a disabled
114 // history would be a privacy leak, bail out as if the page did not exist.
115 return NS_ERROR_NOT_AVAILABLE;
116 }
117 else {
118 // The page, or a redirect to it, is bookmarked. If the bookmarked spec
119 // is different from the requested one, use it.
120 if (!_page.bookmarkedSpec.Equals(_page.spec)) {
121 _page.spec = _page.bookmarkedSpec;
122 rv = FetchPageInfo(aDB, _page);
123 NS_ENSURE_SUCCESS(rv, rv);
124 }
125 }
126 }
128 return NS_OK;
129 }
131 /**
132 * Stores information on a icon in the database.
133 *
134 * @param aDBConn
135 * Database connection to history tables.
136 * @param aIcon
137 * Icon that should be stored.
138 */
139 nsresult
140 SetIconInfo(nsRefPtr<Database>& aDB,
141 IconData& aIcon)
142 {
143 NS_PRECONDITION(!NS_IsMainThread(),
144 "This should not be called on the main thread");
146 // The 'multi-coalesce' here ensures that replacing a favicon without
147 // specifying a :guid parameter doesn't cause it to be allocated a new
148 // GUID.
149 nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
150 "INSERT OR REPLACE INTO moz_favicons "
151 "(id, url, data, mime_type, expiration, guid) "
152 "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), "
153 ":icon_url, :data, :mime_type, :expiration, "
154 "COALESCE(:guid, "
155 "(SELECT guid FROM moz_favicons "
156 "WHERE url = :icon_url), "
157 "GENERATE_GUID()))"
158 );
159 NS_ENSURE_STATE(stmt);
160 mozStorageStatementScoper scoper(stmt);
161 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aIcon.spec);
162 NS_ENSURE_SUCCESS(rv, rv);
163 rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
164 TO_INTBUFFER(aIcon.data), aIcon.data.Length());
165 NS_ENSURE_SUCCESS(rv, rv);
166 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), aIcon.mimeType);
167 NS_ENSURE_SUCCESS(rv, rv);
168 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aIcon.expiration);
169 NS_ENSURE_SUCCESS(rv, rv);
171 // Binding a GUID allows us to override the current (or generated) GUID.
172 if (aIcon.guid.IsEmpty()) {
173 rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid"));
174 }
175 else {
176 rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aIcon.guid);
177 }
178 NS_ENSURE_SUCCESS(rv, rv);
180 rv = stmt->Execute();
181 NS_ENSURE_SUCCESS(rv, rv);
183 return NS_OK;
184 }
186 /**
187 * Fetches information on a icon from the Places database.
188 *
189 * @param aDBConn
190 * Database connection to history tables.
191 * @param _icon
192 * Icon that should be fetched.
193 */
194 nsresult
195 FetchIconInfo(nsRefPtr<Database>& aDB,
196 IconData& _icon)
197 {
198 NS_PRECONDITION(_icon.spec.Length(), "Must have a non-empty spec!");
199 NS_PRECONDITION(!NS_IsMainThread(),
200 "This should not be called on the main thread");
202 if (_icon.status & ICON_STATUS_CACHED) {
203 return NS_OK;
204 }
206 nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
207 "SELECT id, expiration, data, mime_type "
208 "FROM moz_favicons WHERE url = :icon_url"
209 );
210 NS_ENSURE_STATE(stmt);
211 mozStorageStatementScoper scoper(stmt);
213 DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"),
214 _icon.spec);
215 MOZ_ASSERT(NS_SUCCEEDED(rv));
217 bool hasResult;
218 rv = stmt->ExecuteStep(&hasResult);
219 MOZ_ASSERT(NS_SUCCEEDED(rv));
220 if (!hasResult) {
221 // The icon does not exist yet, bail out.
222 return NS_OK;
223 }
225 rv = stmt->GetInt64(0, &_icon.id);
226 MOZ_ASSERT(NS_SUCCEEDED(rv));
228 // Expiration can be nullptr.
229 bool isNull;
230 rv = stmt->GetIsNull(1, &isNull);
231 MOZ_ASSERT(NS_SUCCEEDED(rv));
232 if (!isNull) {
233 rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_icon.expiration));
234 MOZ_ASSERT(NS_SUCCEEDED(rv));
235 }
237 // Data can be nullptr.
238 rv = stmt->GetIsNull(2, &isNull);
239 MOZ_ASSERT(NS_SUCCEEDED(rv));
240 if (!isNull) {
241 uint8_t* data;
242 uint32_t dataLen = 0;
243 rv = stmt->GetBlob(2, &dataLen, &data);
244 MOZ_ASSERT(NS_SUCCEEDED(rv));
245 _icon.data.Adopt(TO_CHARBUFFER(data), dataLen);
246 // Read mime only if we have data.
247 rv = stmt->GetUTF8String(3, _icon.mimeType);
248 MOZ_ASSERT(NS_SUCCEEDED(rv));
249 }
251 return NS_OK;
252 }
254 nsresult
255 FetchIconURL(nsRefPtr<Database>& aDB,
256 const nsACString& aPageSpec,
257 nsACString& aIconSpec)
258 {
259 NS_PRECONDITION(!aPageSpec.IsEmpty(), "Page spec must not be empty.");
260 NS_PRECONDITION(!NS_IsMainThread(),
261 "This should not be called on the main thread.");
263 aIconSpec.Truncate();
265 nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
266 "SELECT f.url "
267 "FROM moz_places h "
268 "JOIN moz_favicons f ON h.favicon_id = f.id "
269 "WHERE h.url = :page_url"
270 );
271 NS_ENSURE_STATE(stmt);
272 mozStorageStatementScoper scoper(stmt);
274 nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
275 aPageSpec);
276 NS_ENSURE_SUCCESS(rv, rv);
278 bool hasResult;
279 if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
280 rv = stmt->GetUTF8String(0, aIconSpec);
281 NS_ENSURE_SUCCESS(rv, rv);
282 }
284 return NS_OK;
285 }
287 /**
288 * Tries to compute the expiration time for a icon from the channel.
289 *
290 * @param aChannel
291 * The network channel used to fetch the icon.
292 * @return a valid expiration value for the fetched icon.
293 */
294 PRTime
295 GetExpirationTimeFromChannel(nsIChannel* aChannel)
296 {
297 NS_PRECONDITION(NS_IsMainThread(),
298 "This should be called on the main thread");
300 // Attempt to get an expiration time from the cache. If this fails, we'll
301 // make one up.
302 PRTime expiration = -1;
303 nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
304 if (cachingChannel) {
305 nsCOMPtr<nsISupports> cacheToken;
306 nsresult rv = cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
307 if (NS_SUCCEEDED(rv)) {
308 nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
309 uint32_t seconds;
310 rv = cacheEntry->GetExpirationTime(&seconds);
311 if (NS_SUCCEEDED(rv)) {
312 // Set the expiration, but make sure we honor our cap.
313 expiration = PR_Now() + std::min((PRTime)seconds * PR_USEC_PER_SEC,
314 MAX_FAVICON_EXPIRATION);
315 }
316 }
317 }
318 // If we did not obtain a time from the cache, use the cap value.
319 return expiration < 0 ? PR_Now() + MAX_FAVICON_EXPIRATION
320 : expiration;
321 }
323 /**
324 * Checks the icon and evaluates if it needs to be optimized. In such a case it
325 * will try to reduce its size through OptimizeFaviconImage method of the
326 * favicons service.
327 *
328 * @param aIcon
329 * The icon to be evaluated.
330 * @param aFaviconSvc
331 * Pointer to the favicons service.
332 */
333 nsresult
334 OptimizeIconSize(IconData& aIcon,
335 nsFaviconService* aFaviconSvc)
336 {
337 NS_PRECONDITION(NS_IsMainThread(),
338 "This should be called on the main thread");
340 // Even if the page provides a large image for the favicon (eg, a highres
341 // image or a multiresolution .ico file), don't try to store more data than
342 // needed.
343 nsAutoCString newData, newMimeType;
344 if (aIcon.data.Length() > MAX_ICON_FILESIZE(aFaviconSvc->GetOptimizedIconDimension())) {
345 nsresult rv = aFaviconSvc->OptimizeFaviconImage(TO_INTBUFFER(aIcon.data),
346 aIcon.data.Length(),
347 aIcon.mimeType,
348 newData,
349 newMimeType);
350 if (NS_SUCCEEDED(rv) && newData.Length() < aIcon.data.Length()) {
351 aIcon.data = newData;
352 aIcon.mimeType = newMimeType;
353 }
354 }
355 return NS_OK;
356 }
358 } // Anonymous namespace.
361 ////////////////////////////////////////////////////////////////////////////////
362 //// AsyncFaviconHelperBase
364 AsyncFaviconHelperBase::AsyncFaviconHelperBase(
365 nsCOMPtr<nsIFaviconDataCallback>& aCallback
366 ) : mDB(Database::GetDatabase())
367 {
368 // Don't AddRef or Release in runnables for thread-safety.
369 mCallback.swap(aCallback);
370 }
372 AsyncFaviconHelperBase::~AsyncFaviconHelperBase()
373 {
374 nsCOMPtr<nsIThread> thread;
375 (void)NS_GetMainThread(getter_AddRefs(thread));
376 if (mCallback) {
377 (void)NS_ProxyRelease(thread, mCallback, true);
378 }
379 }
381 ////////////////////////////////////////////////////////////////////////////////
382 //// AsyncFetchAndSetIconForPage
384 // static
385 nsresult
386 AsyncFetchAndSetIconForPage::start(nsIURI* aFaviconURI,
387 nsIURI* aPageURI,
388 enum AsyncFaviconFetchMode aFetchMode,
389 uint32_t aFaviconLoadType,
390 nsIFaviconDataCallback* aCallback)
391 {
392 NS_PRECONDITION(NS_IsMainThread(),
393 "This should be called on the main thread");
395 PageData page;
396 nsresult rv = aPageURI->GetSpec(page.spec);
397 NS_ENSURE_SUCCESS(rv, rv);
398 // URIs can arguably miss a host.
399 (void)GetReversedHostname(aPageURI, page.revHost);
400 bool canAddToHistory;
401 nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
402 NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
403 rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
404 NS_ENSURE_SUCCESS(rv, rv);
405 page.canAddToHistory = !!canAddToHistory && aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE;
407 IconData icon;
409 nsFaviconService* favicons = nsFaviconService::GetFaviconService();
410 NS_ENSURE_STATE(favicons);
412 UnassociatedIconHashKey* iconKey =
413 favicons->mUnassociatedIcons.GetEntry(aFaviconURI);
415 if (iconKey) {
416 icon = iconKey->iconData;
417 favicons->mUnassociatedIcons.RemoveEntry(aFaviconURI);
418 } else {
419 icon.fetchMode = aFetchMode;
420 rv = aFaviconURI->GetSpec(icon.spec);
421 NS_ENSURE_SUCCESS(rv, rv);
422 }
424 // If the page url points to an image, the icon's url will be the same.
425 // In future evaluate to store a resample of the image. For now avoid that
426 // for database size concerns.
427 // Don't store favicons for error pages too.
428 if (icon.spec.Equals(page.spec) ||
429 icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
430 return NS_OK;
431 }
433 // The event will swap owning pointers, thus we need a new pointer.
434 nsCOMPtr<nsIFaviconDataCallback> callback(aCallback);
435 nsRefPtr<AsyncFetchAndSetIconForPage> event =
436 new AsyncFetchAndSetIconForPage(icon, page, aFaviconLoadType, callback);
438 // Get the target thread and start the work.
439 nsRefPtr<Database> DB = Database::GetDatabase();
440 NS_ENSURE_STATE(DB);
441 DB->DispatchToAsyncThread(event);
443 return NS_OK;
444 }
446 AsyncFetchAndSetIconForPage::AsyncFetchAndSetIconForPage(
447 IconData& aIcon
448 , PageData& aPage
449 , uint32_t aFaviconLoadType
450 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
451 ) : AsyncFaviconHelperBase(aCallback)
452 , mIcon(aIcon)
453 , mPage(aPage)
454 , mFaviconLoadPrivate(aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE)
455 {
456 }
458 AsyncFetchAndSetIconForPage::~AsyncFetchAndSetIconForPage()
459 {
460 }
462 NS_IMETHODIMP
463 AsyncFetchAndSetIconForPage::Run()
464 {
465 NS_PRECONDITION(!NS_IsMainThread(),
466 "This should not be called on the main thread");
468 // Try to fetch the icon from the database.
469 nsresult rv = FetchIconInfo(mDB, mIcon);
470 NS_ENSURE_SUCCESS(rv, rv);
472 bool isInvalidIcon = mIcon.data.IsEmpty() ||
473 (mIcon.expiration && PR_Now() > mIcon.expiration);
474 bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS ||
475 (mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon);
477 if (!fetchIconFromNetwork) {
478 // There is already a valid icon or we don't want to fetch a new one,
479 // directly proceed with association.
480 nsRefPtr<AsyncAssociateIconToPage> event =
481 new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
482 mDB->DispatchToAsyncThread(event);
484 return NS_OK;
485 }
486 else {
487 // Fetch the icon from network. When done this will associate the
488 // icon to the page and notify.
489 nsRefPtr<AsyncFetchAndSetIconFromNetwork> event =
490 new AsyncFetchAndSetIconFromNetwork(mIcon, mPage, mFaviconLoadPrivate, mCallback);
492 // Start the work on the main thread.
493 rv = NS_DispatchToMainThread(event);
494 NS_ENSURE_SUCCESS(rv, rv);
495 }
497 return NS_OK;
498 }
500 ////////////////////////////////////////////////////////////////////////////////
501 //// AsyncFetchAndSetIconFromNetwork
503 NS_IMPL_ISUPPORTS_INHERITED(
504 AsyncFetchAndSetIconFromNetwork
505 , nsRunnable
506 , nsIStreamListener
507 , nsIInterfaceRequestor
508 , nsIChannelEventSink
509 )
511 AsyncFetchAndSetIconFromNetwork::AsyncFetchAndSetIconFromNetwork(
512 IconData& aIcon
513 , PageData& aPage
514 , bool aFaviconLoadPrivate
515 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
516 )
517 : AsyncFaviconHelperBase(aCallback)
518 , mIcon(aIcon)
519 , mPage(aPage)
520 , mFaviconLoadPrivate(aFaviconLoadPrivate)
521 {
522 }
524 AsyncFetchAndSetIconFromNetwork::~AsyncFetchAndSetIconFromNetwork()
525 {
526 }
528 NS_IMETHODIMP
529 AsyncFetchAndSetIconFromNetwork::Run()
530 {
531 NS_PRECONDITION(NS_IsMainThread(),
532 "This should be called on the main thread");
534 // Ensure data is cleared, since it's going to be overwritten.
535 if (mIcon.data.Length() > 0) {
536 mIcon.data.Truncate(0);
537 mIcon.mimeType.Truncate(0);
538 }
540 nsCOMPtr<nsIURI> iconURI;
541 nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
542 NS_ENSURE_SUCCESS(rv, rv);
543 nsCOMPtr<nsIChannel> channel;
544 rv = NS_NewChannel(getter_AddRefs(channel), iconURI);
545 NS_ENSURE_SUCCESS(rv, rv);
546 nsCOMPtr<nsIInterfaceRequestor> listenerRequestor =
547 do_QueryInterface(reinterpret_cast<nsISupports*>(this));
548 NS_ENSURE_STATE(listenerRequestor);
549 rv = channel->SetNotificationCallbacks(listenerRequestor);
550 NS_ENSURE_SUCCESS(rv, rv);
551 nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
552 if (pbChannel) {
553 rv = pbChannel->SetPrivate(mFaviconLoadPrivate);
554 NS_ENSURE_SUCCESS(rv, rv);
555 }
557 nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(channel);
558 if (priorityChannel) {
559 priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
560 }
562 return channel->AsyncOpen(this, nullptr);
563 }
565 NS_IMETHODIMP
566 AsyncFetchAndSetIconFromNetwork::OnStartRequest(nsIRequest* aRequest,
567 nsISupports* aContext)
568 {
569 return NS_OK;
570 }
572 NS_IMETHODIMP
573 AsyncFetchAndSetIconFromNetwork::OnDataAvailable(nsIRequest* aRequest,
574 nsISupports* aContext,
575 nsIInputStream* aInputStream,
576 uint64_t aOffset,
577 uint32_t aCount)
578 {
579 nsAutoCString buffer;
580 nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
581 if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) {
582 return rv;
583 }
585 mIcon.data.Append(buffer);
586 return NS_OK;
587 }
590 NS_IMETHODIMP
591 AsyncFetchAndSetIconFromNetwork::GetInterface(const nsIID& uuid,
592 void** aResult)
593 {
594 return QueryInterface(uuid, aResult);
595 }
598 NS_IMETHODIMP
599 AsyncFetchAndSetIconFromNetwork::AsyncOnChannelRedirect(
600 nsIChannel* oldChannel
601 , nsIChannel* newChannel
602 , uint32_t flags
603 , nsIAsyncVerifyRedirectCallback *cb
604 )
605 {
606 (void)cb->OnRedirectVerifyCallback(NS_OK);
607 return NS_OK;
608 }
610 NS_IMETHODIMP
611 AsyncFetchAndSetIconFromNetwork::OnStopRequest(nsIRequest* aRequest,
612 nsISupports* aContext,
613 nsresult aStatusCode)
614 {
615 MOZ_ASSERT(NS_IsMainThread());
617 nsFaviconService* favicons = nsFaviconService::GetFaviconService();
618 NS_ENSURE_STATE(favicons);
620 nsresult rv;
622 // If fetching the icon failed, add it to the failed cache.
623 if (NS_FAILED(aStatusCode) || mIcon.data.Length() == 0) {
624 nsCOMPtr<nsIURI> iconURI;
625 rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
626 NS_ENSURE_SUCCESS(rv, rv);
627 rv = favicons->AddFailedFavicon(iconURI);
628 NS_ENSURE_SUCCESS(rv, rv);
629 return NS_OK;
630 }
632 NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
633 TO_INTBUFFER(mIcon.data), mIcon.data.Length(),
634 mIcon.mimeType);
636 // If the icon does not have a valid MIME type, add it to the failed cache.
637 if (mIcon.mimeType.IsEmpty()) {
638 nsCOMPtr<nsIURI> iconURI;
639 rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
640 NS_ENSURE_SUCCESS(rv, rv);
641 rv = favicons->AddFailedFavicon(iconURI);
642 NS_ENSURE_SUCCESS(rv, rv);
643 return NS_OK;
644 }
646 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
647 // aRequest should always QI to nsIChannel.
648 // See AsyncFetchAndSetIconFromNetwork::Run()
649 MOZ_ASSERT(channel);
650 mIcon.expiration = GetExpirationTimeFromChannel(channel);
652 rv = OptimizeIconSize(mIcon, favicons);
653 NS_ENSURE_SUCCESS(rv, rv);
655 // If over the maximum size allowed, don't save data to the database to
656 // avoid bloating it.
657 if (mIcon.data.Length() > MAX_FAVICON_SIZE) {
658 return NS_OK;
659 }
661 mIcon.status = ICON_STATUS_CHANGED;
663 nsRefPtr<AsyncAssociateIconToPage> event =
664 new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
665 mDB->DispatchToAsyncThread(event);
667 return NS_OK;
668 }
670 ////////////////////////////////////////////////////////////////////////////////
671 //// AsyncAssociateIconToPage
673 AsyncAssociateIconToPage::AsyncAssociateIconToPage(
674 IconData& aIcon
675 , PageData& aPage
676 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
677 ) : AsyncFaviconHelperBase(aCallback)
678 , mIcon(aIcon)
679 , mPage(aPage)
680 {
681 }
683 AsyncAssociateIconToPage::~AsyncAssociateIconToPage()
684 {
685 }
687 NS_IMETHODIMP
688 AsyncAssociateIconToPage::Run()
689 {
690 NS_PRECONDITION(!NS_IsMainThread(),
691 "This should not be called on the main thread");
693 nsresult rv = FetchPageInfo(mDB, mPage);
694 if (rv == NS_ERROR_NOT_AVAILABLE){
695 // We have never seen this page. If we can add the page to history,
696 // we will try to do it later, otherwise just bail out.
697 if (!mPage.canAddToHistory) {
698 return NS_OK;
699 }
700 }
701 else {
702 NS_ENSURE_SUCCESS(rv, rv);
703 }
705 mozStorageTransaction transaction(mDB->MainConn(), false,
706 mozIStorageConnection::TRANSACTION_IMMEDIATE);
708 // If there is no entry for this icon, or the entry is obsolete, replace it.
709 if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) {
710 rv = SetIconInfo(mDB, mIcon);
711 NS_ENSURE_SUCCESS(rv, rv);
713 // Get the new icon id. Do this regardless mIcon.id, since other code
714 // could have added a entry before us. Indeed we interrupted the thread
715 // after the previous call to FetchIconInfo.
716 mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED;
717 rv = FetchIconInfo(mDB, mIcon);
718 NS_ENSURE_SUCCESS(rv, rv);
719 }
721 // If the page does not have an id, don't try to insert a new one, cause we
722 // don't know where the page comes from. Not doing so we may end adding
723 // a page that otherwise we'd explicitly ignore, like a POST or an error page.
724 if (mPage.id == 0) {
725 return NS_OK;
726 }
728 // Otherwise just associate the icon to the page, if needed.
729 if (mPage.iconId != mIcon.id) {
730 nsCOMPtr<mozIStorageStatement> stmt;
731 if (mPage.id) {
732 stmt = mDB->GetStatement(
733 "UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id"
734 );
735 NS_ENSURE_STATE(stmt);
736 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
737 NS_ENSURE_SUCCESS(rv, rv);
738 }
739 else {
740 stmt = mDB->GetStatement(
741 "UPDATE moz_places SET favicon_id = :icon_id WHERE url = :page_url"
742 );
743 NS_ENSURE_STATE(stmt);
744 rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
745 NS_ENSURE_SUCCESS(rv, rv);
746 }
747 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), mIcon.id);
748 NS_ENSURE_SUCCESS(rv, rv);
750 mozStorageStatementScoper scoper(stmt);
751 rv = stmt->Execute();
752 NS_ENSURE_SUCCESS(rv, rv);
754 mIcon.status |= ICON_STATUS_ASSOCIATED;
755 }
757 rv = transaction.Commit();
758 NS_ENSURE_SUCCESS(rv, rv);
760 // Finally, dispatch an event to the main thread to notify observers.
761 nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(mIcon, mPage, mCallback);
762 rv = NS_DispatchToMainThread(event);
763 NS_ENSURE_SUCCESS(rv, rv);
765 return NS_OK;
766 }
768 ////////////////////////////////////////////////////////////////////////////////
769 //// AsyncGetFaviconURLForPage
771 // static
772 nsresult
773 AsyncGetFaviconURLForPage::start(nsIURI* aPageURI,
774 nsIFaviconDataCallback* aCallback)
775 {
776 NS_ENSURE_ARG(aCallback);
777 NS_ENSURE_ARG(aPageURI);
778 NS_PRECONDITION(NS_IsMainThread(),
779 "This should be called on the main thread.");
781 nsAutoCString pageSpec;
782 nsresult rv = aPageURI->GetSpec(pageSpec);
783 NS_ENSURE_SUCCESS(rv, rv);
785 nsCOMPtr<nsIFaviconDataCallback> callback = aCallback;
786 nsRefPtr<AsyncGetFaviconURLForPage> event =
787 new AsyncGetFaviconURLForPage(pageSpec, callback);
789 nsRefPtr<Database> DB = Database::GetDatabase();
790 NS_ENSURE_STATE(DB);
791 DB->DispatchToAsyncThread(event);
793 return NS_OK;
794 }
796 AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
797 const nsACString& aPageSpec
798 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
799 ) : AsyncFaviconHelperBase(aCallback)
800 {
801 mPageSpec.Assign(aPageSpec);
802 }
804 AsyncGetFaviconURLForPage::~AsyncGetFaviconURLForPage()
805 {
806 }
808 NS_IMETHODIMP
809 AsyncGetFaviconURLForPage::Run()
810 {
811 NS_PRECONDITION(!NS_IsMainThread(),
812 "This should not be called on the main thread.");
814 nsAutoCString iconSpec;
815 nsresult rv = FetchIconURL(mDB, mPageSpec, iconSpec);
816 NS_ENSURE_SUCCESS(rv, rv);
818 // Now notify our callback of the icon spec we retrieved, even if empty.
819 IconData iconData;
820 iconData.spec.Assign(iconSpec);
822 PageData pageData;
823 pageData.spec.Assign(mPageSpec);
825 nsCOMPtr<nsIRunnable> event =
826 new NotifyIconObservers(iconData, pageData, mCallback);
827 rv = NS_DispatchToMainThread(event);
828 NS_ENSURE_SUCCESS(rv, rv);
830 return NS_OK;
831 }
833 ////////////////////////////////////////////////////////////////////////////////
834 //// AsyncGetFaviconDataForPage
836 // static
837 nsresult
838 AsyncGetFaviconDataForPage::start(nsIURI* aPageURI,
839 nsIFaviconDataCallback* aCallback)
840 {
841 NS_ENSURE_ARG(aCallback);
842 NS_ENSURE_ARG(aPageURI);
843 NS_PRECONDITION(NS_IsMainThread(),
844 "This should be called on the main thread.");
846 nsAutoCString pageSpec;
847 nsresult rv = aPageURI->GetSpec(pageSpec);
848 NS_ENSURE_SUCCESS(rv, rv);
850 nsCOMPtr<nsIFaviconDataCallback> callback = aCallback;
851 nsRefPtr<AsyncGetFaviconDataForPage> event =
852 new AsyncGetFaviconDataForPage(pageSpec, callback);
854 nsRefPtr<Database> DB = Database::GetDatabase();
855 NS_ENSURE_STATE(DB);
856 DB->DispatchToAsyncThread(event);
858 return NS_OK;
859 }
861 AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage(
862 const nsACString& aPageSpec
863 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
864 ) : AsyncFaviconHelperBase(aCallback)
865 {
866 mPageSpec.Assign(aPageSpec);
867 }
869 AsyncGetFaviconDataForPage::~AsyncGetFaviconDataForPage()
870 {
871 }
873 NS_IMETHODIMP
874 AsyncGetFaviconDataForPage::Run()
875 {
876 NS_PRECONDITION(!NS_IsMainThread(),
877 "This should not be called on the main thread.");
879 nsAutoCString iconSpec;
880 nsresult rv = FetchIconURL(mDB, mPageSpec, iconSpec);
881 NS_ENSURE_SUCCESS(rv, rv);
883 IconData iconData;
884 iconData.spec.Assign(iconSpec);
886 PageData pageData;
887 pageData.spec.Assign(mPageSpec);
889 if (!iconSpec.IsEmpty()) {
890 rv = FetchIconInfo(mDB, iconData);
891 if (NS_FAILED(rv)) {
892 iconData.spec.Truncate();
893 }
894 }
896 nsCOMPtr<nsIRunnable> event =
897 new NotifyIconObservers(iconData, pageData, mCallback);
898 rv = NS_DispatchToMainThread(event);
899 NS_ENSURE_SUCCESS(rv, rv);
900 return NS_OK;
901 }
903 ////////////////////////////////////////////////////////////////////////////////
904 //// AsyncReplaceFaviconData
906 // static
907 nsresult
908 AsyncReplaceFaviconData::start(IconData *aIcon)
909 {
910 NS_ENSURE_ARG(aIcon);
911 NS_PRECONDITION(NS_IsMainThread(),
912 "This should be called on the main thread.");
914 nsCOMPtr<nsIFaviconDataCallback> callback;
915 nsRefPtr<AsyncReplaceFaviconData> event =
916 new AsyncReplaceFaviconData(*aIcon, callback);
918 nsRefPtr<Database> DB = Database::GetDatabase();
919 NS_ENSURE_STATE(DB);
920 DB->DispatchToAsyncThread(event);
922 return NS_OK;
923 }
925 AsyncReplaceFaviconData::AsyncReplaceFaviconData(
926 IconData &aIcon
927 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
928 ) : AsyncFaviconHelperBase(aCallback)
929 , mIcon(aIcon)
930 {
931 }
933 AsyncReplaceFaviconData::~AsyncReplaceFaviconData()
934 {
935 }
937 NS_IMETHODIMP
938 AsyncReplaceFaviconData::Run()
939 {
940 NS_PRECONDITION(!NS_IsMainThread(),
941 "This should not be called on the main thread");
943 IconData dbIcon;
944 dbIcon.spec.Assign(mIcon.spec);
945 nsresult rv = FetchIconInfo(mDB, dbIcon);
946 NS_ENSURE_SUCCESS(rv, rv);
948 if (!dbIcon.id) {
949 return NS_OK;
950 }
952 rv = SetIconInfo(mDB, mIcon);
953 NS_ENSURE_SUCCESS(rv, rv);
955 // We can invalidate the cache version since we now persist the icon.
956 nsCOMPtr<nsIRunnable> event = new RemoveIconDataCacheEntry(mIcon, mCallback);
957 rv = NS_DispatchToMainThread(event);
958 NS_ENSURE_SUCCESS(rv, rv);
960 return NS_OK;
961 }
964 ////////////////////////////////////////////////////////////////////////////////
965 //// RemoveIconDataCacheEntry
967 RemoveIconDataCacheEntry::RemoveIconDataCacheEntry(
968 IconData& aIcon
969 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
970 )
971 : AsyncFaviconHelperBase(aCallback)
972 , mIcon(aIcon)
973 {
974 }
976 RemoveIconDataCacheEntry::~RemoveIconDataCacheEntry()
977 {
978 }
980 NS_IMETHODIMP
981 RemoveIconDataCacheEntry::Run()
982 {
983 NS_PRECONDITION(NS_IsMainThread(),
984 "This should be called on the main thread");
986 nsCOMPtr<nsIURI> iconURI;
987 nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
988 NS_ENSURE_SUCCESS(rv, rv);
990 nsFaviconService* favicons = nsFaviconService::GetFaviconService();
991 NS_ENSURE_STATE(favicons);
992 favicons->mUnassociatedIcons.RemoveEntry(iconURI);
994 return NS_OK;
995 }
998 ////////////////////////////////////////////////////////////////////////////////
999 //// NotifyIconObservers
1001 NotifyIconObservers::NotifyIconObservers(
1002 IconData& aIcon
1003 , PageData& aPage
1004 , nsCOMPtr<nsIFaviconDataCallback>& aCallback
1005 )
1006 : AsyncFaviconHelperBase(aCallback)
1007 , mIcon(aIcon)
1008 , mPage(aPage)
1009 {
1010 }
1012 NotifyIconObservers::~NotifyIconObservers()
1013 {
1014 }
1016 NS_IMETHODIMP
1017 NotifyIconObservers::Run()
1018 {
1019 NS_PRECONDITION(NS_IsMainThread(),
1020 "This should be called on the main thread");
1022 nsCOMPtr<nsIURI> iconURI;
1023 if (!mIcon.spec.IsEmpty()) {
1024 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(iconURI), mIcon.spec)));
1025 if (iconURI)
1026 {
1027 // Notify observers only if something changed.
1028 if (mIcon.status & ICON_STATUS_SAVED ||
1029 mIcon.status & ICON_STATUS_ASSOCIATED) {
1030 SendGlobalNotifications(iconURI);
1031 }
1032 }
1033 }
1035 if (mCallback) {
1036 (void)mCallback->OnComplete(iconURI, mIcon.data.Length(),
1037 TO_INTBUFFER(mIcon.data), mIcon.mimeType);
1038 }
1040 return NS_OK;
1041 }
1043 void
1044 NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI)
1045 {
1046 nsCOMPtr<nsIURI> pageURI;
1047 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pageURI), mPage.spec)));
1048 if (pageURI) {
1049 nsFaviconService* favicons = nsFaviconService::GetFaviconService();
1050 MOZ_ASSERT(favicons);
1051 if (favicons) {
1052 (void)favicons->SendFaviconNotifications(pageURI, aIconURI, mPage.guid);
1053 }
1054 }
1056 // If the page is bookmarked and the bookmarked url is different from the
1057 // updated one, start a new task to update its icon as well.
1058 if (!mPage.bookmarkedSpec.IsEmpty() &&
1059 !mPage.bookmarkedSpec.Equals(mPage.spec)) {
1060 // Create a new page struct to avoid polluting it with old data.
1061 PageData bookmarkedPage;
1062 bookmarkedPage.spec = mPage.bookmarkedSpec;
1064 // This will be silent, so be sure to not pass in the current callback.
1065 nsCOMPtr<nsIFaviconDataCallback> nullCallback;
1066 nsRefPtr<AsyncAssociateIconToPage> event =
1067 new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
1068 mDB->DispatchToAsyncThread(event);
1069 }
1070 }
1072 } // namespace places
1073 } // namespace mozilla