blob: a518f45e6bff174faa457ffeb69f7d09207ac2a7 [file] [log] [blame]
// 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