From a90a5cba08fdf6c0ceb95101c275108a152a3aed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:37 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- toolkit/components/places/nsFaviconService.cpp | 406 ++++++++++--------------- 1 file changed, 166 insertions(+), 240 deletions(-) (limited to 'toolkit/components/places/nsFaviconService.cpp') diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp index c236d7a680..ba338e9d52 100644 --- a/toolkit/components/places/nsFaviconService.cpp +++ b/toolkit/components/places/nsFaviconService.cpp @@ -14,6 +14,7 @@ */ #include "nsFaviconService.h" +#include "PlacesCompletionCallback.h" #include "nsNavHistory.h" #include "nsPlacesMacros.h" @@ -24,6 +25,7 @@ #include "nsStreamUtils.h" #include "plbase64.h" #include "nsIClassInfoImpl.h" +#include "mozilla/AppShutdown.h" #include "mozilla/ArrayUtils.h" #include "mozilla/LoadInfo.h" #include "mozilla/NullPrincipal.h" @@ -35,13 +37,6 @@ #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; @@ -123,12 +118,10 @@ nsresult GetFramesInfoForContainer(imgIContainer* aContainer, PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService) NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID) -NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService, nsITimerCallback, - nsINamed) +NS_IMPL_ISUPPORTS_CI(nsFaviconService, nsIFaviconService) nsFaviconService::nsFaviconService() - : mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH), - mDefaultIconURIPreferredSize(UINT16_MAX) { + : mDefaultIconURIPreferredSize(UINT16_MAX) { NS_ASSERTION(!gFaviconService, "Attempting to create two instances of the service!"); gFaviconService = this; @@ -152,10 +145,6 @@ nsFaviconService::StoreLastInsertedId(const nsACString& aTable, nsresult nsFaviconService::Init() { mDB = Database::GetDatabase(); NS_ENSURE_STATE(mDB); - - mExpireUnassociatedIconsTimer = NS_NewTimer(); - NS_ENSURE_STATE(mExpireUnassociatedIconsTimer); - return NS_OK; } @@ -187,41 +176,6 @@ nsFaviconService::ExpireAllFavicons() { 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 @@ -258,6 +212,161 @@ void nsFaviconService::ClearImageCache(nsIURI* aImageURI) { } } +NS_IMETHODIMP +nsFaviconService::SetFaviconForPage( + nsIURI* aPageURI, nsIURI* aFaviconURI, nsIURI* aDataURL, + PRTime aExpiration = 0, PlacesCompletionCallback* aCallback = nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG(aPageURI); + NS_ENSURE_ARG(aFaviconURI); + NS_ENSURE_ARG(aDataURL); + + MOZ_DIAGNOSTIC_ASSERT(aDataURL->SchemeIs("data")); + if (!aDataURL->SchemeIs("data")) { + return NS_ERROR_INVALID_ARG; + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return NS_OK; + } + + PRTime now = PR_Now(); + if (aExpiration < now + MIN_FAVICON_EXPIRATION) { + // Invalid input, just use the default. + aExpiration = now + MAX_FAVICON_EXPIRATION; + } + + // Use the data: protocol handler to convert the data. + nsresult rv = NS_OK; + auto guard = MakeScopeExit([&]() { + if (aCallback) { + aCallback->Complete(rv); + } + }); + + 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 = + NullPrincipal::CreateWithoutOriginAttributes(); + if (MOZ_UNLIKELY(!(loadingPrincipal))) { + return (rv = NS_ERROR_NULL_POINTER); + } + + 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(aDataURL, 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 (rv = 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); + NS_ENSURE_SUCCESS(rv, rv); + if (numRead != available) { + return (rv = NS_ERROR_UNEXPECTED); + } + + nsAutoCString mimeType; + rv = channel->GetContentType(mimeType); + NS_ENSURE_SUCCESS(rv, rv); + if (!imgLoader::SupportImageWithMimeType( + mimeType, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) { + return (rv = NS_ERROR_UNEXPECTED); + } + + // Favicon should be handled without userpass. + nsCOMPtr faviconURI = GetExposableURI(aFaviconURI); + nsCOMPtr pageURI = GetExposableURI(aPageURI); + + IconData icon; + icon.expiration = aExpiration; + icon.status = ICON_STATUS_CACHED; + icon.fetchMode = FETCH_NEVER; + rv = faviconURI->GetSpec(icon.spec); + NS_ENSURE_SUCCESS(rv, rv); + // URIs can arguably lack a host. + (void)faviconURI->GetHost(icon.host); + if (StringBeginsWith(icon.host, "www."_ns)) { + icon.host.Cut(0, 4); + } + + IconPayload payload; + payload.mimeType = mimeType; + payload.data.Assign(TO_CHARBUFFER(buffer.Elements()), buffer.Length()); + if (payload.mimeType.EqualsLiteral(SVG_MIME_TYPE)) { + payload.width = UINT16_MAX; + } + icon.payloads.AppendElement(payload); + + rv = OptimizeIconSizes(icon); + NS_ENSURE_SUCCESS(rv, rv); + + PageData page; + rv = pageURI->GetSpec(page.spec); + NS_ENSURE_SUCCESS(rv, rv); + // URIs can arguably lack a host. + (void)pageURI->GetHost(page.host); + if (StringBeginsWith(page.host, "www."_ns)) { + page.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(faviconURI->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 AsyncSetIconForPage(icon, page, aCallback); + RefPtr DB = Database::GetDatabase(); + if (MOZ_UNLIKELY(!DB)) { + return (rv = NS_ERROR_UNEXPECTED); + } + + DB->DispatchToAsyncThread(event); + + guard.release(); + return NS_OK; +} + NS_IMETHODIMP nsFaviconService::SetAndFetchFaviconForPage( nsIURI* aPageURI, nsIURI* aFaviconURI, bool aForceReload, @@ -310,20 +419,13 @@ nsFaviconService::SetAndFetchFaviconForPage( // Build icon data. IconData icon; - // If we have an in-memory icon payload, it overwrites the actual request. - UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(faviconURI); - if (iconKey) { - icon = iconKey->iconData; - mUnassociatedIcons.RemoveEntry(iconKey); - } else { - icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING; - rv = faviconURI->GetSpec(icon.spec); - NS_ENSURE_SUCCESS(rv, rv); - // URIs can arguably lack a host. - Unused << faviconURI->GetHost(icon.host); - if (StringBeginsWith(icon.host, "www."_ns)) { - icon.host.Cut(0, 4); - } + icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING; + rv = faviconURI->GetSpec(icon.spec); + NS_ENSURE_SUCCESS(rv, rv); + // URIs can arguably lack a host. + Unused << faviconURI->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 @@ -360,182 +462,6 @@ nsFaviconService::SetAndFetchFaviconForPage( 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)); - - nsCOMPtr faviconURI = GetExposableURI(aFaviconURI); - - 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(faviconURI); - if (!iconKey) { - return NS_ERROR_OUT_OF_MEMORY; - } - - iconKey->created = 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. - uint32_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 = faviconURI->GetSpec(iconData->spec); - NS_ENSURE_SUCCESS(rv, rv); - // URIs can arguably lack a host. - Unused << faviconURI->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(faviconURI); - 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, -- cgit v1.2.3