diff options
37 files changed, 1346 insertions, 46 deletions
diff --git a/src/core/core_gyp_generator.pro b/src/core/core_gyp_generator.pro index 80bf662d6..5e6641d51 100644 --- a/src/core/core_gyp_generator.pro +++ b/src/core/core_gyp_generator.pro @@ -53,6 +53,7 @@ SOURCES = \ desktop_screen_qt.cpp \ dev_tools_http_handler_delegate_qt.cpp \ download_manager_delegate_qt.cpp \ + favicon_manager.cpp \ file_picker_controller.cpp \ gl_context_qt.cpp \ gl_surface_qt.cpp \ @@ -130,6 +131,8 @@ HEADERS = \ dev_tools_http_handler_delegate_qt.h \ download_manager_delegate_qt.h \ chromium_gpu_helper.h \ + favicon_manager_p.h \ + favicon_manager.h \ file_picker_controller.h \ gl_context_qt.h \ gl_surface_qt.h \ diff --git a/src/core/favicon_manager.cpp b/src/core/favicon_manager.cpp new file mode 100644 index 000000000..f3260b3c4 --- /dev/null +++ b/src/core/favicon_manager.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** 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 "favicon_manager_p.h" + +#include "type_conversion.h" +#include "web_contents_adapter_client.h" + +#include "base/bind.h" +#include "content/public/browser/web_contents.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkPixelRef.h" +#include "ui/gfx/geometry/size.h" + +#include <QtCore/QUrl> +#include <QtGui/QIcon> + +namespace QtWebEngineCore { + +static inline bool isResourceUrl(const QUrl &url) +{ + return !url.scheme().compare(QLatin1String("qrc")); +} + +static inline unsigned area(const QSize &size) +{ + return size.width() * size.height(); +} + + +FaviconManagerPrivate::FaviconManagerPrivate(content::WebContents *webContents, WebContentsAdapterClient *viewClient) + : m_webContents(webContents) + , m_viewClient(viewClient) + , m_weakFactory(this) +{ +} + +FaviconManagerPrivate::~FaviconManagerPrivate() +{ +} + +int FaviconManagerPrivate::downloadIcon(const QUrl &url, bool candidate) +{ + static int fakeId = 0; + int id; + + bool cached = candidate && m_icons.contains(url); + if (isResourceUrl(url) || cached) { + id = --fakeId; + m_pendingRequests.insert(id, url); + } else { + id = m_webContents->DownloadImage( + toGurl(url), + true, // is_favicon + 0, // no max size + false, // normal cache policy + base::Bind(&FaviconManagerPrivate::iconDownloadFinished, m_weakFactory.GetWeakPtr())); + } + + if (candidate) { + Q_ASSERT(!m_inProgressCandidateRequests.contains(id)); + m_inProgressCandidateRequests.insert(id, url); + } else { + Q_ASSERT(!m_inProgressCustomRequests.contains(id)); + m_inProgressCustomRequests.insert(id, url); + } + + return id; +} + +void FaviconManagerPrivate::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 as a workaround for avoiding signal iconChanged when + * accessing each cached icons or icons stored in qrc. They don't have to be downloaded + * thus the m_inProgressCustomRequests maybe emptied right before the next icon is added to + * in-progress-requests queue. The m_pendingRequests stores these requests until all + * candidates are added to the queue then pending requests should be cleaned up by this + * function. + */ +void FaviconManagerPrivate::downloadPendingRequests() +{ + Q_FOREACH (int id, m_pendingRequests.keys()) { + QIcon icon; + + QUrl requestUrl = m_pendingRequests[id]; + if (isResourceUrl(requestUrl) && !m_icons.contains(requestUrl)) + icon = QIcon(requestUrl.toString().remove(0, 3)); + + storeIcon(id, icon); + } + + m_pendingRequests.clear(); +} + +void FaviconManagerPrivate::storeIcon(int id, const QIcon &icon) +{ + Q_Q(FaviconManager); + + bool candidate = m_inProgressCandidateRequests.contains(id); + + QUrl requestUrl = candidate ? m_inProgressCandidateRequests[id] : m_inProgressCustomRequests[id]; + FaviconInfo &faviconInfo = q->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); + } + } + } + + q->m_hasDownloadedIcon = true; + } else if (id < 0) { + // Icon is cached + q->m_hasDownloadedIcon = true; + } else { + // Reset size if icon cannot be downloaded + faviconInfo.size = QSize(0, 0); + } + + if (candidate) { + m_inProgressCandidateRequests.remove(id); + if (m_inProgressCandidateRequests.isEmpty()) + m_viewClient->iconChanged(q->getProposedFaviconInfo().url); + } else { + m_inProgressCustomRequests.remove(id); + } + + Q_EMIT q->iconDownloaded(requestUrl); +} + +FaviconManager::FaviconManager(FaviconManagerPrivate *d) + : m_hasDownloadedIcon(false) +{ + Q_ASSERT(d); + d_ptr.reset(d); + + d->q_ptr = this; +} + +FaviconManager::~FaviconManager() +{ +} + +QIcon FaviconManager::getIcon(const QUrl &url) const +{ + Q_D(const FaviconManager); + return d->m_icons[url]; +} + +FaviconInfo FaviconManager::getFaviconInfo(const QUrl &url) const +{ + return m_faviconInfoMap[url]; +} + +QList<FaviconInfo> FaviconManager::getFaviconInfoList(bool candidatesOnly) const +{ + QList<FaviconInfo> faviconInfoList = m_faviconInfoMap.values(); + + if (candidatesOnly) { + QMutableListIterator<FaviconInfo> it(faviconInfoList); + while (it.hasNext()) { + if (!it.next().candidate) + it.remove(); + } + } + + return faviconInfoList; +} + + +void FaviconManager::downloadIcon(const QUrl &url, FaviconInfo::FaviconType iconType) +{ + Q_D(FaviconManager); + + // If the favicon cannot be found in the list that means that it is not a candidate + // for any visited page (including the current one). In this case the type of the icon + // is unknown: it has to be specified explicitly. + if (!m_faviconInfoMap.contains(url)) { + FaviconInfo newFaviconInfo(url, iconType); + m_faviconInfoMap.insert(url, newFaviconInfo); + } + + d->downloadIcon(url, false); + d->downloadPendingRequests(); +} + +void FaviconManager::removeIcon(const QUrl &url) +{ + Q_D(FaviconManager); + int removed = d->m_icons.remove(url); + + if (removed) { + Q_ASSERT(removed == 1); + Q_ASSERT(m_faviconInfoMap.contains(url)); + m_faviconInfoMap[url].size = QSize(0, 0); + } +} + +bool FaviconManager::hasAvailableCandidateIcon() const +{ + Q_D(const FaviconManager); + return m_hasDownloadedIcon || !d->m_inProgressCandidateRequests.isEmpty(); +} + +void FaviconManager::update(QList<FaviconInfo> &candidates) +{ + Q_D(FaviconManager); + updateCandidates(candidates); + + Q_FOREACH (FaviconInfo faviconInfo, m_faviconInfoMap.values()) { + if (!faviconInfo.candidate || faviconInfo.type != FaviconInfo::Favicon) + continue; + + if (faviconInfo.isValid()) + d->downloadIcon(faviconInfo.url, true); + } + + d->downloadPendingRequests(); +} + +void FaviconManager::updateCandidates(QList<FaviconInfo> &candidates) +{ + Q_FOREACH (FaviconInfo candidateFaviconInfo, candidates) { + QUrl candidateUrl = candidateFaviconInfo.url; + if (m_faviconInfoMap.contains(candidateUrl)) { + m_faviconInfoMap[candidateUrl].candidate = true; + // Update type in case of the icon was downloaded manually + m_faviconInfoMap[candidateUrl].type = candidateFaviconInfo.type; + continue; + } + + candidateFaviconInfo.candidate = true; + m_faviconInfoMap.insert(candidateUrl, candidateFaviconInfo); + } +} + +void FaviconManager::resetCandidates() +{ + m_hasDownloadedIcon = false; + Q_FOREACH (const QUrl key, m_faviconInfoMap.keys()) + m_faviconInfoMap[key].candidate = false; +} + + +FaviconInfo FaviconManager::getProposedFaviconInfo() const +{ + FaviconInfo proposedFaviconInfo = getFirstFaviconInfo(); + + // If nothing has been downloaded yet return the first favicon + // if there is available for dev-tools + if (!m_hasDownloadedIcon) + return proposedFaviconInfo; + + unsigned bestArea = area(proposedFaviconInfo.size); + Q_FOREACH (const FaviconInfo faviconInfo, m_faviconInfoMap.values()) { + if (!faviconInfo.candidate || faviconInfo.type != FaviconInfo::Favicon) + continue; + + if (faviconInfo.isValid() && bestArea < area(faviconInfo.size)) { + proposedFaviconInfo = faviconInfo; + bestArea = area(proposedFaviconInfo.size); + } + } + + return proposedFaviconInfo; +} + +FaviconInfo FaviconManager::getFirstFaviconInfo() const +{ + Q_FOREACH (const FaviconInfo faviconInfo, m_faviconInfoMap.values()) { + if (!faviconInfo.candidate || faviconInfo.type != FaviconInfo::Favicon) + continue; + + if (faviconInfo.isValid()) + return faviconInfo; + } + + return FaviconInfo(); +} + + + +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) +{ +} + +FaviconInfo::FaviconInfo(const QUrl &url, FaviconInfo::FaviconType 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 diff --git a/src/core/favicon_manager.h b/src/core/favicon_manager.h new file mode 100644 index 000000000..eaae8123f --- /dev/null +++ b/src/core/favicon_manager.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FAVICON_MANAGER_H +#define FAVICON_MANAGER_H + +#include "qtwebenginecoreglobal.h" + +#include <QtCore/QMap> +#include <QtCore/QObject> +#include <QtCore/QSize> +#include <QtCore/QUrl> + +#include "web_engine_settings.h" + +namespace QtWebEngineCore { + +class FaviconManagerPrivate; + +// Based on src/3rdparty/chromium/content/public/common/favicon_url.h +class QWEBENGINE_EXPORT FaviconInfo { +public: + enum FaviconType { + InvalidIcon, + Favicon, + TouchIcon, + TouchPrecomposedIcon + }; + + FaviconInfo(); + FaviconInfo(const FaviconInfo &); + FaviconInfo(const QUrl &, FaviconInfo::FaviconType); + ~FaviconInfo(); + + bool isValid() const; + bool isDownloaded() const; + + QUrl url; + FaviconType type; + // Stores the size of the highest quality in case of multi-size icon + QSize size; + bool candidate; + bool multiSize; +}; + + +class QWEBENGINE_EXPORT FaviconManager : public QObject { + Q_OBJECT +public: + ~FaviconManager(); + + QIcon getIcon(const QUrl &) const; + FaviconInfo getFaviconInfo(const QUrl &) const; + QList<FaviconInfo> getFaviconInfoList(bool) const; + void downloadIcon(const QUrl &url, FaviconInfo::FaviconType iconType = FaviconInfo::Favicon); + void removeIcon(const QUrl &); + +Q_SIGNALS: + void iconDownloaded(const QUrl &url); + +private: + FaviconManager(FaviconManagerPrivate *); + + bool hasAvailableCandidateIcon() const; + void update(QList<FaviconInfo> &); + void updateCandidates(QList<FaviconInfo> &); + void resetCandidates(); + + FaviconInfo getProposedFaviconInfo() const; + FaviconInfo getFirstFaviconInfo() const; + + QMap<QUrl, FaviconInfo> m_faviconInfoMap; + bool m_hasDownloadedIcon; + + Q_DISABLE_COPY(FaviconManager) + Q_DECLARE_PRIVATE(FaviconManager) + QScopedPointer<FaviconManagerPrivate> d_ptr; + + friend class WebContentsDelegateQt; +}; + +} // namespace QtWebEngineCore + +#endif // FAVICON_MANAGER_H diff --git a/src/core/favicon_manager_p.h b/src/core/favicon_manager_p.h new file mode 100644 index 000000000..8358245a2 --- /dev/null +++ b/src/core/favicon_manager_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FAVICON_MANAGER_P_H +#define FAVICON_MANAGER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qtwebenginecoreglobal_p.h" + +#include <QtCore/QMap> +#include <QtCore/QObject> +#include <vector> + +#include "base/memory/weak_ptr.h" + +QT_FORWARD_DECLARE_CLASS(QUrl) + +class GURL; +class SkBitmap; + +namespace gfx { + class Size; +} + +namespace content { + class WebContents; +} + +namespace QtWebEngineCore { + +class FaviconManager; +class WebContentsAdapterClient; + +class FaviconManagerPrivate { +public: + FaviconManagerPrivate(content::WebContents *, WebContentsAdapterClient *); + ~FaviconManagerPrivate(); + + int downloadIcon(const QUrl &, bool); + + void iconDownloadFinished(int, int, const GURL &, const std::vector<SkBitmap> &, const std::vector<gfx::Size> &); + void storeIcon(int, const QIcon &); + void downloadPendingRequests(); + + content::WebContents *m_webContents; + WebContentsAdapterClient *m_viewClient; + base::WeakPtrFactory<FaviconManagerPrivate> m_weakFactory; + + QMap<QUrl, QIcon> m_icons; + QMap<int, QUrl> m_inProgressCandidateRequests; + QMap<int, QUrl> m_inProgressCustomRequests; + QMap<int, QUrl> m_pendingRequests; + + Q_DECLARE_PUBLIC(FaviconManager) + FaviconManager *q_ptr; +}; + +} // namespace QtWebEngineCore + +#endif // FAVICON_MANAGER_P_H diff --git a/src/core/type_conversion.cpp b/src/core/type_conversion.cpp index ac458df31..ccfc2fdc6 100644 --- a/src/core/type_conversion.cpp +++ b/src/core/type_conversion.cpp @@ -128,6 +128,23 @@ QImage toQImage(const gfx::ImageSkiaRep &imageSkiaRep) return image; } +QIcon toQIcon(const std::vector<SkBitmap> &bitmaps) +{ + if (!bitmaps.size()) + return QIcon(); + + QIcon icon; + + for (unsigned i = 0; i < bitmaps.size(); ++i) { + SkBitmap bitmap = bitmaps[i]; + QImage image = toQImage(bitmap); + + icon.addPixmap(QPixmap::fromImage(image).copy()); + } + + return icon; +} + int flagsFromModifiers(Qt::KeyboardModifiers modifiers) { int modifierFlags = ui::EF_NONE; @@ -152,5 +169,33 @@ int flagsFromModifiers(Qt::KeyboardModifiers modifiers) return modifierFlags; } +FaviconInfo toFaviconInfo(const content::FaviconURL &favicon_url) +{ + FaviconInfo info; + + info.url = toQt(favicon_url.icon_url); + + switch (favicon_url.icon_type) { + case content::FaviconURL::FAVICON: + info.type = FaviconInfo::Favicon; + break; + case content::FaviconURL::TOUCH_ICON: + info.type = FaviconInfo::TouchIcon; + break; + case content::FaviconURL::TOUCH_PRECOMPOSED_ICON: + info.type = FaviconInfo::TouchPrecomposedIcon; + break; + default: + info.type = FaviconInfo::InvalidIcon; + break; + } + + // TODO: Add support for rel sizes attribute (favicon_url.icon_sizes): + // http://www.w3schools.com/tags/att_link_sizes.asp + info.size = QSize(0, 0); + + return info; +} + } // namespace QtWebEngineCore diff --git a/src/core/type_conversion.h b/src/core/type_conversion.h index 0f3357948..8789cf2b7 100644 --- a/src/core/type_conversion.h +++ b/src/core/type_conversion.h @@ -43,6 +43,7 @@ #include <QColor> #include <QDateTime> #include <QDir> +#include <QIcon> #include <QImage> #include <QMatrix4x4> #include <QNetworkCookie> @@ -53,6 +54,8 @@ #include "base/files/file_path.h" #include "base/time/time.h" #include "content/public/common/file_chooser_file_info.h" +#include "content/public/common/favicon_url.h" +#include "favicon_manager.h" #include "net/cookies/canonical_cookie.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColor.h" @@ -164,6 +167,7 @@ inline QImage toQImage(const SkBitmap &bitmap, QImage::Format format) QImage toQImage(const SkBitmap &bitmap); QImage toQImage(const gfx::ImageSkiaRep &imageSkiaRep); +QIcon toQIcon(const std::vector<SkBitmap> &bitmaps); inline QMatrix4x4 toQt(const SkMatrix44 &m) { @@ -261,6 +265,8 @@ inline QStringList fromVector(const std::vector<base::string16> &vector) } #endif +FaviconInfo toFaviconInfo(const content::FaviconURL &); + } // namespace QtWebEngineCore #endif // TYPE_CONVERSION_H diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index bf36a771c..eb4436018 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -1207,4 +1207,10 @@ WebContentsAdapterClient::renderProcessExitStatus(int terminationStatus) { return status; } +FaviconManager *WebContentsAdapter::faviconManager() +{ + Q_D(WebContentsAdapter); + return d->webContentsDelegate->faviconManager(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index e6aef23ec..c7c2c1edf 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -65,6 +65,7 @@ namespace QtWebEngineCore { class BrowserContextQt; class MessagePassingInterface; class WebContentsAdapterPrivate; +class FaviconManager; class QWEBENGINE_EXPORT WebContentsAdapter : public QSharedData { public: @@ -154,6 +155,7 @@ public: BrowserContextAdapter* browserContextAdapter(); QWebChannel *webChannel() const; void setWebChannel(QWebChannel *, uint worldId); + FaviconManager *faviconManager(); QPointF lastScrollOffset() const; QSizeF lastContentsSize() const; diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 95066a065..a3d2d816e 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -46,6 +46,8 @@ #include "browser_context_adapter.h" #include "color_chooser_qt.h" #include "color_chooser_controller.h" +#include "favicon_manager.h" +#include "favicon_manager_p.h" #include "file_picker_controller.h" #include "media_capture_devices_dispatcher.h" #include "network_delegate_qt.h" @@ -89,6 +91,7 @@ static WebContentsAdapterClient::JavaScriptConsoleMessageLevel mapToJavascriptCo WebContentsDelegateQt::WebContentsDelegateQt(content::WebContents *webContents, WebContentsAdapterClient *adapterClient) : m_viewClient(adapterClient) , m_lastReceivedFindReply(0) + , m_faviconManager(new FaviconManager(new FaviconManagerPrivate(webContents, adapterClient))) { webContents->SetDelegate(this); Observe(webContents); @@ -175,8 +178,10 @@ void WebContentsDelegateQt::DidStartProvisionalLoadForFrame(content::RenderFrame m_loadingErrorFrameList.append(render_frame_host->GetRoutingID()); // Trigger LoadStarted signal for main frame's error page only. - if (!render_frame_host->GetParent()) + if (!render_frame_host->GetParent()) { + m_faviconManager->resetCandidates(); m_viewClient->loadStarted(toQt(validated_url), true); + } return; } @@ -185,6 +190,7 @@ void WebContentsDelegateQt::DidStartProvisionalLoadForFrame(content::RenderFrame return; m_loadingErrorFrameList.clear(); + m_faviconManager->resetCandidates(); m_viewClient->loadStarted(toQt(validated_url)); } @@ -208,6 +214,7 @@ void WebContentsDelegateQt::DidFailLoad(content::RenderFrameHost* render_frame_h if (m_loadingErrorFrameList.removeOne(render_frame_host->GetRoutingID()) || render_frame_host->GetParent()) return; + m_viewClient->iconChanged(QUrl()); m_viewClient->loadFinished(false /* success */ , toQt(validated_url), false /* isErrorPage */, error_code, toQt(error_description)); m_viewClient->loadProgressChanged(0); } @@ -230,28 +237,31 @@ void WebContentsDelegateQt::DidFinishLoad(content::RenderFrameHost* render_frame m_viewClient->loadFinished(true, toQt(validated_url)); - content::NavigationEntry *entry = web_contents()->GetController().GetActiveEntry(); + content::NavigationEntry *entry = web_contents()->GetController().GetVisibleEntry(); if (!entry) return; - content::FaviconStatus &favicon = entry->GetFavicon(); - if (favicon.valid) - m_viewClient->iconChanged(toQt(favicon.url)); - else + + // No available icon for the current entry + if (!entry->GetFavicon().valid && !m_faviconManager->hasAvailableCandidateIcon()) m_viewClient->iconChanged(QUrl()); } -void WebContentsDelegateQt::DidUpdateFaviconURL(const std::vector<content::FaviconURL>& candidates) +void WebContentsDelegateQt::DidUpdateFaviconURL(const std::vector<content::FaviconURL> &candidates) { + QList<FaviconInfo> faviconCandidates; Q_FOREACH (content::FaviconURL candidate, candidates) { - if (candidate.icon_type == content::FaviconURL::FAVICON && !candidate.icon_url.is_empty()) { - content::NavigationEntry *entry = web_contents()->GetController().GetActiveEntry(); - if (!entry) - continue; - content::FaviconStatus &favicon = entry->GetFavicon(); - favicon.url = candidate.icon_url; - favicon.valid = toQt(candidate.icon_url).isValid(); - break; - } + // Store invalid candidates too for later debugging via API + faviconCandidates.append(toFaviconInfo(candidate)); + } + + m_faviconManager->update(faviconCandidates); + + content::NavigationEntry *entry = web_contents()->GetController().GetVisibleEntry(); + if (entry) { + FaviconInfo proposedFaviconInfo = m_faviconManager->getProposedFaviconInfo(); + content::FaviconStatus &favicon = entry->GetFavicon(); + favicon.url = toGurl(proposedFaviconInfo.url); + favicon.valid = proposedFaviconInfo.isValid(); } } @@ -426,4 +436,9 @@ void WebContentsDelegateQt::BeforeUnloadFired(content::WebContents *tab, bool pr m_viewClient->windowCloseRejected(); } +FaviconManager *WebContentsDelegateQt::faviconManager() +{ + return m_faviconManager.data(); +} + } // namespace QtWebEngineCore diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index d646a1322..afa4030d0 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -48,6 +48,7 @@ #include "base/callback.h" #include "color_chooser_controller.h" +#include "favicon_manager.h" #include "javascript_dialog_manager_qt.h" #include <QtCore/qvector.h> #include <QtCore/qcompilerdetection.h> @@ -119,6 +120,7 @@ public: void allowCertificateError(const QSharedPointer<CertificateErrorController> &) ; void requestGeolocationPermission(const QUrl &requestingOrigin); void launchExternalURL(const QUrl &url, ui::PageTransition page_transition, bool is_main_frame); + FaviconManager *faviconManager(); private: WebContentsAdapter *createWindow(content::WebContents *new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture); @@ -127,6 +129,7 @@ private: QString m_lastSearchedString; int m_lastReceivedFindReply; QVector<int64> m_loadingErrorFrameList; + QScopedPointer<FaviconManager> m_faviconManager; }; } // namespace QtWebEngineCore diff --git a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp index a65ffb868..180953ed4 100644 --- a/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp +++ b/tests/auto/core/qwebengineurlrequestinterceptor/tst_qwebengineurlrequestinterceptor.cpp @@ -150,6 +150,9 @@ public: void interceptRequest(QWebEngineUrlRequestInfo &info) override { + if (info.resourceType() == QWebEngineUrlRequestInfo::ResourceTypeFavicon) + return; + requestedUrls.append(info.requestUrl()); info.redirect(QUrl("data:text/html,<p>hello")); } diff --git a/tests/auto/quick/qmltests/data/favicon-misc.html b/tests/auto/quick/qmltests/data/favicon-misc.html new file mode 100644 index 000000000..9e788bdf4 --- /dev/null +++ b/tests/auto/quick/qmltests/data/favicon-misc.html @@ -0,0 +1,11 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + <link rel="apple-touch-icon" href="icons/qt144.png" /> + <link rel="shortcut icon" href="icons/unavailable.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/quick/qmltests/data/favicon-shortcut.html b/tests/auto/quick/qmltests/data/favicon-shortcut.html new file mode 100644 index 000000000..786cdb816 --- /dev/null +++ b/tests/auto/quick/qmltests/data/favicon-shortcut.html @@ -0,0 +1,10 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + <link rel="shortcut icon" href="icons/qt144.png" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/quick/qmltests/data/favicon-single.html b/tests/auto/quick/qmltests/data/favicon-single.html new file mode 100644 index 000000000..eb4675c75 --- /dev/null +++ b/tests/auto/quick/qmltests/data/favicon-single.html @@ -0,0 +1,9 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/quick/qmltests/data/favicon-touch.html b/tests/auto/quick/qmltests/data/favicon-touch.html new file mode 100644 index 000000000..271783434 --- /dev/null +++ b/tests/auto/quick/qmltests/data/favicon-touch.html @@ -0,0 +1,10 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="apple-touch-icon" href="icons/qt32.ico" /> + <link rel="apple-touch-icon" href="icons/qt144.png" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/quick/qmltests/data/favicon-unavailable.html b/tests/auto/quick/qmltests/data/favicon-unavailable.html new file mode 100644 index 000000000..c45664294 --- /dev/null +++ b/tests/auto/quick/qmltests/data/favicon-unavailable.html @@ -0,0 +1,9 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/unavailable.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/quick/qmltests/data/favicon.html b/tests/auto/quick/qmltests/data/favicon.html index c9f225c52..9823fa323 100644 --- a/tests/auto/quick/qmltests/data/favicon.html +++ b/tests/auto/quick/qmltests/data/favicon.html @@ -1,10 +1,10 @@ <html> <head> </head> -<link type="image/png" href="favicon.png" sizes="48x48" rel="icon" /> +<link type="image/png" href="icons/favicon.png" sizes="48x48" rel="icon" /> <body> <p>It's expected that you see a favicon displayed for this page when you open it as a local file.</p> <p>The favicon looks like this:</p> -<img src="favicon.png"/> +<img src="icons/favicon.png"/> </body> </html> diff --git a/tests/auto/quick/qmltests/data/favicon2.html b/tests/auto/quick/qmltests/data/favicon2.html index 5548b867f..81c2690fe 100644 --- a/tests/auto/quick/qmltests/data/favicon2.html +++ b/tests/auto/quick/qmltests/data/favicon2.html @@ -1,10 +1,10 @@ <html> <head> </head> -<link type="image/png" href="small-favicon.png" sizes="16x16" rel="icon" /> +<link type="image/png" href="icons/small-favicon.png" sizes="16x16" rel="icon" /> <body> <p>It's expected that you see a favicon displayed for this page when you open it as a local file.</p> <p>The favicon looks like this:</p> -<img src="small-favicon.png"/> +<img src="icons/small-favicon.png"/> </body> </html> diff --git a/tests/auto/quick/qmltests/data/favicon.png b/tests/auto/quick/qmltests/data/icons/favicon.png Binary files differindex 35717cca5..35717cca5 100644 --- a/tests/auto/quick/qmltests/data/favicon.png +++ b/tests/auto/quick/qmltests/data/icons/favicon.png diff --git a/tests/auto/quick/qmltests/data/icons/qt144.png b/tests/auto/quick/qmltests/data/icons/qt144.png Binary files differnew file mode 100644 index 000000000..050b1e066 --- /dev/null +++ b/tests/auto/quick/qmltests/data/icons/qt144.png diff --git a/tests/auto/quick/qmltests/data/icons/qt32.ico b/tests/auto/quick/qmltests/data/icons/qt32.ico Binary files differnew file mode 100644 index 000000000..2f6fcb5bc --- /dev/null +++ b/tests/auto/quick/qmltests/data/icons/qt32.ico diff --git a/tests/auto/quick/qmltests/data/small-favicon.png b/tests/auto/quick/qmltests/data/icons/small-favicon.png Binary files differindex 4462752a5..4462752a5 100644 --- a/tests/auto/quick/qmltests/data/small-favicon.png +++ b/tests/auto/quick/qmltests/data/icons/small-favicon.png diff --git a/tests/auto/quick/qmltests/data/tst_favicon.qml b/tests/auto/quick/qmltests/data/tst_favicon.qml new file mode 100644 index 000000000..fab2e9755 --- /dev/null +++ b/tests/auto/quick/qmltests/data/tst_favicon.qml @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtTest 1.0 +import QtWebEngine 1.3 +import QtWebEngine.testsupport 1.0 + +TestWebEngineView { + id: webEngineView + width: 200 + height: 400 + + testSupport: WebEngineTestSupport { + property var errorPageLoadStatus: null + + function waitForErrorPageLoadSucceeded() { + var success = _waitFor(function() { return testSupport.errorPageLoadStatus == WebEngineView.LoadSucceededStatus }) + testSupport.errorPageLoadStatus = null + return success + } + + errorPage.onLoadingChanged: { + errorPageLoadStatus = loadRequest.status + } + } + + SignalSpy { + id: iconChangedSpy + target: webEngineView + signalName: "iconChanged" + } + + TestCase { + id: test + name: "WebEngineFavicon" + when: windowShown + + function init() { + if (webEngineView.icon != '') { + // If this is not the first test, then load a blank page without favicon, restoring the initial state. + webEngineView.url = 'about:blank' + verify(webEngineView.waitForLoadSucceeded()) + iconChangedSpy.wait() + } + + iconChangedSpy.clear() + } + + function test_noFavicon() { + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("test1.html") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("")) + } + + function test_aboutBlank() { + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("about:blank") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("")) + } + + function test_unavailableFavicon() { + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("favicon-unavailable.html") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("icons/unavailable.ico")) + } + + function test_errorPageEnabled() { + skip("Error page does not work properly: QTBUG-48995") + WebEngine.settings.errorPageEnabled = true + + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("http://non.existent/url") + webEngineView.url = url + verify(webEngineView.testSupport.waitForErrorPageLoadSucceeded()) + + iconChangedSpy.wait() + // Icon is reseted at load start. + // Load is started twice: once for unavailale page then error page + compare(iconChangedSpy.count, 2) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("")) + } + + function test_errorPageDisabled() { + WebEngine.settings.errorPageEnabled = false + + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("http://non.existent/url") + webEngineView.url = url + verify(webEngineView.waitForLoadFailed()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("")) + } + + function test_touchIcon() { + compare(iconChangedSpy.count, 0) + + var url = Qt.resolvedUrl("favicon-touch.html") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + var iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("")) + } + } +} diff --git a/tests/auto/quick/qmltests/data/tst_favIconLoad.qml b/tests/auto/quick/qmltests/data/tst_faviconImage.qml index 2527cc740..603f76954 100644 --- a/tests/auto/quick/qmltests/data/tst_favIconLoad.qml +++ b/tests/auto/quick/qmltests/data/tst_faviconImage.qml @@ -28,7 +28,7 @@ import QtQuick 2.0 import QtTest 1.0 -import QtWebEngine 1.2 +import QtWebEngine 1.3 TestWebEngineView { id: webEngineView @@ -36,25 +36,19 @@ TestWebEngineView { height: 400 SignalSpy { - id: spy + id: iconChangedSpy target: webEngineView signalName: "iconChanged" } - // FIXME: This test is flaky if the loading of the icon image is asynchronous, - // because the iconChanged signal is emitted before the image has been downloaded. - // We can set this property to true after we have some kind of favicon downloading - // logic in the WebEngine. - Image { - id: favicon - asynchronous: false + id: faviconImage source: webEngineView.icon } TestCase { id: test - name: "WebEngineViewLoadFavIcon" + name: "WebEngineFaviconImage" when: windowShown function init() { @@ -62,31 +56,70 @@ TestWebEngineView { // If this is not the first test, then load a blank page without favicon, restoring the initial state. webEngineView.url = 'about:blank' verify(webEngineView.waitForLoadSucceeded()) - spy.wait() + iconChangedSpy.wait() } - spy.clear() + + iconChangedSpy.clear() } - function test_favIconLoad() { - compare(spy.count, 0) + function test_faviconImageLoad() { + compare(iconChangedSpy.count, 0) + var url = Qt.resolvedUrl("favicon.html") webEngineView.url = url verify(webEngineView.waitForLoadSucceeded()) - spy.wait() - compare(spy.count, 1) - compare(favicon.width, 48) - compare(favicon.height, 48) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + compare(faviconImage.width, 48) + compare(faviconImage.height, 48) } - function test_favIconLoadEncodedUrl() { - compare(spy.count, 0) + function test_faviconImageLoadEncodedUrl() { + compare(iconChangedSpy.count, 0) + var url = Qt.resolvedUrl("favicon2.html?favicon=load should work with#whitespace!") webEngineView.url = url verify(webEngineView.waitForLoadSucceeded()) - spy.wait() - compare(spy.count, 1) - compare(favicon.width, 16) - compare(favicon.height, 16) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + compare(faviconImage.width, 16) + compare(faviconImage.height, 16) + } + + function test_bestFaviconImage() { + compare(iconChangedSpy.count, 0) + var url, iconUrl + + url = Qt.resolvedUrl("favicon-misc.html") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + iconUrl = webEngineView.icon + // Touch icon is ignored + compare(iconUrl, Qt.resolvedUrl("icons/qt32.ico")) + compare(faviconImage.width, 32) + compare(faviconImage.height, 32) + + iconChangedSpy.clear() + + url = Qt.resolvedUrl("favicon-shortcut.html") + webEngineView.url = url + verify(webEngineView.waitForLoadSucceeded()) + + iconChangedSpy.wait() + compare(iconChangedSpy.count, 1) + + iconUrl = webEngineView.icon + compare(iconUrl, Qt.resolvedUrl("icons/qt144.png")) + compare(faviconImage.width, 144) + compare(faviconImage.height, 144) } } } diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro index 57649384d..d1849d020 100644 --- a/tests/auto/quick/qmltests/qmltests.pro +++ b/tests/auto/quick/qmltests/qmltests.pro @@ -15,8 +15,12 @@ OTHER_FILES += \ $$PWD/data/confirmclose.html \ $$PWD/data/directoryupload.html \ $$PWD/data/favicon.html \ - $$PWD/data/favicon.png \ $$PWD/data/favicon2.html \ + $$PWD/data/favicon-misc.html \ + $$PWD/data/favicon-single.html \ + $$PWD/data/favicon-shortcut.html \ + $$PWD/data/favicon-touch.html \ + $$PWD/data/favicon-unavailable.html \ $$PWD/data/forms.html \ $$PWD/data/geolocation.html \ $$PWD/data/javascript.html \ @@ -34,7 +38,8 @@ OTHER_FILES += \ $$PWD/data/titleupdate.js \ $$PWD/data/tst_desktopBehaviorLoadHtml.qml \ $$PWD/data/tst_download.qml \ - $$PWD/data/tst_favIconLoad.qml \ + $$PWD/data/tst_favicon.qml \ + $$PWD/data/tst_faviconImage.qml \ $$PWD/data/tst_filePicker.qml \ $$PWD/data/tst_formValidation.qml \ $$PWD/data/tst_geopermission.qml \ @@ -55,6 +60,10 @@ OTHER_FILES += \ $$PWD/data/tst_userScripts.qml \ $$PWD/data/tst_webchannel.qml \ $$PWD/data/tst_keyboardModifierMapping.qml \ + $$PWD/data/icons/favicon.png \ + $$PWD/data/icons/small-favicon.png \ + $$PWD/data/icons/qt144.png \ + $$PWD/data/icons/qt32.ico \ $$PWD/mock-delegates/QtWebEngine/UIDelegates/AlertDialog.qml \ $$PWD/mock-delegates/QtWebEngine/UIDelegates/ConfirmDialog.qml \ $$PWD/mock-delegates/QtWebEngine/UIDelegates/FilePicker.qml \ diff --git a/tests/auto/widgets/qwebenginefaviconmanager/qwebenginefaviconmanager.pro b/tests/auto/widgets/qwebenginefaviconmanager/qwebenginefaviconmanager.pro new file mode 100644 index 000000000..70786e70f --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/qwebenginefaviconmanager.pro @@ -0,0 +1,2 @@ +include(../tests.pri) +QT *= core-private gui-private diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-misc.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-misc.html new file mode 100644 index 000000000..9e788bdf4 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-misc.html @@ -0,0 +1,11 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + <link rel="apple-touch-icon" href="icons/qt144.png" /> + <link rel="shortcut icon" href="icons/unavailable.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-shortcut.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-shortcut.html new file mode 100644 index 000000000..786cdb816 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-shortcut.html @@ -0,0 +1,10 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + <link rel="shortcut icon" href="icons/qt144.png" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-single.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-single.html new file mode 100644 index 000000000..eb4675c75 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-single.html @@ -0,0 +1,9 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/qt32.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-touch.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-touch.html new file mode 100644 index 000000000..271783434 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-touch.html @@ -0,0 +1,10 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="apple-touch-icon" href="icons/qt32.ico" /> + <link rel="apple-touch-icon" href="icons/qt144.png" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-unavailable.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-unavailable.html new file mode 100644 index 000000000..c45664294 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/favicon-unavailable.html @@ -0,0 +1,9 @@ +<html> + <head> + <title>Favicon Test</title> + <link rel="shortcut icon" href="icons/unavailable.ico" /> + </head> + <body> + <h1>Favicon Test</h1> + </body> +</html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt144.png b/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt144.png Binary files differnew file mode 100644 index 000000000..050b1e066 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt144.png diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt32.ico b/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt32.ico Binary files differnew file mode 100644 index 000000000..2f6fcb5bc --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/icons/qt32.ico diff --git a/tests/auto/widgets/qwebenginefaviconmanager/resources/test1.html b/tests/auto/widgets/qwebenginefaviconmanager/resources/test1.html new file mode 100644 index 000000000..b323f966e --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/resources/test1.html @@ -0,0 +1 @@ +<html><body><p>Some text 1</p></body></html> diff --git a/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.cpp b/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.cpp new file mode 100644 index 000000000..ececb0efd --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.cpp @@ -0,0 +1,279 @@ +/**************************************************************************** +** +** 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:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include "../util.h" + +#include <qwebenginepage.h> +#include <qwebenginesettings.h> +#include <qwebengineview.h> + + +class tst_QWebEngineFaviconManager : public QObject { + Q_OBJECT + +public Q_SLOTS: + void init(); + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + +private Q_SLOTS: + void faviconLoad(); + void faviconLoadFromResources(); + void faviconLoadEncodedUrl(); + void noFavicon(); + void aboutBlank(); + void unavailableFavicon(); + void errorPageEnabled(); + void errorPageDisabled(); + void bestFavicon(); + void touchIcon(); + +private: + QWebEngineView* m_view; + QWebEnginePage* m_page; +}; + + +void tst_QWebEngineFaviconManager::init() +{ + m_view = new QWebEngineView(); + m_page = m_view->page(); +} + + +void tst_QWebEngineFaviconManager::initTestCase() +{ +} + +void tst_QWebEngineFaviconManager::cleanupTestCase() +{ +} + + +void tst_QWebEngineFaviconManager::cleanup() +{ + delete m_view; +} + +void tst_QWebEngineFaviconManager::faviconLoad() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-single.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/icons/qt32.ico"))); +} + +void tst_QWebEngineFaviconManager::faviconLoadFromResources() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl("qrc:/resources/favicon-single.html"); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico")); +} + +void tst_QWebEngineFaviconManager::faviconLoadEncodedUrl() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QString urlString = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-single.html")).toString(); + QUrl url = QUrl(urlString + QLatin1String("?favicon=load should work with#whitespace!")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/icons/qt32.ico"))); +} + +void tst_QWebEngineFaviconManager::noFavicon() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/test1.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); +} + +void tst_QWebEngineFaviconManager::aboutBlank() +{ + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl("about:blank"); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); +} + +void tst_QWebEngineFaviconManager::unavailableFavicon() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-unavailable.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/icons/unavailable.ico"))); +} + +void tst_QWebEngineFaviconManager::errorPageEnabled() +{ + m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, true); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl(QUrl("http://non.existent/url")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + // Icon is reseted at load start. + // Load is started twice: once for unavailale page then error page + QTRY_COMPARE(iconUrlChangedSpy.count(), 2); + + QUrl iconUrl; + iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); + iconUrl = iconUrlChangedSpy.at(1).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); +} + +void tst_QWebEngineFaviconManager::errorPageDisabled() +{ + m_page->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl(QUrl("http://non.existent/url")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); +} + +void tst_QWebEngineFaviconManager::bestFavicon() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + QUrl url, iconUrl; + + url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-misc.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + // Touch icon is ignored + QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/icons/qt32.ico"))); + + loadFinishedSpy.clear(); + iconUrlChangedSpy.clear(); + + url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-shortcut.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QCOMPARE(iconUrl, QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/icons/qt144.png"))); +} + +void tst_QWebEngineFaviconManager::touchIcon() +{ + if (!QDir(TESTS_SOURCE_DIR).exists()) + W_QSKIP(QString("This test requires access to resources found in '%1'").arg(TESTS_SOURCE_DIR).toLatin1().constData(), SkipAll); + + QSignalSpy loadFinishedSpy(m_page, SIGNAL(loadFinished(bool))); + QSignalSpy iconUrlChangedSpy(m_page, SIGNAL(iconUrlChanged(QUrl))); + + QUrl url = QUrl::fromLocalFile(TESTS_SOURCE_DIR + QLatin1String("qwebenginefaviconmanager/resources/favicon-touch.html")); + m_page->load(url); + + QTRY_COMPARE(loadFinishedSpy.count(), 1); + QTRY_COMPARE(iconUrlChangedSpy.count(), 1); + + QUrl iconUrl = iconUrlChangedSpy.at(0).at(0).toString(); + QVERIFY(iconUrl.isEmpty()); +} + +QTEST_MAIN(tst_QWebEngineFaviconManager) + +#include "tst_qwebenginefaviconmanager.moc" diff --git a/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.qrc b/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.qrc new file mode 100644 index 000000000..65d8ed928 --- /dev/null +++ b/tests/auto/widgets/qwebenginefaviconmanager/tst_qwebenginefaviconmanager.qrc @@ -0,0 +1,12 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>resources/favicon-misc.html</file> + <file>resources/favicon-shortcut.html</file> + <file>resources/favicon-single.html</file> + <file>resources/favicon-touch.html</file> + <file>resources/favicon-unavailable.html</file> + <file>resources/icons/qt144.png</file> + <file>resources/icons/qt32.ico</file> + <file>resources/test1.html</file> +</qresource> +</RCC> diff --git a/tests/auto/widgets/widgets.pro b/tests/auto/widgets/widgets.pro index 986d5bbee..c65d7dd6c 100644 --- a/tests/auto/widgets/widgets.pro +++ b/tests/auto/widgets/widgets.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs SUBDIRS += \ qwebengineaccessibility \ + qwebenginefaviconmanager \ qwebenginepage \ qwebenginehistory \ qwebenginehistoryinterface \ |