summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/nsFaviconService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/nsFaviconService.cpp')
-rw-r--r--toolkit/components/places/nsFaviconService.cpp716
1 files changed, 716 insertions, 0 deletions
diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp
new file mode 100644
index 000000000..42526b285
--- /dev/null
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the favicon service, which stores favicons for web pages with your
+ * history as you browse. It is also used to save the favicons for bookmarks.
+ *
+ * DANGER: The history query system makes assumptions about the favicon storage
+ * so that icons can be quickly generated for history/bookmark result sets. If
+ * you change the database layout at all, you will have to update both services.
+ */
+
+#include "nsFaviconService.h"
+
+#include "nsNavHistory.h"
+#include "nsPlacesMacros.h"
+#include "Helpers.h"
+
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "plbase64.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Preferences.h"
+#include "nsILoadInfo.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "nsNullPrincipal.h"
+
+// For large favicons optimization.
+#include "imgITools.h"
+#include "imgIContainer.h"
+
+// The target dimension, in pixels, for favicons we optimize.
+#define OPTIMIZED_FAVICON_DIMENSION 32
+
+#define MAX_FAILED_FAVICONS 256
+#define FAVICON_CACHE_REDUCE_COUNT 64
+
+#define UNASSOCIATED_FAVICONS_LENGTH 32
+
+// When replaceFaviconData is called, we store the icons in an in-memory cache
+// instead of in storage. Icons in the cache are expired according to this
+// interval.
+#define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
+
+// The MIME type of the default favicon and favicons created by
+// OptimizeFaviconImage.
+#define DEFAULT_MIME_TYPE "image/png"
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+/**
+ * Used to notify a topic to system observers on async execute completion.
+ * Will throw on error.
+ */
+class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
+{
+public:
+ ExpireFaviconsStatementCallbackNotifier();
+ NS_IMETHOD HandleCompletion(uint16_t aReason);
+};
+
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
+
+NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
+NS_IMPL_ISUPPORTS_CI(
+ nsFaviconService
+, nsIFaviconService
+, mozIAsyncFavicons
+, nsITimerCallback
+)
+
+nsFaviconService::nsFaviconService()
+ : mFailedFaviconSerial(0)
+ , mFailedFavicons(MAX_FAILED_FAVICONS / 2)
+ , mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH)
+{
+ NS_ASSERTION(!gFaviconService,
+ "Attempting to create two instances of the service!");
+ gFaviconService = this;
+}
+
+
+nsFaviconService::~nsFaviconService()
+{
+ NS_ASSERTION(gFaviconService == this,
+ "Deleting a non-singleton instance of the service");
+ if (gFaviconService == this)
+ gFaviconService = nullptr;
+}
+
+
+nsresult
+nsFaviconService::Init()
+{
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ mExpireUnassociatedIconsTimer = do_CreateInstance("@mozilla.org/timer;1");
+ NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ExpireAllFavicons()
+{
+ nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
+ "UPDATE moz_places "
+ "SET favicon_id = NULL "
+ "WHERE favicon_id NOT NULL"
+ );
+ NS_ENSURE_STATE(unlinkIconsStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_favicons WHERE id NOT IN ("
+ "SELECT favicon_id FROM moz_places WHERE favicon_id NOT NULL "
+ ")"
+ );
+ NS_ENSURE_STATE(removeIconsStmt);
+
+ mozIStorageBaseStatement* stmts[] = {
+ unlinkIconsStmt.get()
+ , removeIconsStmt.get()
+ };
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
+ new ExpireFaviconsStatementCallbackNotifier();
+ nsresult rv = mDB->MainConn()->ExecuteAsync(
+ stmts, ArrayLength(stmts), callback, getter_AddRefs(ps)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsITimerCallback
+
+NS_IMETHODIMP
+nsFaviconService::Notify(nsITimer* timer)
+{
+ if (timer != mExpireUnassociatedIconsTimer.get()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRTime now = PR_Now();
+ for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
+ UnassociatedIconHashKey* iconKey = iter.Get();
+ if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
+ iter.Remove();
+ }
+ }
+
+ // Re-init the expiry timer if the cache isn't empty.
+ if (mUnassociatedIcons.Count() > 0) {
+ mExpireUnassociatedIconsTimer->InitWithCallback(
+ this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIFaviconService
+
+NS_IMETHODIMP
+nsFaviconService::GetDefaultFavicon(nsIURI** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // not found, use default
+ if (!mDefaultIcon) {
+ nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
+ NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return mDefaultIcon->Clone(_retval);
+}
+
+void
+nsFaviconService::SendFaviconNotifications(nsIURI* aPageURI,
+ nsIURI* aFaviconURI,
+ const nsACString& aGUID)
+{
+ nsAutoCString faviconSpec;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ if (history && NS_SUCCEEDED(aFaviconURI->GetSpec(faviconSpec))) {
+ history->SendPageChangedNotification(aPageURI,
+ nsINavHistoryObserver::ATTRIBUTE_FAVICON,
+ NS_ConvertUTF8toUTF16(faviconSpec),
+ aGUID);
+ }
+}
+
+NS_IMETHODIMP
+nsFaviconService::SetAndFetchFaviconForPage(nsIURI* aPageURI,
+ nsIURI* aFaviconURI,
+ bool aForceReload,
+ uint32_t aFaviconLoadType,
+ nsIFaviconDataCallback* aCallback,
+ nsIPrincipal* aLoadingPrincipal,
+ mozIPlacesPendingOperation **_canceler)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG_POINTER(_canceler);
+
+ // If a favicon is in the failed cache, only load it during a forced reload.
+ bool previouslyFailed;
+ nsresult rv = IsFailedFavicon(aFaviconURI, &previouslyFailed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (previouslyFailed) {
+ if (aForceReload)
+ RemoveFailedFavicon(aFaviconURI);
+ else
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
+ MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
+ if (!loadingPrincipal) {
+ // Let's default to the nullPrincipal if no loadingPrincipal is provided.
+ const char16_t* params[] = {
+ u"nsFaviconService::setAndFetchFaviconForPage()",
+ u"nsFaviconService::setAndFetchFaviconForPage(..., [optional aLoadingPrincipal])"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+ loadingPrincipal = nsNullPrincipal::Create();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ // Check if the icon already exists and fetch it from the network, if needed.
+ // Finally associate the icon to the requested page if not yet associated.
+ bool loadPrivate = aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
+
+ PageData page;
+ rv = aPageURI->GetSpec(page.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // URIs can arguably miss a host.
+ (void)GetReversedHostname(aPageURI, page.revHost);
+ bool canAddToHistory;
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+ rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ page.canAddToHistory = !!canAddToHistory && !loadPrivate;
+
+ IconData icon;
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
+ if (iconKey) {
+ icon = iconKey->iconData;
+ mUnassociatedIcons.RemoveEntry(iconKey);
+ } else {
+ icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
+ rv = aFaviconURI->GetSpec(icon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the page url points to an image, the icon's url will be the same.
+ // In future evaluate to store a resample of the image. For now avoid that
+ // for database size concerns.
+ // Don't store favicons for error pages too.
+ if (icon.spec.Equals(page.spec) ||
+ icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
+ return NS_OK;
+ }
+
+ RefPtr<AsyncFetchAndSetIconForPage> event =
+ new AsyncFetchAndSetIconForPage(icon, page, loadPrivate,
+ aCallback, aLoadingPrincipal);
+
+ // Get the target thread and start the work.
+ // DB will be updated and observers notified when data has finished loading.
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ // Return this event to the caller to allow aborting an eventual fetch.
+ event.forget(_canceler);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
+ const uint8_t* aData,
+ uint32_t aDataLen,
+ const nsACString& aMimeType,
+ PRTime aExpiration)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_TRUE(aDataLen > 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMimeType.Length() > 0, NS_ERROR_INVALID_ARG);
+ if (aExpiration == 0) {
+ aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
+ }
+
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
+ if (!iconKey) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ iconKey->created = PR_Now();
+
+ // If the cache contains unassociated icons, an expiry timer should already exist, otherwise
+ // there may be a timer left hanging around, so make sure we fire a new one.
+ int32_t unassociatedCount = mUnassociatedIcons.Count();
+ if (unassociatedCount == 1) {
+ mExpireUnassociatedIconsTimer->Cancel();
+ mExpireUnassociatedIconsTimer->InitWithCallback(
+ this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ IconData* iconData = &(iconKey->iconData);
+ iconData->expiration = aExpiration;
+ iconData->status = ICON_STATUS_CACHED;
+ iconData->fetchMode = FETCH_NEVER;
+ nsresult rv = aFaviconURI->GetSpec(iconData->spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the page provided a large image for the favicon (eg, a highres image
+ // or a multiresolution .ico file), we don't want to store more data than
+ // needed.
+ if (aDataLen > MAX_FAVICON_FILESIZE) {
+ rv = OptimizeFaviconImage(aData, aDataLen, aMimeType, iconData->data, iconData->mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iconData->data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ // We cannot optimize this favicon size and we are over the maximum size
+ // allowed, so we will not save data to the db to avoid bloating it.
+ mUnassociatedIcons.RemoveEntry(aFaviconURI);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ iconData->mimeType.Assign(aMimeType);
+ iconData->data.Assign(TO_CHARBUFFER(aData), aDataLen);
+ }
+
+ // If the database contains an icon at the given url, we will update the
+ // database immediately so that the associated pages are kept in sync.
+ // Otherwise, do nothing and let the icon be picked up from the memory hash.
+ RefPtr<AsyncReplaceFaviconData> event = new AsyncReplaceFaviconData(*iconData);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ReplaceFaviconDataFromDataURL(nsIURI* aFaviconURI,
+ const nsAString& aDataURL,
+ PRTime aExpiration,
+ nsIPrincipal* aLoadingPrincipal)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
+ if (aExpiration == 0) {
+ aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
+ }
+
+ nsCOMPtr<nsIURI> dataURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use the data: protocol handler to convert the data.
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIProtocolHandler> protocolHandler;
+ rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
+ MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
+ if (!loadingPrincipal) {
+ // Let's default to the nullPrincipal if no loadingPrincipal is provided.
+ const char16_t* params[] = {
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL()",
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL(..., [optional aLoadingPrincipal])"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ loadingPrincipal = nsNullPrincipal::Create();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(loadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ nullptr, // aLoadingNode
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME |
+ nsILoadInfo::SEC_DISALLOW_SCRIPT,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = protocolHandler->NewChannel2(dataURI, loadInfo, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Blocking stream is OK for data URIs.
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t available64;
+ rv = stream->Available(&available64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t))
+ return NS_ERROR_FILE_TOO_BIG;
+ uint32_t available = (uint32_t)available64;
+
+ // Read all the decoded data.
+ uint8_t* buffer = static_cast<uint8_t*>
+ (moz_xmalloc(sizeof(uint8_t) * available));
+ if (!buffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+ uint32_t numRead;
+ rv = stream->Read(TO_CHARBUFFER(buffer), available, &numRead);
+ if (NS_FAILED(rv) || numRead != available) {
+ free(buffer);
+ return rv;
+ }
+
+ nsAutoCString mimeType;
+ rv = channel->GetContentType(mimeType);
+ if (NS_FAILED(rv)) {
+ free(buffer);
+ return rv;
+ }
+
+ // ReplaceFaviconData can now do the dirty work.
+ rv = ReplaceFaviconData(aFaviconURI, buffer, available, mimeType, aExpiration);
+ free(buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconURLForPage(nsIURI *aPageURI,
+ nsIFaviconDataCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<AsyncGetFaviconURLForPage> event =
+ new AsyncGetFaviconURLForPage(pageSpec, aCallback);
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
+ nsIFaviconDataCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<AsyncGetFaviconDataForPage> event =
+ new AsyncGetFaviconDataForPage(pageSpec, aCallback);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+nsresult
+nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
+ nsIURI** aOutputURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG_POINTER(aOutputURI);
+
+ nsAutoCString spec;
+ if (aFaviconURI) {
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return GetFaviconLinkForIconString(spec, aOutputURI);
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::AddFailedFavicon(nsIURI* aFaviconURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFailedFavicons.Put(spec, mFailedFaviconSerial);
+ mFailedFaviconSerial ++;
+
+ if (mFailedFavicons.Count() > MAX_FAILED_FAVICONS) {
+ // need to expire some entries, delete the FAVICON_CACHE_REDUCE_COUNT number
+ // of items that are the oldest
+ uint32_t threshold = mFailedFaviconSerial -
+ MAX_FAILED_FAVICONS + FAVICON_CACHE_REDUCE_COUNT;
+ for (auto iter = mFailedFavicons.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < threshold) {
+ iter.Remove();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::RemoveFailedFavicon(nsIURI* aFaviconURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we silently do nothing and succeed if the icon is not in the cache
+ mFailedFavicons.Remove(spec);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::IsFailedFavicon(nsIURI* aFaviconURI, bool* _retval)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t serial;
+ *_retval = mFailedFavicons.Get(spec, &serial);
+ return NS_OK;
+}
+
+
+// nsFaviconService::GetFaviconLinkForIconString
+//
+// This computes a favicon URL with string input and using the cached
+// default one to minimize parsing.
+
+nsresult
+nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
+ nsIURI** aOutput)
+{
+ if (aSpec.IsEmpty()) {
+ // default icon for empty strings
+ if (! mDefaultIcon) {
+ nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
+ NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return mDefaultIcon->Clone(aOutput);
+ }
+
+ if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
+ // pass through for chrome URLs, since they can be referenced without
+ // this service
+ return NS_NewURI(aOutput, aSpec);
+ }
+
+ nsAutoCString annoUri;
+ annoUri.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
+ annoUri += aSpec;
+ return NS_NewURI(aOutput, annoUri);
+}
+
+
+// nsFaviconService::GetFaviconSpecForIconString
+//
+// This computes a favicon spec for when you don't want a URI object (as in
+// the tree view implementation), sparing all parsing and normalization.
+void
+nsFaviconService::GetFaviconSpecForIconString(const nsCString& aSpec,
+ nsACString& aOutput)
+{
+ if (aSpec.IsEmpty()) {
+ aOutput.AssignLiteral(FAVICON_DEFAULT_URL);
+ } else if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
+ aOutput = aSpec;
+ } else {
+ aOutput.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
+ aOutput += aSpec;
+ }
+}
+
+
+// nsFaviconService::OptimizeFaviconImage
+//
+// Given a blob of data (a image file already read into a buffer), optimize
+// its size by recompressing it as a 16x16 PNG.
+nsresult
+nsFaviconService::OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
+ const nsACString& aMimeType,
+ nsACString& aNewData,
+ nsACString& aNewMimeType)
+{
+ nsresult rv;
+
+ nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ reinterpret_cast<const char*>(aData), aDataLen,
+ NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // decode image
+ nsCOMPtr<imgIContainer> container;
+ rv = imgtool->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNewMimeType.AssignLiteral(DEFAULT_MIME_TYPE);
+
+ // scale and recompress
+ nsCOMPtr<nsIInputStream> iconStream;
+ rv = imgtool->EncodeScaledImage(container, aNewMimeType,
+ OPTIMIZED_FAVICON_DIMENSION,
+ OPTIMIZED_FAVICON_DIMENSION,
+ EmptyString(),
+ getter_AddRefs(iconStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read the stream into a new buffer.
+ rv = NS_ConsumeStream(iconStream, UINT32_MAX, aNewData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsFaviconService::GetFaviconDataAsync(nsIURI* aFaviconURI,
+ mozIStorageStatementCallback *aCallback)
+{
+ NS_ASSERTION(aCallback, "Doesn't make sense to call this without a callback");
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "SELECT f.data, f.mime_type FROM moz_favicons f WHERE url = :icon_url"
+ );
+ NS_ENSURE_STATE(stmt);
+
+ // Ignore the ref part of the URI before querying the database because
+ // we may have added a media fragment for rendering purposes.
+
+ nsAutoCString faviconURI;
+ aFaviconURI->GetSpecIgnoringRef(faviconURI);
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), faviconURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
+ return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// ExpireFaviconsStatementCallbackNotifier
+
+ExpireFaviconsStatementCallbackNotifier::ExpireFaviconsStatementCallbackNotifier()
+{
+}
+
+
+NS_IMETHODIMP
+ExpireFaviconsStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
+{
+ // We should dispatch only if expiration has been successful.
+ if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
+ return NS_OK;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID,
+ nullptr);
+ }
+
+ return NS_OK;
+}