/* -*- 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 "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& aFramesInfo) { // Don't extract frames from animated images. bool animated; nsresult rv = aContainer->GetAnimated(&animated); if (NS_FAILED(rv) || !animated) { nsTArray 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 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 removePagesStmt = mDB->GetAsyncStatement("DELETE FROM moz_pages_w_icons"); NS_ENSURE_STATE(removePagesStmt); nsCOMPtr removeIconsStmt = mDB->GetAsyncStatement("DELETE FROM moz_icons"); NS_ENSURE_STATE(removeIconsStmt); nsCOMPtr unlinkIconsStmt = mDB->GetAsyncStatement("DELETE FROM moz_icons_to_pages"); NS_ENSURE_STATE(unlinkIconsStmt); nsTArray> stmts = { ToRefPtr(std::move(removePagesStmt)), ToRefPtr(std::move(removeIconsStmt)), ToRefPtr(std::move(unlinkIconsStmt))}; nsCOMPtr conn = mDB->MainConn(); if (!conn) { return NS_ERROR_UNEXPECTED; } nsCOMPtr ps; RefPtr 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 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 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 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 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 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 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& 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 event = new AsyncReplaceFaviconData(*iconData); RefPtr 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 dataURI; nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL); NS_ENSURE_SUCCESS(rv, rv); // Use the data: protocol handler to convert the data. nsCOMPtr ioService = do_GetIOService(&rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr protocolHandler; rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 channel; rv = protocolHandler->NewChannel(dataURI, loadInfo, getter_AddRefs(channel)); NS_ENSURE_SUCCESS(rv, rv); // Blocking stream is OK for data URIs. nsCOMPtr 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 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 event = new AsyncGetFaviconURLForPage( pageSpec, pageHost, aPreferredWidth, aCallback); RefPtr 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 event = new AsyncGetFaviconDataForPage( pageSpec, pageHost, aPreferredWidth, aCallback); RefPtr 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 event = new AsyncCopyFavicons(fromPage, toPage, aCallback); // Get the target thread and start the work. // DB will be updated and observers notified when done. RefPtr 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 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 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 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 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 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(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 observerService = mozilla::services::GetObserverService(); if (observerService) { (void)observerService->NotifyObservers( nullptr, NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID, nullptr); } return NS_OK; }