| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/favicon/content/content_favicon_driver.h" |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "components/favicon/content/favicon_url_util.h" |
| #include "components/favicon/core/favicon_service.h" |
| #include "components/favicon/core/favicon_url.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/favicon_status.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_details.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/page.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom.h" |
| #include "ui/gfx/image/image.h" |
| |
| namespace favicon { |
| |
| gfx::Image ContentFaviconDriver::GetFavicon() const { |
| // Like GetTitle(), we also want to use the favicon for the last committed |
| // entry rather than a pending navigation entry. |
| content::NavigationController& controller = web_contents()->GetController(); |
| |
| content::NavigationEntry* entry = controller.GetLastCommittedEntry(); |
| if (entry) |
| return entry->GetFavicon().image; |
| return gfx::Image(); |
| } |
| |
| bool ContentFaviconDriver::FaviconIsValid() const { |
| content::NavigationController& controller = web_contents()->GetController(); |
| |
| content::NavigationEntry* entry = controller.GetLastCommittedEntry(); |
| if (entry) |
| return entry->GetFavicon().valid; |
| |
| return false; |
| } |
| |
| GURL ContentFaviconDriver::GetActiveURL() { |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| return entry ? entry->GetURL() : GURL(); |
| } |
| |
| GURL ContentFaviconDriver::GetManifestURL(content::RenderFrameHost* rfh) { |
| DocumentManifestData* document_data = |
| DocumentManifestData::GetOrCreateForCurrentDocument(rfh); |
| return document_data->has_manifest_url |
| ? rfh->GetPage().GetManifestUrl().value_or(GURL()) |
| : GURL(); |
| } |
| |
| ContentFaviconDriver::ContentFaviconDriver(content::WebContents* web_contents, |
| CoreFaviconService* favicon_service) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<ContentFaviconDriver>(*web_contents), |
| FaviconDriverImpl(favicon_service) {} |
| |
| ContentFaviconDriver::~ContentFaviconDriver() = default; |
| |
| ContentFaviconDriver::DocumentManifestData::DocumentManifestData( |
| content::RenderFrameHost* rfh) |
| : content::DocumentUserData<DocumentManifestData>(rfh) {} |
| ContentFaviconDriver::DocumentManifestData::~DocumentManifestData() = default; |
| |
| ContentFaviconDriver::NavigationManifestData::NavigationManifestData( |
| content::NavigationHandle& navigation_handle) {} |
| ContentFaviconDriver::NavigationManifestData::~NavigationManifestData() = |
| default; |
| |
| void ContentFaviconDriver::OnDidDownloadManifest( |
| ManifestDownloadCallback callback, |
| const GURL& manifest_url, |
| blink::mojom::ManifestPtr manifest) { |
| // ~WebContentsImpl triggers running any pending callbacks for manifests. |
| // As we're about to be destroyed ignore the request. To do otherwise may |
| // result in calling back to this and attempting to use the WebContents, which |
| // will crash. |
| if (!web_contents()) |
| return; |
| |
| std::vector<FaviconURL> candidates; |
| if (manifest) { |
| for (const auto& icon : manifest->icons) { |
| candidates.emplace_back( |
| icon.src, favicon_base::IconType::kWebManifestIcon, icon.sizes); |
| } |
| } |
| std::move(callback).Run(candidates); |
| } |
| |
| int ContentFaviconDriver::DownloadImage(const GURL& url, |
| int max_image_size, |
| ImageDownloadCallback callback) { |
| bool bypass_cache = (bypass_cache_page_url_ == GetActiveURL()); |
| bypass_cache_page_url_ = GURL(); |
| |
| const gfx::Size preferred_size(max_image_size, max_image_size); |
| return web_contents()->DownloadImage(url, true, preferred_size, |
| /*max_bitmap_size=*/max_image_size, |
| bypass_cache, std::move(callback)); |
| } |
| |
| void ContentFaviconDriver::DownloadManifest(const GURL& url, |
| ManifestDownloadCallback callback) { |
| // TODO(crbug.com/1201237): This appears to be reachable from pages other |
| // than the primary page. This code should likely be refactored so that either |
| // this is unreachable from other pages, or the correct page is plumbed in |
| // here. |
| web_contents()->GetPrimaryPage().GetManifest( |
| base::BindOnce(&ContentFaviconDriver::OnDidDownloadManifest, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| bool ContentFaviconDriver::IsOffTheRecord() { |
| DCHECK(web_contents()); |
| return web_contents()->GetBrowserContext()->IsOffTheRecord(); |
| } |
| |
| void ContentFaviconDriver::OnFaviconUpdated( |
| const GURL& page_url, |
| FaviconDriverObserver::NotificationIconType notification_icon_type, |
| const GURL& icon_url, |
| bool icon_url_changed, |
| const gfx::Image& image) { |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| DCHECK(entry); |
| DCHECK_EQ(entry->GetURL(), page_url); |
| |
| if (notification_icon_type == FaviconDriverObserver::NON_TOUCH_16_DIP) { |
| entry->GetFavicon().valid = true; |
| entry->GetFavicon().url = icon_url; |
| entry->GetFavicon().image = image; |
| web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); |
| } |
| |
| NotifyFaviconUpdatedObservers(notification_icon_type, icon_url, |
| icon_url_changed, image); |
| } |
| |
| void ContentFaviconDriver::OnFaviconDeleted( |
| const GURL& page_url, |
| FaviconDriverObserver::NotificationIconType notification_icon_type) { |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| DCHECK(entry && entry->GetURL() == page_url); |
| |
| if (notification_icon_type == FaviconDriverObserver::NON_TOUCH_16_DIP) { |
| entry->GetFavicon() = content::FaviconStatus(); |
| web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB); |
| } |
| |
| NotifyFaviconUpdatedObservers(notification_icon_type, /*icon_url=*/GURL(), |
| /*icon_url_changed=*/true, |
| content::FaviconStatus().image); |
| } |
| |
| void ContentFaviconDriver::DidUpdateFaviconURL( |
| content::RenderFrameHost* rfh, |
| const std::vector<blink::mojom::FaviconURLPtr>& candidates) { |
| // Ignore the update if there is no last committed navigation entry. This can |
| // occur when loading an initially blank page. |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| |
| if (!entry) |
| return; |
| |
| if (!rfh->IsDocumentOnLoadCompletedInMainFrame()) |
| return; |
| |
| OnUpdateCandidates(rfh->GetLastCommittedURL(), |
| FaviconURLsFromContentFaviconURLs(candidates), |
| GetManifestURL(rfh)); |
| } |
| |
| void ContentFaviconDriver::DidUpdateWebManifestURL( |
| content::RenderFrameHost* rfh, |
| const GURL& manifest_url) { |
| // Ignore the update if there is no last committed navigation entry. This can |
| // occur when loading an initially blank page. |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| if (!entry || !rfh->IsDocumentOnLoadCompletedInMainFrame()) |
| return; |
| |
| DocumentManifestData* document_data = |
| DocumentManifestData::GetOrCreateForCurrentDocument(rfh); |
| document_data->has_manifest_url = true; |
| |
| // On regular page loads, DidUpdateManifestURL() is guaranteed to be called |
| // before DidUpdateFaviconURL(). However, a page can update the favicons via |
| // javascript. |
| if (!rfh->FaviconURLs().empty()) { |
| OnUpdateCandidates(rfh->GetLastCommittedURL(), |
| FaviconURLsFromContentFaviconURLs(rfh->FaviconURLs()), |
| GetManifestURL(rfh)); |
| } |
| } |
| |
| void ContentFaviconDriver::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame()) |
| return; |
| |
| content::ReloadType reload_type = navigation_handle->GetReloadType(); |
| if (reload_type == content::ReloadType::NONE || IsOffTheRecord()) |
| return; |
| |
| if (!navigation_handle->IsSameDocument()) { |
| NavigationManifestData* navigation_data = |
| NavigationManifestData::GetOrCreateForNavigationHandle( |
| *navigation_handle); |
| navigation_data->has_manifest_url = false; |
| } |
| |
| if (reload_type == content::ReloadType::BYPASSING_CACHE) |
| bypass_cache_page_url_ = navigation_handle->GetURL(); |
| |
| SetFaviconOutOfDateForPage( |
| navigation_handle->GetURL(), |
| reload_type == content::ReloadType::BYPASSING_CACHE); |
| } |
| |
| void ContentFaviconDriver::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInPrimaryMainFrame() || |
| !navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) { |
| return; |
| } |
| |
| // Transfer in-flight navigation data to the document user data. |
| NavigationManifestData* navigation_data = |
| NavigationManifestData::GetOrCreateForNavigationHandle( |
| *navigation_handle); |
| DocumentManifestData* document_data = |
| DocumentManifestData::GetOrCreateForCurrentDocument( |
| navigation_handle->GetRenderFrameHost()); |
| document_data->has_manifest_url = navigation_data->has_manifest_url; |
| |
| // Wait till the user navigates to a new URL to start checking the cache |
| // again. The cache may be ignored for non-reload navigations (e.g. |
| // history.replace() in-page navigation). This is allowed to increase the |
| // likelihood that "reloading a page ignoring the cache" redownloads the |
| // favicon. In particular, a page may do an in-page navigation before |
| // FaviconHandler has the time to determine that the favicon needs to be |
| // redownloaded. |
| GURL url = navigation_handle->GetURL(); |
| if (url != bypass_cache_page_url_) |
| bypass_cache_page_url_ = GURL(); |
| |
| // Get the favicon, either from history or request it from the net. |
| FetchFavicon(url, navigation_handle->IsSameDocument()); |
| } |
| |
| NAVIGATION_HANDLE_USER_DATA_KEY_IMPL( |
| ContentFaviconDriver::NavigationManifestData); |
| DOCUMENT_USER_DATA_KEY_IMPL(ContentFaviconDriver::DocumentManifestData); |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(ContentFaviconDriver); |
| |
| } // namespace favicon |