| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtWebEngine module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "favicon_manager.h" |
| #include "type_conversion.h" |
| #include "web_contents_adapter_client.h" |
| #include "web_engine_settings.h" |
| |
| #include "base/bind.h" |
| #include "content/public/browser/favicon_status.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| #include "net/base/data_url.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkPixelRef.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace QtWebEngineCore { |
| |
| static inline bool isResourceUrl(const QUrl &url) |
| { |
| return !url.scheme().compare(QLatin1String("qrc")); |
| } |
| |
| static inline bool isDataUrl(const QUrl &url) |
| { |
| return !url.scheme().compare(QLatin1String(url::kDataScheme)); |
| } |
| |
| static inline unsigned area(const QSize &size) |
| { |
| return size.width() * size.height(); |
| } |
| |
| |
| FaviconManager::FaviconManager(content::WebContents *webContents, WebContentsAdapterClient *viewClient) |
| : m_webContents(webContents) |
| , m_viewClient(viewClient) |
| , m_candidateCount(0) |
| , m_weakFactory(new base::WeakPtrFactory<FaviconManager>(this)) |
| { |
| } |
| |
| FaviconManager::~FaviconManager() |
| { |
| } |
| |
| int FaviconManager::downloadIcon(const QUrl &url) |
| { |
| static const uint32_t maxSize = 256; |
| static int fakeId = 0; |
| int id; |
| |
| bool cached = m_icons.contains(url); |
| if (isResourceUrl(url) || isDataUrl(url) || cached) { |
| id = --fakeId; |
| m_pendingRequests.insert(id, url); |
| } else { |
| id = m_webContents->DownloadImage( |
| toGurl(url), |
| true, // is_favicon |
| maxSize, |
| false, // normal cache policy |
| base::Bind(&FaviconManager::iconDownloadFinished, m_weakFactory->GetWeakPtr())); |
| } |
| |
| Q_ASSERT(!m_inProgressRequests.contains(id)); |
| m_inProgressRequests.insert(id, url); |
| |
| return id; |
| } |
| |
| void FaviconManager::iconDownloadFinished(int id, |
| int status, |
| const GURL &url, |
| const std::vector<SkBitmap> &bitmaps, |
| const std::vector<gfx::Size> &original_bitmap_sizes) |
| { |
| Q_UNUSED(status); |
| Q_UNUSED(url); |
| Q_UNUSED(original_bitmap_sizes); |
| |
| storeIcon(id, toQIcon(bitmaps)); |
| } |
| |
| /* Pending requests are used to mark icons that are already downloaded (cached icons or icons |
| * stored in qrc). These requests are also stored in the m_inProgressRequests but the corresponding |
| * icons are stored in m_icons explicitly by this function. It is necessary to avoid |
| * m_inProgressRequests being emptied right before the next icon is added by a downloadIcon() call. |
| */ |
| void FaviconManager::downloadPendingRequests() |
| { |
| for (auto it = m_pendingRequests.cbegin(), end = m_pendingRequests.cend(); it != end; ++it) { |
| QIcon icon; |
| |
| QUrl requestUrl = it.value(); |
| if (!m_icons.contains(requestUrl)) { |
| if (isResourceUrl(requestUrl)) { |
| icon = QIcon(requestUrl.toString().remove(0, 3)); |
| } else if (isDataUrl(requestUrl)) { |
| std::string mime_type, char_set, data; |
| if (net::DataURL::Parse(toGurl(requestUrl), &mime_type, &char_set, &data) && !data.empty()) { |
| const unsigned char *src_data = reinterpret_cast<const unsigned char *>(data.data()); |
| QImage image = QImage::fromData(src_data, data.size()); |
| icon.addPixmap(QPixmap::fromImage(image).copy()); |
| } |
| } |
| } |
| |
| storeIcon(it.key(), icon); |
| } |
| |
| m_pendingRequests.clear(); |
| } |
| |
| void FaviconManager::storeIcon(int id, const QIcon &icon) |
| { |
| |
| // Icon download has been interrupted |
| if (!m_inProgressRequests.contains(id)) |
| return; |
| |
| QUrl requestUrl = m_inProgressRequests[id]; |
| FaviconInfo &faviconInfo = m_faviconInfoMap[requestUrl]; |
| |
| unsigned iconCount = 0; |
| if (!icon.isNull()) |
| iconCount = icon.availableSizes().count(); |
| |
| if (iconCount > 0) { |
| m_icons.insert(requestUrl, icon); |
| |
| faviconInfo.size = icon.availableSizes().at(0); |
| if (iconCount > 1) { |
| faviconInfo.multiSize = true; |
| unsigned bestArea = area(faviconInfo.size); |
| for (unsigned i = 1; i < iconCount; ++i) { |
| QSize iconSize = icon.availableSizes().at(i); |
| if (bestArea < area(iconSize)) { |
| faviconInfo.size = iconSize; |
| bestArea = area(iconSize); |
| } |
| } |
| } |
| } else if (id >= 0) { |
| // Reset size if icon cannot be downloaded |
| faviconInfo.size = QSize(0, 0); |
| } |
| |
| m_inProgressRequests.remove(id); |
| if (m_inProgressRequests.isEmpty()) { |
| WebEngineSettings *settings = m_viewClient->webEngineSettings(); |
| bool touchIconsEnabled = settings->testAttribute(WebEngineSettings::TouchIconsEnabled); |
| |
| generateCandidateIcon(touchIconsEnabled); |
| const QUrl &iconUrl = candidateIconUrl(touchIconsEnabled); |
| propagateIcon(iconUrl); |
| } |
| } |
| |
| void FaviconManager::propagateIcon(const QUrl &iconUrl) const |
| { |
| content::NavigationEntry *entry = m_webContents->GetController().GetVisibleEntry(); |
| if (entry) { |
| content::FaviconStatus &favicon = entry->GetFavicon(); |
| favicon.url = toGurl(iconUrl); |
| favicon.valid = true; |
| } |
| |
| m_viewClient->iconChanged(iconUrl); |
| } |
| |
| QIcon FaviconManager::getIcon(const QUrl &url) const |
| { |
| if (url.isEmpty()) |
| return m_candidateIcon; |
| |
| if (!m_icons.contains(url)) |
| return QIcon(); |
| |
| return m_icons[url]; |
| } |
| |
| FaviconInfo FaviconManager::getFaviconInfo(const QUrl &url) const |
| { |
| Q_ASSERT(m_faviconInfoMap.contains(url)); |
| return m_faviconInfoMap[url]; |
| } |
| |
| QList<FaviconInfo> FaviconManager::getFaviconInfoList(bool candidatesOnly) const |
| { |
| QList<FaviconInfo> faviconInfoList = m_faviconInfoMap.values(); |
| |
| if (candidatesOnly) { |
| const auto hasNoCandidate = [](const FaviconInfo &info) { return !info.candidate; }; |
| faviconInfoList.erase(std::remove_if(faviconInfoList.begin(), faviconInfoList.end(), |
| hasNoCandidate), |
| faviconInfoList.end()); |
| } |
| |
| return faviconInfoList; |
| } |
| |
| void FaviconManager::update(const QList<FaviconInfo> &candidates) |
| { |
| updateCandidates(candidates); |
| |
| WebEngineSettings *settings = m_viewClient->webEngineSettings(); |
| if (!settings->testAttribute(WebEngineSettings::AutoLoadIconsForPage)) { |
| m_viewClient->iconChanged(QUrl()); |
| return; |
| } |
| |
| bool touchIconsEnabled = settings->testAttribute(WebEngineSettings::TouchIconsEnabled); |
| |
| const QList<FaviconInfo> &faviconInfoList = getFaviconInfoList(true /* candidates only */); |
| for (auto it = faviconInfoList.cbegin(), end = faviconInfoList.cend(); it != end; ++it) { |
| if (!touchIconsEnabled && !(it->type & FaviconInfo::Favicon)) |
| continue; |
| |
| if (it->isValid()) |
| downloadIcon(it->url); |
| } |
| |
| downloadPendingRequests(); |
| |
| // Reset icon if nothing was downloaded |
| if (m_inProgressRequests.isEmpty()) { |
| content::NavigationEntry *entry = m_webContents->GetController().GetVisibleEntry(); |
| if (entry && !entry->GetFavicon().valid) |
| m_viewClient->iconChanged(QUrl()); |
| } |
| } |
| |
| void FaviconManager::updateCandidates(const QList<FaviconInfo> &candidates) |
| { |
| // Invalidate types of the already stored candidate icons because it might differ |
| // among pages. |
| for (FaviconInfo candidateFaviconInfo : candidates) { |
| const QUrl &candidateUrl = candidateFaviconInfo.url; |
| if (m_faviconInfoMap.contains(candidateUrl)) |
| m_faviconInfoMap[candidateUrl].type = FaviconInfo::InvalidIcon; |
| } |
| |
| m_candidateCount = candidates.count(); |
| for (FaviconInfo candidateFaviconInfo : candidates) { |
| const QUrl &candidateUrl = candidateFaviconInfo.url; |
| |
| if (!m_faviconInfoMap.contains(candidateUrl)) |
| m_faviconInfoMap.insert(candidateUrl, candidateFaviconInfo); |
| else { |
| // The same icon URL can be used for different types. |
| m_faviconInfoMap[candidateUrl].type |= candidateFaviconInfo.type; |
| } |
| |
| m_faviconInfoMap[candidateUrl].candidate = true; |
| } |
| } |
| |
| void FaviconManager::resetCandidates() |
| { |
| // Interrupt in progress icon downloads |
| m_pendingRequests.clear(); |
| m_inProgressRequests.clear(); |
| |
| m_candidateCount = 0; |
| m_candidateIcon = QIcon(); |
| for (auto it = m_faviconInfoMap.begin(), end = m_faviconInfoMap.end(); it != end; ++it) |
| it->candidate = false; |
| } |
| |
| bool FaviconManager::hasCandidate() const |
| { |
| return (m_candidateCount > 0); |
| } |
| |
| QUrl FaviconManager::candidateIconUrl(bool touchIconsEnabled) const |
| { |
| QUrl iconUrl; |
| const QList<FaviconInfo> &faviconInfoList = getFaviconInfoList(true /* candidates only */); |
| |
| unsigned bestArea = 0; |
| for (auto it = faviconInfoList.cbegin(), end = faviconInfoList.cend(); it != end; ++it) { |
| if (!touchIconsEnabled && !(it->type & FaviconInfo::Favicon)) |
| continue; |
| |
| if (it->isValid() && bestArea < area(it->size)) { |
| iconUrl = it->url; |
| bestArea = area(it->size); |
| } |
| } |
| |
| return iconUrl; |
| } |
| |
| void FaviconManager::generateCandidateIcon(bool touchIconsEnabled) |
| { |
| Q_ASSERT(m_candidateCount); |
| |
| m_candidateIcon = QIcon(); |
| const QList<FaviconInfo> &faviconInfoList = getFaviconInfoList(true /* candidates only */); |
| |
| for (auto it = faviconInfoList.cbegin(), end = faviconInfoList.cend(); it != end; ++it) { |
| if (!touchIconsEnabled && !(it->type & FaviconInfo::Favicon)) |
| continue; |
| |
| if (!it->isValid() || !it->isDownloaded()) |
| continue; |
| |
| const QIcon &icon = getIcon(it->url); |
| |
| if (!it->multiSize) { |
| if (!m_candidateIcon.availableSizes().contains(it->size)) |
| m_candidateIcon.addPixmap(icon.pixmap(it->size)); |
| |
| continue; |
| } |
| |
| const auto sizes = icon.availableSizes(); |
| for (const QSize &size : sizes) { |
| if (!m_candidateIcon.availableSizes().contains(size)) |
| m_candidateIcon.addPixmap(icon.pixmap(size)); |
| } |
| } |
| } |
| |
| void FaviconManager::copyStateFrom(FaviconManager *source) |
| { |
| m_faviconInfoMap = source->m_faviconInfoMap; |
| m_icons = source->m_icons; |
| } |
| |
| FaviconInfo::FaviconInfo() |
| : url(QUrl()) |
| , type(FaviconInfo::InvalidIcon) |
| , size(QSize(0, 0)) |
| , candidate(false) |
| , multiSize(false) |
| { |
| } |
| |
| FaviconInfo::FaviconInfo(const FaviconInfo &other) |
| : url(other.url) |
| , type(other.type) |
| , size(other.size) |
| , candidate(other.candidate) |
| , multiSize(other.multiSize) |
| { |
| } |
| |
| FaviconInfo::FaviconInfo(const QUrl &url, FaviconInfo::FaviconTypeFlags type) |
| : url(url) |
| , type(type) |
| , size(QSize(0, 0)) |
| , candidate(false) |
| , multiSize(false) |
| { |
| } |
| |
| FaviconInfo::~FaviconInfo() |
| { |
| } |
| |
| bool FaviconInfo::isValid() const |
| { |
| if (type == FaviconInfo::InvalidIcon) |
| return false; |
| |
| if (url.isEmpty() || !url.isValid()) |
| return false; |
| |
| return true; |
| } |
| |
| bool FaviconInfo::isDownloaded() const |
| { |
| return area(size) > 0; |
| } |
| |
| } // namespace QtWebEngineCore |