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.cpp837
1 files changed, 837 insertions, 0 deletions
diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp
new file mode 100644
index 0000000000..cf708f136a
--- /dev/null
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -0,0 +1,837 @@
+/* -*- 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 "plbase64.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/Preferences.h"
+#include "nsILoadInfo.h"
+#include "nsIContentPolicy.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "imgICache.h"
+
+#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
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+const uint16_t gFaviconSizes[7] = {192, 144, 96, 64, 48, 32, 16};
+
+/**
+ * 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) override;
+};
+
+namespace {
+
+/**
+ * Extracts and filters native sizes from the given container, based on the
+ * list of sizes we are supposed to retain.
+ * All calculation is done considering square sizes and the largest side.
+ * In case of multiple frames of the same size, only the first one is retained.
+ */
+nsresult GetFramesInfoForContainer(imgIContainer* aContainer,
+ nsTArray<FrameData>& aFramesInfo) {
+ // Don't extract frames from animated images.
+ bool animated;
+ nsresult rv = aContainer->GetAnimated(&animated);
+ if (NS_FAILED(rv) || !animated) {
+ nsTArray<nsIntSize> nativeSizes;
+ rv = aContainer->GetNativeSizes(nativeSizes);
+ if (NS_SUCCEEDED(rv) && nativeSizes.Length() > 1) {
+ for (uint32_t i = 0; i < nativeSizes.Length(); ++i) {
+ nsIntSize nativeSize = nativeSizes[i];
+ // Only retain square frames.
+ if (nativeSize.width != nativeSize.height) {
+ continue;
+ }
+ // Check if it's one of the sizes we care about.
+ auto end = std::end(gFaviconSizes);
+ const uint16_t* matchingSize =
+ std::find(std::begin(gFaviconSizes), end, nativeSize.width);
+ if (matchingSize != end) {
+ // We must avoid duped sizes, an image could contain multiple frames
+ // of the same size, but we can only store one. We could use an
+ // hashtable, but considered the average low number of frames, we'll
+ // just do a linear search.
+ bool dupe = false;
+ for (const auto& frameInfo : aFramesInfo) {
+ if (frameInfo.width == *matchingSize) {
+ dupe = true;
+ break;
+ }
+ }
+ if (!dupe) {
+ aFramesInfo.AppendElement(FrameData(i, *matchingSize));
+ }
+ }
+ }
+ }
+ }
+
+ if (aFramesInfo.Length() == 0) {
+ // Always have at least the default size.
+ int32_t width;
+ rv = aContainer->GetWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t height;
+ rv = aContainer->GetHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // For non-square images, pick the largest side.
+ aFramesInfo.AppendElement(FrameData(0, std::max(width, height)));
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
+
+NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
+NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService, nsITimerCallback,
+ nsINamed)
+
+nsFaviconService::nsFaviconService()
+ : mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH),
+ mDefaultIconURIPreferredSize(UINT16_MAX) {
+ 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;
+}
+
+Atomic<int64_t> nsFaviconService::sLastInsertedIconId(0);
+
+void // static
+nsFaviconService::StoreLastInsertedId(const nsACString& aTable,
+ const int64_t aLastInsertedId) {
+ MOZ_ASSERT(aTable.EqualsLiteral("moz_icons"));
+ sLastInsertedIconId = aLastInsertedId;
+}
+
+nsresult nsFaviconService::Init() {
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ mExpireUnassociatedIconsTimer = NS_NewTimer();
+ NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ExpireAllFavicons() {
+ NS_ENSURE_STATE(mDB);
+
+ nsCOMPtr<mozIStorageAsyncStatement> removePagesStmt =
+ mDB->GetAsyncStatement("DELETE FROM moz_pages_w_icons");
+ NS_ENSURE_STATE(removePagesStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt =
+ mDB->GetAsyncStatement("DELETE FROM moz_icons");
+ NS_ENSURE_STATE(removeIconsStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt =
+ mDB->GetAsyncStatement("DELETE FROM moz_icons_to_pages");
+ NS_ENSURE_STATE(unlinkIconsStmt);
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(removePagesStmt)),
+ ToRefPtr(std::move(removeIconsStmt)),
+ ToRefPtr(std::move(unlinkIconsStmt))};
+ nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
+ if (!conn) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
+ new ExpireFaviconsStatementCallbackNotifier();
+ return conn->ExecuteAsync(stmts, callback, getter_AddRefs(ps));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsINamed
+
+NS_IMETHODIMP
+nsFaviconService::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsFaviconService");
+ 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),
+ nsLiteralCString(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIURI> uri = mDefaultIcon;
+ uri.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetDefaultFaviconMimeType(nsACString& _retval) {
+ _retval = nsLiteralCString(FAVICON_DEFAULT_MIMETYPE);
+ return NS_OK;
+}
+
+void nsFaviconService::ClearImageCache(nsIURI* aImageURI) {
+ MOZ_ASSERT(aImageURI, "Must pass a non-null URI");
+ nsCOMPtr<imgICache> imgCache;
+ nsresult rv =
+ GetImgTools()->GetImgCacheForDocument(nullptr, getter_AddRefs(imgCache));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_SUCCEEDED(rv)) {
+ Unused << imgCache->RemoveEntry(aImageURI, nullptr);
+ }
+}
+
+NS_IMETHODIMP
+nsFaviconService::SetAndFetchFaviconForPage(
+ nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload,
+ uint32_t aFaviconLoadType, nsIFaviconDataCallback* aCallback,
+ nsIPrincipal* aLoadingPrincipal, uint64_t aRequestContextID,
+ mozIPlacesPendingOperation** _canceler) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG_POINTER(_canceler);
+
+ 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.
+ AutoTArray<nsString, 2> params = {
+ u"nsFaviconService::setAndFetchFaviconForPage()"_ns,
+ u"nsFaviconService::setAndFetchFaviconForPage(..., "
+ "[optional aLoadingPrincipal])"_ns};
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Security by Default"_ns,
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
+ loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ bool loadPrivate =
+ aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
+
+ // Build page data.
+ PageData page;
+ nsresult rv = aPageURI->GetSpec(page.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // URIs can arguably lack a host.
+ Unused << aPageURI->GetHost(page.host);
+ if (StringBeginsWith(page.host, "www."_ns)) {
+ page.host.Cut(0, 4);
+ }
+ 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;
+
+ // Build icon data.
+ IconData icon;
+ // If we have an in-memory icon payload, it overwrites the actual request.
+ 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);
+ // URIs can arguably lack a host.
+ Unused << aFaviconURI->GetHost(icon.host);
+ if (StringBeginsWith(icon.host, "www."_ns)) {
+ icon.host.Cut(0, 4);
+ }
+ }
+
+ // A root icon is when the icon and page have the same host and the path
+ // is just /favicon.ico. These icons are considered valid for the whole
+ // origin and expired with the origin through a trigger.
+ nsAutoCString path;
+ if (NS_SUCCEEDED(aFaviconURI->GetPathQueryRef(path)) &&
+ !icon.host.IsEmpty() && icon.host.Equals(page.host) &&
+ path.EqualsLiteral("/favicon.ico")) {
+ icon.rootIcon = 1;
+ }
+
+ // If the page url points to an image, the icon's url will be the same.
+ // TODO (Bug 403651): store a resample of the image. For now avoid that
+ // for database size and UX concerns.
+ // Don't store favicons for error pages either.
+ if (icon.spec.Equals(page.spec) ||
+ icon.spec.EqualsLiteral(FAVICON_CERTERRORPAGE_URL) ||
+ icon.spec.EqualsLiteral(FAVICON_ERRORPAGE_URL)) {
+ return NS_OK;
+ }
+
+ RefPtr<AsyncFetchAndSetIconForPage> event = new AsyncFetchAndSetIconForPage(
+ icon, page, loadPrivate, aCallback, aLoadingPrincipal, aRequestContextID);
+
+ // 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 nsTArray<uint8_t>& aData,
+ const nsACString& aMimeType,
+ PRTime aExpiration) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG(aData.Length() > 0);
+ NS_ENSURE_ARG(aMimeType.Length() > 0);
+ NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(
+ aMimeType, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS));
+
+ PRTime now = PR_Now();
+ if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
+ // Invalid input, just use the default.
+ aExpiration = 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);
+ // URIs can arguably lack a host.
+ Unused << aFaviconURI->GetHost(iconData->host);
+ if (StringBeginsWith(iconData->host, "www."_ns)) {
+ iconData->host.Cut(0, 4);
+ }
+
+ // Note we can't set rootIcon here, because don't know the page it will be
+ // associated with. We'll do that later in SetAndFetchFaviconForPage if the
+ // icon doesn't exist; otherwise, if AsyncReplaceFaviconData updates an
+ // existing icon, it will take care of not overwriting an existing
+ // root = 1 value.
+
+ IconPayload payload;
+ payload.mimeType = aMimeType;
+ payload.data.Assign(TO_CHARBUFFER(aData.Elements()), aData.Length());
+ if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
+ payload.width = UINT16_MAX;
+ }
+ // There may already be a previous payload, so ensure to only have one.
+ iconData->payloads.Clear();
+ iconData->payloads.AppendElement(payload);
+
+ rv = OptimizeIconSizes(*iconData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there's not valid payload, don't store the icon into to the database.
+ if ((*iconData).payloads.Length() == 0) {
+ // 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;
+ }
+
+ // 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);
+ PRTime now = PR_Now();
+ if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
+ // Invalid input, just use the default.
+ aExpiration = 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.
+ AutoTArray<nsString, 2> params = {
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL()"_ns,
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL(...,"
+ " [optional aLoadingPrincipal])"_ns};
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "Security by Default"_ns,
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params);
+
+ loadingPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ loadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ nullptr, // aLoadingNode
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT |
+ nsILoadInfo::SEC_ALLOW_CHROME | nsILoadInfo::SEC_DISALLOW_SCRIPT,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = protocolHandler->NewChannel(dataURI, loadInfo, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Blocking stream is OK for data URIs.
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open(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.
+ nsTArray<uint8_t> buffer;
+ buffer.SetLength(available);
+ uint32_t numRead;
+ rv = stream->Read(TO_CHARBUFFER(buffer.Elements()), available, &numRead);
+ if (NS_FAILED(rv) || numRead != available) {
+ return rv;
+ }
+
+ nsAutoCString mimeType;
+ rv = channel->GetContentType(mimeType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // ReplaceFaviconData can now do the dirty work.
+ rv = ReplaceFaviconData(aFaviconURI, buffer, mimeType, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
+ nsIFaviconDataCallback* aCallback,
+ uint16_t aPreferredWidth) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+ // Use the default value, may be UINT16_MAX if a default is not set.
+ if (aPreferredWidth == 0) {
+ aPreferredWidth = mDefaultIconURIPreferredSize;
+ }
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString pageHost;
+ // It's expected that some domains may not have a host.
+ Unused << aPageURI->GetHost(pageHost);
+
+ RefPtr<AsyncGetFaviconURLForPage> event = new AsyncGetFaviconURLForPage(
+ pageSpec, pageHost, aPreferredWidth, aCallback);
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
+ nsIFaviconDataCallback* aCallback,
+ uint16_t aPreferredWidth) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+ // Use the default value, may be UINT16_MAX if a default is not set.
+ if (aPreferredWidth == 0) {
+ aPreferredWidth = mDefaultIconURIPreferredSize;
+ }
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString pageHost;
+ // It's expected that some domains may not have a host.
+ Unused << aPageURI->GetHost(pageHost);
+
+ RefPtr<AsyncGetFaviconDataForPage> event = new AsyncGetFaviconDataForPage(
+ pageSpec, pageHost, aPreferredWidth, aCallback);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::CopyFavicons(nsIURI* aFromPageURI, nsIURI* aToPageURI,
+ uint32_t aFaviconLoadType,
+ nsIFaviconDataCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aFromPageURI);
+ NS_ENSURE_ARG(aToPageURI);
+ NS_ENSURE_TRUE(
+ aFaviconLoadType >= nsIFaviconService::FAVICON_LOAD_PRIVATE &&
+ aFaviconLoadType <= nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
+ NS_ERROR_INVALID_ARG);
+
+ PageData fromPage;
+ nsresult rv = aFromPageURI->GetSpec(fromPage.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PageData toPage;
+ rv = aToPageURI->GetSpec(toPage.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool canAddToHistory;
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+ rv = navHistory->CanAddURI(aToPageURI, &canAddToHistory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ toPage.canAddToHistory =
+ !!canAddToHistory &&
+ aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE;
+
+ RefPtr<AsyncCopyFavicons> event =
+ new AsyncCopyFavicons(fromPage, toPage, aCallback);
+
+ // Get the target thread and start the work.
+ // DB will be updated and observers notified when done.
+ 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);
+}
+
+// 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()) {
+ return GetDefaultFavicon(aOutput);
+ }
+
+ if (StringBeginsWith(aSpec, "chrome:"_ns)) {
+ // 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);
+}
+
+/**
+ * Checks the icon and evaluates if it needs to be optimized.
+ *
+ * @param aIcon
+ * The icon to be evaluated.
+ */
+nsresult nsFaviconService::OptimizeIconSizes(IconData& aIcon) {
+ // TODO (bug 1346139): move optimization to the async thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ // There should only be a single payload at this point, it may have to be
+ // split though, if it's an ico file.
+ MOZ_ASSERT(aIcon.payloads.Length() == 1);
+
+ // Even if the page provides a large image for the favicon (eg, a highres
+ // image or a multiresolution .ico file), don't try to store more data than
+ // needed.
+ IconPayload payload = aIcon.payloads[0];
+ if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) {
+ // Nothing to optimize, but check the payload size.
+ if (payload.data.Length() >= nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ aIcon.payloads.Clear();
+ }
+ return NS_OK;
+ }
+
+ // Make space for the optimized payloads.
+ aIcon.payloads.Clear();
+
+ // decode image
+ nsCOMPtr<imgIContainer> container;
+ nsresult rv = GetImgTools()->DecodeImageFromBuffer(
+ payload.data.get(), payload.data.Length(), payload.mimeType,
+ getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For ICO files, we must evaluate each of the frames we care about.
+ nsTArray<FrameData> framesInfo;
+ rv = GetFramesInfoForContainer(container, framesInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& frameInfo : framesInfo) {
+ IconPayload newPayload;
+ newPayload.mimeType = nsLiteralCString(PNG_MIME_TYPE);
+ newPayload.width = frameInfo.width;
+ for (uint16_t size : gFaviconSizes) {
+ // The icon could be smaller than 16, that is our minimum.
+ // Icons smaller than 16px are kept as-is.
+ if (frameInfo.width >= 16) {
+ if (size > frameInfo.width) {
+ continue;
+ }
+ newPayload.width = size;
+ }
+
+ // If the original payload is png, the size is the same and not animated,
+ // rescale the image only if it's larger than the maximum allowed.
+ bool animated;
+ if (newPayload.mimeType.Equals(payload.mimeType) &&
+ newPayload.width == frameInfo.width &&
+ payload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE &&
+ (NS_FAILED(container->GetAnimated(&animated)) || !animated)) {
+ newPayload.data = payload.data;
+ break;
+ }
+
+ // Otherwise, scale and recompress. Rescaling will also take care of
+ // extracting a static image from an animated one.
+ // Since EncodeScaledImage uses SYNC_DECODE, it will pick the best
+ // frame.
+ nsCOMPtr<nsIInputStream> iconStream;
+ rv = GetImgTools()->EncodeScaledImage(container, newPayload.mimeType,
+ newPayload.width, newPayload.width,
+ u""_ns, getter_AddRefs(iconStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Read the stream into the new buffer.
+ rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the icon size is good, we are done, otherwise try the next size.
+ if (newPayload.data.Length() <
+ nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ break;
+ }
+ }
+
+ MOZ_ASSERT(newPayload.data.Length() <
+ nsIFaviconService::MAX_FAVICON_BUFFER_SIZE);
+ if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ aIcon.payloads.AppendElement(newPayload);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFaviconService::GetFaviconDataAsync(
+ const nsCString& aFaviconURI, mozIStorageStatementCallback* aCallback) {
+ MOZ_ASSERT(aCallback, "Doesn't make sense to call this without a callback");
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "/*Do not warn (bug no: not worth adding an index */ "
+ "SELECT data, width FROM moz_icons "
+ "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) AND icon_url = :url "
+ "ORDER BY width DESC");
+ NS_ENSURE_STATE(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, "url"_ns, aFaviconURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
+ return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
+}
+
+NS_IMETHODIMP
+nsFaviconService::SetDefaultIconURIPreferredSize(uint16_t aDefaultSize) {
+ mDefaultIconURIPreferredSize = aDefaultSize > 0 ? aDefaultSize : UINT16_MAX;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::PreferredSizeFromURI(nsIURI* aURI, uint16_t* _size) {
+ NS_ENSURE_ARG(aURI);
+ *_size = mDefaultIconURIPreferredSize;
+ nsAutoCString ref;
+ // Check for a ref first.
+ if (NS_FAILED(aURI->GetRef(ref)) || ref.Length() == 0) return NS_OK;
+
+ // Look for a "size=" fragment.
+ int32_t start = ref.RFind("size=");
+ if (start >= 0 && ref.Length() > static_cast<uint32_t>(start) + 5) {
+ nsDependentCSubstring size;
+ // This is safe regardless, since Rebind checks start is not over Length().
+ size.Rebind(ref, start + 5);
+ // Check if the string contains any non-digit.
+ auto begin = size.BeginReading(), end = size.EndReading();
+ for (auto ch = begin; ch < end; ++ch) {
+ if (*ch < '0' || *ch > '9') {
+ // Not a digit.
+ return NS_OK;
+ }
+ }
+ // Convert the string to an integer value.
+ nsresult rv;
+ uint16_t val = PromiseFlatCString(size).ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ *_size = val;
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// ExpireFaviconsStatementCallbackNotifier
+
+ExpireFaviconsStatementCallbackNotifier::
+ ExpireFaviconsStatementCallbackNotifier() = default;
+
+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;
+}