michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AsyncFaviconHelpers.h" michael@0: michael@0: #include "nsICacheService.h" michael@0: #include "nsICacheEntry.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: michael@0: #include "nsNavHistory.h" michael@0: #include "nsFaviconService.h" michael@0: #include "mozilla/storage.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsIPrivateBrowsingChannel.h" michael@0: #include "nsISupportsPriority.h" michael@0: #include michael@0: michael@0: using namespace mozilla::places; michael@0: using namespace mozilla::storage; michael@0: michael@0: namespace mozilla { michael@0: namespace places { michael@0: michael@0: namespace { michael@0: michael@0: /** michael@0: * Fetches information on a page from the Places database. michael@0: * michael@0: * @param aDBConn michael@0: * Database connection to history tables. michael@0: * @param _page michael@0: * Page that should be fetched. michael@0: */ michael@0: nsresult michael@0: FetchPageInfo(nsRefPtr& aDB, michael@0: PageData& _page) michael@0: { michael@0: NS_PRECONDITION(_page.spec.Length(), "Must have a non-empty spec!"); michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: // This query finds the bookmarked uri we want to set the icon for, michael@0: // walking up to two redirect levels. michael@0: nsCString query = nsPrintfCString( michael@0: "SELECT h.id, h.favicon_id, h.guid, ( " michael@0: "SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id " michael@0: "UNION ALL " // Union not directly bookmarked pages. michael@0: "SELECT url FROM moz_places WHERE id = ( " michael@0: "SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id " michael@0: "FROM moz_historyvisits dest " michael@0: "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit " michael@0: "AND dest.visit_type IN (%d, %d) " michael@0: "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id " michael@0: "AND parent.visit_type IN (%d, %d) " michael@0: "WHERE dest.place_id = h.id " michael@0: "AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) " michael@0: "LIMIT 1 " michael@0: ") " michael@0: ") FROM moz_places h WHERE h.url = :page_url", michael@0: nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, michael@0: nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY, michael@0: nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT, michael@0: nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY michael@0: ); michael@0: michael@0: nsCOMPtr stmt = aDB->GetStatement(query); michael@0: NS_ENSURE_STATE(stmt); michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), michael@0: _page.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!hasResult) { michael@0: // The page does not exist. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(0, &_page.id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: bool isNull; michael@0: rv = stmt->GetIsNull(1, &isNull); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // favicon_id can be nullptr. michael@0: if (!isNull) { michael@0: rv = stmt->GetInt64(1, &_page.iconId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: rv = stmt->GetUTF8String(2, _page.guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->GetIsNull(3, &isNull); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // The page could not be bookmarked. michael@0: if (!isNull) { michael@0: rv = stmt->GetUTF8String(3, _page.bookmarkedSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!_page.canAddToHistory) { michael@0: // Either history is disabled or the scheme is not supported. In such a michael@0: // case we want to update the icon only if the page is bookmarked. michael@0: michael@0: if (_page.bookmarkedSpec.IsEmpty()) { michael@0: // The page is not bookmarked. Since updating the icon with a disabled michael@0: // history would be a privacy leak, bail out as if the page did not exist. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: else { michael@0: // The page, or a redirect to it, is bookmarked. If the bookmarked spec michael@0: // is different from the requested one, use it. michael@0: if (!_page.bookmarkedSpec.Equals(_page.spec)) { michael@0: _page.spec = _page.bookmarkedSpec; michael@0: rv = FetchPageInfo(aDB, _page); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Stores information on a icon in the database. michael@0: * michael@0: * @param aDBConn michael@0: * Database connection to history tables. michael@0: * @param aIcon michael@0: * Icon that should be stored. michael@0: */ michael@0: nsresult michael@0: SetIconInfo(nsRefPtr& aDB, michael@0: IconData& aIcon) michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: // The 'multi-coalesce' here ensures that replacing a favicon without michael@0: // specifying a :guid parameter doesn't cause it to be allocated a new michael@0: // GUID. michael@0: nsCOMPtr stmt = aDB->GetStatement( michael@0: "INSERT OR REPLACE INTO moz_favicons " michael@0: "(id, url, data, mime_type, expiration, guid) " michael@0: "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), " michael@0: ":icon_url, :data, :mime_type, :expiration, " michael@0: "COALESCE(:guid, " michael@0: "(SELECT guid FROM moz_favicons " michael@0: "WHERE url = :icon_url), " michael@0: "GENERATE_GUID()))" michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: mozStorageStatementScoper scoper(stmt); michael@0: nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aIcon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"), michael@0: TO_INTBUFFER(aIcon.data), aIcon.data.Length()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), aIcon.mimeType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aIcon.expiration); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Binding a GUID allows us to override the current (or generated) GUID. michael@0: if (aIcon.guid.IsEmpty()) { michael@0: rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid")); michael@0: } michael@0: else { michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aIcon.guid); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Fetches information on a icon from the Places database. michael@0: * michael@0: * @param aDBConn michael@0: * Database connection to history tables. michael@0: * @param _icon michael@0: * Icon that should be fetched. michael@0: */ michael@0: nsresult michael@0: FetchIconInfo(nsRefPtr& aDB, michael@0: IconData& _icon) michael@0: { michael@0: NS_PRECONDITION(_icon.spec.Length(), "Must have a non-empty spec!"); michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: if (_icon.status & ICON_STATUS_CACHED) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr stmt = aDB->GetStatement( michael@0: "SELECT id, expiration, data, mime_type " michael@0: "FROM moz_favicons WHERE url = :icon_url" michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: DebugOnly rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), michael@0: _icon.spec); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: if (!hasResult) { michael@0: // The icon does not exist yet, bail out. michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = stmt->GetInt64(0, &_icon.id); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: // Expiration can be nullptr. michael@0: bool isNull; michael@0: rv = stmt->GetIsNull(1, &isNull); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: if (!isNull) { michael@0: rv = stmt->GetInt64(1, reinterpret_cast(&_icon.expiration)); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: // Data can be nullptr. michael@0: rv = stmt->GetIsNull(2, &isNull); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: if (!isNull) { michael@0: uint8_t* data; michael@0: uint32_t dataLen = 0; michael@0: rv = stmt->GetBlob(2, &dataLen, &data); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: _icon.data.Adopt(TO_CHARBUFFER(data), dataLen); michael@0: // Read mime only if we have data. michael@0: rv = stmt->GetUTF8String(3, _icon.mimeType); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: FetchIconURL(nsRefPtr& aDB, michael@0: const nsACString& aPageSpec, michael@0: nsACString& aIconSpec) michael@0: { michael@0: NS_PRECONDITION(!aPageSpec.IsEmpty(), "Page spec must not be empty."); michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread."); michael@0: michael@0: aIconSpec.Truncate(); michael@0: michael@0: nsCOMPtr stmt = aDB->GetStatement( michael@0: "SELECT f.url " michael@0: "FROM moz_places h " michael@0: "JOIN moz_favicons f ON h.favicon_id = f.id " michael@0: "WHERE h.url = :page_url" michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), michael@0: aPageSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: rv = stmt->GetUTF8String(0, aIconSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Tries to compute the expiration time for a icon from the channel. michael@0: * michael@0: * @param aChannel michael@0: * The network channel used to fetch the icon. michael@0: * @return a valid expiration value for the fetched icon. michael@0: */ michael@0: PRTime michael@0: GetExpirationTimeFromChannel(nsIChannel* aChannel) michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: // Attempt to get an expiration time from the cache. If this fails, we'll michael@0: // make one up. michael@0: PRTime expiration = -1; michael@0: nsCOMPtr cachingChannel = do_QueryInterface(aChannel); michael@0: if (cachingChannel) { michael@0: nsCOMPtr cacheToken; michael@0: nsresult rv = cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr cacheEntry = do_QueryInterface(cacheToken); michael@0: uint32_t seconds; michael@0: rv = cacheEntry->GetExpirationTime(&seconds); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Set the expiration, but make sure we honor our cap. michael@0: expiration = PR_Now() + std::min((PRTime)seconds * PR_USEC_PER_SEC, michael@0: MAX_FAVICON_EXPIRATION); michael@0: } michael@0: } michael@0: } michael@0: // If we did not obtain a time from the cache, use the cap value. michael@0: return expiration < 0 ? PR_Now() + MAX_FAVICON_EXPIRATION michael@0: : expiration; michael@0: } michael@0: michael@0: /** michael@0: * Checks the icon and evaluates if it needs to be optimized. In such a case it michael@0: * will try to reduce its size through OptimizeFaviconImage method of the michael@0: * favicons service. michael@0: * michael@0: * @param aIcon michael@0: * The icon to be evaluated. michael@0: * @param aFaviconSvc michael@0: * Pointer to the favicons service. michael@0: */ michael@0: nsresult michael@0: OptimizeIconSize(IconData& aIcon, michael@0: nsFaviconService* aFaviconSvc) michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: // Even if the page provides a large image for the favicon (eg, a highres michael@0: // image or a multiresolution .ico file), don't try to store more data than michael@0: // needed. michael@0: nsAutoCString newData, newMimeType; michael@0: if (aIcon.data.Length() > MAX_ICON_FILESIZE(aFaviconSvc->GetOptimizedIconDimension())) { michael@0: nsresult rv = aFaviconSvc->OptimizeFaviconImage(TO_INTBUFFER(aIcon.data), michael@0: aIcon.data.Length(), michael@0: aIcon.mimeType, michael@0: newData, michael@0: newMimeType); michael@0: if (NS_SUCCEEDED(rv) && newData.Length() < aIcon.data.Length()) { michael@0: aIcon.data = newData; michael@0: aIcon.mimeType = newMimeType; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // Anonymous namespace. michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncFaviconHelperBase michael@0: michael@0: AsyncFaviconHelperBase::AsyncFaviconHelperBase( michael@0: nsCOMPtr& aCallback michael@0: ) : mDB(Database::GetDatabase()) michael@0: { michael@0: // Don't AddRef or Release in runnables for thread-safety. michael@0: mCallback.swap(aCallback); michael@0: } michael@0: michael@0: AsyncFaviconHelperBase::~AsyncFaviconHelperBase() michael@0: { michael@0: nsCOMPtr thread; michael@0: (void)NS_GetMainThread(getter_AddRefs(thread)); michael@0: if (mCallback) { michael@0: (void)NS_ProxyRelease(thread, mCallback, true); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncFetchAndSetIconForPage michael@0: michael@0: // static michael@0: nsresult michael@0: AsyncFetchAndSetIconForPage::start(nsIURI* aFaviconURI, michael@0: nsIURI* aPageURI, michael@0: enum AsyncFaviconFetchMode aFetchMode, michael@0: uint32_t aFaviconLoadType, michael@0: nsIFaviconDataCallback* aCallback) michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: PageData page; michael@0: nsresult rv = aPageURI->GetSpec(page.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // URIs can arguably miss a host. michael@0: (void)GetReversedHostname(aPageURI, page.revHost); michael@0: bool canAddToHistory; michael@0: nsNavHistory* navHistory = nsNavHistory::GetHistoryService(); michael@0: NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY); michael@0: rv = navHistory->CanAddURI(aPageURI, &canAddToHistory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: page.canAddToHistory = !!canAddToHistory && aFaviconLoadType != nsIFaviconService::FAVICON_LOAD_PRIVATE; michael@0: michael@0: IconData icon; michael@0: michael@0: nsFaviconService* favicons = nsFaviconService::GetFaviconService(); michael@0: NS_ENSURE_STATE(favicons); michael@0: michael@0: UnassociatedIconHashKey* iconKey = michael@0: favicons->mUnassociatedIcons.GetEntry(aFaviconURI); michael@0: michael@0: if (iconKey) { michael@0: icon = iconKey->iconData; michael@0: favicons->mUnassociatedIcons.RemoveEntry(aFaviconURI); michael@0: } else { michael@0: icon.fetchMode = aFetchMode; michael@0: rv = aFaviconURI->GetSpec(icon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // If the page url points to an image, the icon's url will be the same. michael@0: // In future evaluate to store a resample of the image. For now avoid that michael@0: // for database size concerns. michael@0: // Don't store favicons for error pages too. michael@0: if (icon.spec.Equals(page.spec) || michael@0: icon.spec.Equals(FAVICON_ERRORPAGE_URL)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The event will swap owning pointers, thus we need a new pointer. michael@0: nsCOMPtr callback(aCallback); michael@0: nsRefPtr event = michael@0: new AsyncFetchAndSetIconForPage(icon, page, aFaviconLoadType, callback); michael@0: michael@0: // Get the target thread and start the work. michael@0: nsRefPtr DB = Database::GetDatabase(); michael@0: NS_ENSURE_STATE(DB); michael@0: DB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: AsyncFetchAndSetIconForPage::AsyncFetchAndSetIconForPage( michael@0: IconData& aIcon michael@0: , PageData& aPage michael@0: , uint32_t aFaviconLoadType michael@0: , nsCOMPtr& aCallback michael@0: ) : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: , mPage(aPage) michael@0: , mFaviconLoadPrivate(aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE) michael@0: { michael@0: } michael@0: michael@0: AsyncFetchAndSetIconForPage::~AsyncFetchAndSetIconForPage() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconForPage::Run() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: // Try to fetch the icon from the database. michael@0: nsresult rv = FetchIconInfo(mDB, mIcon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool isInvalidIcon = mIcon.data.IsEmpty() || michael@0: (mIcon.expiration && PR_Now() > mIcon.expiration); michael@0: bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS || michael@0: (mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon); michael@0: michael@0: if (!fetchIconFromNetwork) { michael@0: // There is already a valid icon or we don't want to fetch a new one, michael@0: // directly proceed with association. michael@0: nsRefPtr event = michael@0: new AsyncAssociateIconToPage(mIcon, mPage, mCallback); michael@0: mDB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: // Fetch the icon from network. When done this will associate the michael@0: // icon to the page and notify. michael@0: nsRefPtr event = michael@0: new AsyncFetchAndSetIconFromNetwork(mIcon, mPage, mFaviconLoadPrivate, mCallback); michael@0: michael@0: // Start the work on the main thread. michael@0: rv = NS_DispatchToMainThread(event); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncFetchAndSetIconFromNetwork michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED( michael@0: AsyncFetchAndSetIconFromNetwork michael@0: , nsRunnable michael@0: , nsIStreamListener michael@0: , nsIInterfaceRequestor michael@0: , nsIChannelEventSink michael@0: ) michael@0: michael@0: AsyncFetchAndSetIconFromNetwork::AsyncFetchAndSetIconFromNetwork( michael@0: IconData& aIcon michael@0: , PageData& aPage michael@0: , bool aFaviconLoadPrivate michael@0: , nsCOMPtr& aCallback michael@0: ) michael@0: : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: , mPage(aPage) michael@0: , mFaviconLoadPrivate(aFaviconLoadPrivate) michael@0: { michael@0: } michael@0: michael@0: AsyncFetchAndSetIconFromNetwork::~AsyncFetchAndSetIconFromNetwork() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::Run() michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: // Ensure data is cleared, since it's going to be overwritten. michael@0: if (mIcon.data.Length() > 0) { michael@0: mIcon.data.Truncate(0); michael@0: mIcon.mimeType.Truncate(0); michael@0: } michael@0: michael@0: nsCOMPtr iconURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr channel; michael@0: rv = NS_NewChannel(getter_AddRefs(channel), iconURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr listenerRequestor = michael@0: do_QueryInterface(reinterpret_cast(this)); michael@0: NS_ENSURE_STATE(listenerRequestor); michael@0: rv = channel->SetNotificationCallbacks(listenerRequestor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr pbChannel = do_QueryInterface(channel); michael@0: if (pbChannel) { michael@0: rv = pbChannel->SetPrivate(mFaviconLoadPrivate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr priorityChannel = do_QueryInterface(channel); michael@0: if (priorityChannel) { michael@0: priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST); michael@0: } michael@0: michael@0: return channel->AsyncOpen(this, nullptr); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::OnStartRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::OnDataAvailable(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsIInputStream* aInputStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: nsAutoCString buffer; michael@0: nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer); michael@0: if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: mIcon.data.Append(buffer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::GetInterface(const nsIID& uuid, michael@0: void** aResult) michael@0: { michael@0: return QueryInterface(uuid, aResult); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::AsyncOnChannelRedirect( michael@0: nsIChannel* oldChannel michael@0: , nsIChannel* newChannel michael@0: , uint32_t flags michael@0: , nsIAsyncVerifyRedirectCallback *cb michael@0: ) michael@0: { michael@0: (void)cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncFetchAndSetIconFromNetwork::OnStopRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsresult aStatusCode) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsFaviconService* favicons = nsFaviconService::GetFaviconService(); michael@0: NS_ENSURE_STATE(favicons); michael@0: michael@0: nsresult rv; michael@0: michael@0: // If fetching the icon failed, add it to the failed cache. michael@0: if (NS_FAILED(aStatusCode) || mIcon.data.Length() == 0) { michael@0: nsCOMPtr iconURI; michael@0: rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = favicons->AddFailedFavicon(iconURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest, michael@0: TO_INTBUFFER(mIcon.data), mIcon.data.Length(), michael@0: mIcon.mimeType); michael@0: michael@0: // If the icon does not have a valid MIME type, add it to the failed cache. michael@0: if (mIcon.mimeType.IsEmpty()) { michael@0: nsCOMPtr iconURI; michael@0: rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = favicons->AddFailedFavicon(iconURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: // aRequest should always QI to nsIChannel. michael@0: // See AsyncFetchAndSetIconFromNetwork::Run() michael@0: MOZ_ASSERT(channel); michael@0: mIcon.expiration = GetExpirationTimeFromChannel(channel); michael@0: michael@0: rv = OptimizeIconSize(mIcon, favicons); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If over the maximum size allowed, don't save data to the database to michael@0: // avoid bloating it. michael@0: if (mIcon.data.Length() > MAX_FAVICON_SIZE) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mIcon.status = ICON_STATUS_CHANGED; michael@0: michael@0: nsRefPtr event = michael@0: new AsyncAssociateIconToPage(mIcon, mPage, mCallback); michael@0: mDB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncAssociateIconToPage michael@0: michael@0: AsyncAssociateIconToPage::AsyncAssociateIconToPage( michael@0: IconData& aIcon michael@0: , PageData& aPage michael@0: , nsCOMPtr& aCallback michael@0: ) : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: , mPage(aPage) michael@0: { michael@0: } michael@0: michael@0: AsyncAssociateIconToPage::~AsyncAssociateIconToPage() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncAssociateIconToPage::Run() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: nsresult rv = FetchPageInfo(mDB, mPage); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE){ michael@0: // We have never seen this page. If we can add the page to history, michael@0: // we will try to do it later, otherwise just bail out. michael@0: if (!mPage.canAddToHistory) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mozStorageTransaction transaction(mDB->MainConn(), false, michael@0: mozIStorageConnection::TRANSACTION_IMMEDIATE); michael@0: michael@0: // If there is no entry for this icon, or the entry is obsolete, replace it. michael@0: if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) { michael@0: rv = SetIconInfo(mDB, mIcon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the new icon id. Do this regardless mIcon.id, since other code michael@0: // could have added a entry before us. Indeed we interrupted the thread michael@0: // after the previous call to FetchIconInfo. michael@0: mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED; michael@0: rv = FetchIconInfo(mDB, mIcon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // If the page does not have an id, don't try to insert a new one, cause we michael@0: // don't know where the page comes from. Not doing so we may end adding michael@0: // a page that otherwise we'd explicitly ignore, like a POST or an error page. michael@0: if (mPage.id == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Otherwise just associate the icon to the page, if needed. michael@0: if (mPage.iconId != mIcon.id) { michael@0: nsCOMPtr stmt; michael@0: if (mPage.id) { michael@0: stmt = mDB->GetStatement( michael@0: "UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id" michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: stmt = mDB->GetStatement( michael@0: "UPDATE moz_places SET favicon_id = :icon_id WHERE url = :page_url" michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), mIcon.id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mozStorageStatementScoper scoper(stmt); michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mIcon.status |= ICON_STATUS_ASSOCIATED; michael@0: } michael@0: michael@0: rv = transaction.Commit(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Finally, dispatch an event to the main thread to notify observers. michael@0: nsCOMPtr event = new NotifyIconObservers(mIcon, mPage, mCallback); michael@0: rv = NS_DispatchToMainThread(event); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncGetFaviconURLForPage michael@0: michael@0: // static michael@0: nsresult michael@0: AsyncGetFaviconURLForPage::start(nsIURI* aPageURI, michael@0: nsIFaviconDataCallback* aCallback) michael@0: { michael@0: NS_ENSURE_ARG(aCallback); michael@0: NS_ENSURE_ARG(aPageURI); michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread."); michael@0: michael@0: nsAutoCString pageSpec; michael@0: nsresult rv = aPageURI->GetSpec(pageSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr callback = aCallback; michael@0: nsRefPtr event = michael@0: new AsyncGetFaviconURLForPage(pageSpec, callback); michael@0: michael@0: nsRefPtr DB = Database::GetDatabase(); michael@0: NS_ENSURE_STATE(DB); michael@0: DB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage( michael@0: const nsACString& aPageSpec michael@0: , nsCOMPtr& aCallback michael@0: ) : AsyncFaviconHelperBase(aCallback) michael@0: { michael@0: mPageSpec.Assign(aPageSpec); michael@0: } michael@0: michael@0: AsyncGetFaviconURLForPage::~AsyncGetFaviconURLForPage() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncGetFaviconURLForPage::Run() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread."); michael@0: michael@0: nsAutoCString iconSpec; michael@0: nsresult rv = FetchIconURL(mDB, mPageSpec, iconSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now notify our callback of the icon spec we retrieved, even if empty. michael@0: IconData iconData; michael@0: iconData.spec.Assign(iconSpec); michael@0: michael@0: PageData pageData; michael@0: pageData.spec.Assign(mPageSpec); michael@0: michael@0: nsCOMPtr event = michael@0: new NotifyIconObservers(iconData, pageData, mCallback); michael@0: rv = NS_DispatchToMainThread(event); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncGetFaviconDataForPage michael@0: michael@0: // static michael@0: nsresult michael@0: AsyncGetFaviconDataForPage::start(nsIURI* aPageURI, michael@0: nsIFaviconDataCallback* aCallback) michael@0: { michael@0: NS_ENSURE_ARG(aCallback); michael@0: NS_ENSURE_ARG(aPageURI); michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread."); michael@0: michael@0: nsAutoCString pageSpec; michael@0: nsresult rv = aPageURI->GetSpec(pageSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr callback = aCallback; michael@0: nsRefPtr event = michael@0: new AsyncGetFaviconDataForPage(pageSpec, callback); michael@0: michael@0: nsRefPtr DB = Database::GetDatabase(); michael@0: NS_ENSURE_STATE(DB); michael@0: DB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage( michael@0: const nsACString& aPageSpec michael@0: , nsCOMPtr& aCallback michael@0: ) : AsyncFaviconHelperBase(aCallback) michael@0: { michael@0: mPageSpec.Assign(aPageSpec); michael@0: } michael@0: michael@0: AsyncGetFaviconDataForPage::~AsyncGetFaviconDataForPage() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncGetFaviconDataForPage::Run() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread."); michael@0: michael@0: nsAutoCString iconSpec; michael@0: nsresult rv = FetchIconURL(mDB, mPageSpec, iconSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: IconData iconData; michael@0: iconData.spec.Assign(iconSpec); michael@0: michael@0: PageData pageData; michael@0: pageData.spec.Assign(mPageSpec); michael@0: michael@0: if (!iconSpec.IsEmpty()) { michael@0: rv = FetchIconInfo(mDB, iconData); michael@0: if (NS_FAILED(rv)) { michael@0: iconData.spec.Truncate(); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr event = michael@0: new NotifyIconObservers(iconData, pageData, mCallback); michael@0: rv = NS_DispatchToMainThread(event); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// AsyncReplaceFaviconData michael@0: michael@0: // static michael@0: nsresult michael@0: AsyncReplaceFaviconData::start(IconData *aIcon) michael@0: { michael@0: NS_ENSURE_ARG(aIcon); michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread."); michael@0: michael@0: nsCOMPtr callback; michael@0: nsRefPtr event = michael@0: new AsyncReplaceFaviconData(*aIcon, callback); michael@0: michael@0: nsRefPtr DB = Database::GetDatabase(); michael@0: NS_ENSURE_STATE(DB); michael@0: DB->DispatchToAsyncThread(event); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: AsyncReplaceFaviconData::AsyncReplaceFaviconData( michael@0: IconData &aIcon michael@0: , nsCOMPtr& aCallback michael@0: ) : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: { michael@0: } michael@0: michael@0: AsyncReplaceFaviconData::~AsyncReplaceFaviconData() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AsyncReplaceFaviconData::Run() michael@0: { michael@0: NS_PRECONDITION(!NS_IsMainThread(), michael@0: "This should not be called on the main thread"); michael@0: michael@0: IconData dbIcon; michael@0: dbIcon.spec.Assign(mIcon.spec); michael@0: nsresult rv = FetchIconInfo(mDB, dbIcon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!dbIcon.id) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = SetIconInfo(mDB, mIcon); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We can invalidate the cache version since we now persist the icon. michael@0: nsCOMPtr event = new RemoveIconDataCacheEntry(mIcon, mCallback); michael@0: rv = NS_DispatchToMainThread(event); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// RemoveIconDataCacheEntry michael@0: michael@0: RemoveIconDataCacheEntry::RemoveIconDataCacheEntry( michael@0: IconData& aIcon michael@0: , nsCOMPtr& aCallback michael@0: ) michael@0: : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: { michael@0: } michael@0: michael@0: RemoveIconDataCacheEntry::~RemoveIconDataCacheEntry() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: RemoveIconDataCacheEntry::Run() michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: nsCOMPtr iconURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsFaviconService* favicons = nsFaviconService::GetFaviconService(); michael@0: NS_ENSURE_STATE(favicons); michael@0: favicons->mUnassociatedIcons.RemoveEntry(iconURI); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// NotifyIconObservers michael@0: michael@0: NotifyIconObservers::NotifyIconObservers( michael@0: IconData& aIcon michael@0: , PageData& aPage michael@0: , nsCOMPtr& aCallback michael@0: ) michael@0: : AsyncFaviconHelperBase(aCallback) michael@0: , mIcon(aIcon) michael@0: , mPage(aPage) michael@0: { michael@0: } michael@0: michael@0: NotifyIconObservers::~NotifyIconObservers() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotifyIconObservers::Run() michael@0: { michael@0: NS_PRECONDITION(NS_IsMainThread(), michael@0: "This should be called on the main thread"); michael@0: michael@0: nsCOMPtr iconURI; michael@0: if (!mIcon.spec.IsEmpty()) { michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(iconURI), mIcon.spec))); michael@0: if (iconURI) michael@0: { michael@0: // Notify observers only if something changed. michael@0: if (mIcon.status & ICON_STATUS_SAVED || michael@0: mIcon.status & ICON_STATUS_ASSOCIATED) { michael@0: SendGlobalNotifications(iconURI); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mCallback) { michael@0: (void)mCallback->OnComplete(iconURI, mIcon.data.Length(), michael@0: TO_INTBUFFER(mIcon.data), mIcon.mimeType); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI) michael@0: { michael@0: nsCOMPtr pageURI; michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(pageURI), mPage.spec))); michael@0: if (pageURI) { michael@0: nsFaviconService* favicons = nsFaviconService::GetFaviconService(); michael@0: MOZ_ASSERT(favicons); michael@0: if (favicons) { michael@0: (void)favicons->SendFaviconNotifications(pageURI, aIconURI, mPage.guid); michael@0: } michael@0: } michael@0: michael@0: // If the page is bookmarked and the bookmarked url is different from the michael@0: // updated one, start a new task to update its icon as well. michael@0: if (!mPage.bookmarkedSpec.IsEmpty() && michael@0: !mPage.bookmarkedSpec.Equals(mPage.spec)) { michael@0: // Create a new page struct to avoid polluting it with old data. michael@0: PageData bookmarkedPage; michael@0: bookmarkedPage.spec = mPage.bookmarkedSpec; michael@0: michael@0: // This will be silent, so be sure to not pass in the current callback. michael@0: nsCOMPtr nullCallback; michael@0: nsRefPtr event = michael@0: new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback); michael@0: mDB->DispatchToAsyncThread(event); michael@0: } michael@0: } michael@0: michael@0: } // namespace places michael@0: } // namespace mozilla