summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Varga <pvarga@inf.u-szeged.hu>2020-06-19 11:17:50 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-06-28 11:52:05 +0000
commitd411328f2a7ff9993bcce5b1db74280a39c90981 (patch)
tree2b9ccabfa204ce5796c20e8f90c99b577e1a7154
parent45099f1e9e51aeec2266c46b63fa1ecf8670be5a (diff)
Add API for favicon database
[ChangeLog][QtWebEngineCore][QWebEngineProfile] Add new API to access icon database asynchronously. [ChangeLog][QtWebEngineQuick] image:/favicon/ URLs now can be used to access icon database. Task-number: QTBUG-51184 Change-Id: I6096ad9a4210670ed59458c4fa099a02595e8a1e Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io> (cherry picked from commit 2ad450018e8ae22f4c426a421fa5c0995feb1e16) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/core/api/qwebenginepage.cpp9
-rw-r--r--src/core/api/qwebengineprofile.cpp66
-rw-r--r--src/core/api/qwebengineprofile.h3
-rw-r--r--src/core/profile_adapter.cpp99
-rw-r--r--src/core/profile_adapter.h11
-rw-r--r--src/webenginequick/api/qquickwebenginefaviconprovider.cpp179
-rw-r--r--src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h50
-rw-r--r--src/webenginequick/api/qquickwebengineprofile.h1
-rw-r--r--src/webenginequick/api/qquickwebengineview_p.h3
-rw-r--r--src/webenginequick/doc/src/qtwebengine-features.qdoc39
-rw-r--r--src/webenginequick/doc/src/webengineview_lgpl.qdoc12
-rw-r--r--tests/auto/quick/qmltests/CMakeLists.txt1
-rw-r--r--tests/auto/quick/qmltests/data/tst_faviconDatabase.qml235
-rw-r--r--tests/auto/quick/qmltests/tst_qmltests.cpp13
-rw-r--r--tests/auto/widgets/favicon/tst_favicon.cpp292
15 files changed, 975 insertions, 38 deletions
diff --git a/src/core/api/qwebenginepage.cpp b/src/core/api/qwebenginepage.cpp
index 3028c38f5..12dcc867b 100644
--- a/src/core/api/qwebenginepage.cpp
+++ b/src/core/api/qwebenginepage.cpp
@@ -1987,11 +1987,12 @@ QUrl QWebEnginePage::iconUrl() const
\brief The icon associated with the page currently viewed.
\since 5.7
- By default, this property contains a null icon. If the web page specifies more than one icon,
- the \c{icon} property encapsulates the available candidate icons in a single,
- scalable \c{QIcon}.
+ By default, this property contains a null icon. If touch icons are disabled
+ (see \c QWebEngineSettings::TouchIconsEnabled), the favicon is provided in two sizes
+ (16x16 and 32x32 pixels) encapsulated in \c{QIcon}. Otherwise, single icon is provided
+ with the largest available size.
- \sa iconChanged(), iconUrl(), iconUrlChanged()
+ \sa iconChanged(), iconUrl(), iconUrlChanged(), QWebEngineSettings::TouchIconsEnabled
*/
QIcon QWebEnginePage::icon() const
{
diff --git a/src/core/api/qwebengineprofile.cpp b/src/core/api/qwebengineprofile.cpp
index 4074a4c31..13ad0f992 100644
--- a/src/core/api/qwebengineprofile.cpp
+++ b/src/core/api/qwebengineprofile.cpp
@@ -873,4 +873,70 @@ QWebEngineClientCertificateStore *QWebEngineProfile::clientCertificateStore()
#endif
}
+/*!
+ * Requests an icon for a previously loaded page with this profile from the database. Each profile
+ * has its own icon database and it is stored in the persistent storage thus the stored icons
+ * can be accessed without network connection too. The icon must be previously loaded to be
+ * stored in the database.
+ *
+ * \a url specifies the URL of the page what the icon is requested for. In case of more than one
+ * available icons the one with the size closest to \a desiredSizeInPixel will be returned.
+ * The result icon is resized to \a desiredSizeInPixel. If desiredSizeInPixel is 0 the largest
+ * available icon is returned.
+ *
+ * This function is asynchronous and the result is returned by \a iconAvailableCallback.
+ * The callback is called if a request for an icon is performed. If the requested icon is
+ * available, the first parameter (with type QIcon) is the result. Otherwise, it is null.
+ *
+ * The second parameter stores the URL of the requested icon. It is empty if the icon can't be
+ * fetched.
+ *
+ * The third parameter stores the URL of the page which the icon is assigned.
+ *
+ * \note Icons can't be requested with an off-the-record profile.
+ *
+ * \since 6.2
+ * \sa requestIconForIconURL()
+ */
+void QWebEngineProfile::requestIconForPageURL(const QUrl &url, int desiredSizeInPixel,
+ std::function<void(const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback) const
+{
+ Q_D(const QWebEngineProfile);
+ d->profileAdapter()->requestIconForPageURL(url, desiredSizeInPixel,
+ settings()->testAttribute(QWebEngineSettings::TouchIconsEnabled),
+ iconAvailableCallback);
+}
+
+/*!
+ * Requests an icon with the specified \a url from the database. Each profile has its
+ * own icon database and it is stored in the persistent storage thus the stored icons
+ * can be accessed without network connection too. The icon must be previously loaded to be
+ * stored in the database.
+ *
+ * \a url specifies the URL of the icon. In case of more than one
+ * available icons the one with the size closest to \a desiredSizeInPixel will be returned.
+ * The result icon is resized to \a desiredSizeInPixel. If desiredSizeInPixel is 0 the largest
+ * available icon is returned.
+ *
+ * This function is asynchronous and the result is returned by \a iconAvailableCallback.
+ * The callback is called if a request for an icon is performed. If the requested icon is
+ * available, the first parameter (with type QIcon) is the result. Otherwise, it is null.
+ *
+ * The second parameter stores the URL of the requested icon. It is empty if the icon can't be
+ * fetched.
+ *
+ * \note Icons can't be requested with an off-the-record profile.
+ *
+ * \since 6.2
+ * \sa requestIconForPageURL()
+ */
+void QWebEngineProfile::requestIconForIconURL(const QUrl &url, int desiredSizeInPixel,
+ std::function<void(const QIcon &, const QUrl &)> iconAvailableCallback) const
+{
+ Q_D(const QWebEngineProfile);
+ d->profileAdapter()->requestIconForIconURL(url, desiredSizeInPixel,
+ settings()->testAttribute(QWebEngineSettings::TouchIconsEnabled),
+ iconAvailableCallback);
+}
+
QT_END_NAMESPACE
diff --git a/src/core/api/qwebengineprofile.h b/src/core/api/qwebengineprofile.h
index 63ac3a9b9..618576664 100644
--- a/src/core/api/qwebengineprofile.h
+++ b/src/core/api/qwebengineprofile.h
@@ -142,6 +142,9 @@ public:
QWebEngineClientCertificateStore *clientCertificateStore();
+ void requestIconForPageURL(const QUrl &url, int desiredSizeInPixel, std::function<void(const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback) const;
+ void requestIconForIconURL(const QUrl &url, int desiredSizeInPixel, std::function<void(const QIcon &, const QUrl &)> iconAvailableCallback) const;
+
static QWebEngineProfile *defaultProfile();
Q_SIGNALS:
diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp
index 204974c28..0072eea50 100644
--- a/src/core/profile_adapter.cpp
+++ b/src/core/profile_adapter.cpp
@@ -39,6 +39,7 @@
#include "profile_adapter.h"
+#include "base/task/cancelable_task_tracker.h"
#include "components/favicon/core/favicon_service.h"
#include "components/history/content/browser/history_database_helper.h"
#include "components/history/core/browser/history_database_params.h"
@@ -125,6 +126,7 @@ ProfileAdapter::ProfileAdapter(const QString &storageName):
std::vector<network::mojom::CorsOriginPatternPtr> list;
list.push_back(std::move(pattern));
m_profile->GetSharedCorsOriginAccessList()->SetForOrigin(qrc, std::move(list), {}, base::BindOnce([]{}));
+ m_cancelableTaskTracker.reset(new base::CancelableTaskTracker());
}
ProfileAdapter::~ProfileAdapter()
@@ -747,4 +749,101 @@ QWebEngineClientCertificateStore *ProfileAdapter::clientCertificateStore()
}
#endif
+static void callbackOnIconAvailableForPageURL(std::function<void (const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback,
+ const QUrl &pageUrl,
+ const favicon_base::FaviconRawBitmapResult &result)
+{
+ if (!result.is_valid()) {
+ iconAvailableCallback(QIcon(), toQt(result.icon_url), pageUrl);
+ return;
+ }
+ QPixmap pixmap(toQt(result.pixel_size));
+ pixmap.loadFromData(result.bitmap_data->data(), result.bitmap_data->size());
+ iconAvailableCallback(QIcon(pixmap), toQt(result.icon_url), pageUrl);
+}
+
+void ProfileAdapter::requestIconForPageURL(const QUrl &pageUrl,
+ int desiredSizeInPixel,
+ bool touchIconsEnabled,
+ std::function<void (const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback)
+{
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ favicon::FaviconService *service = FaviconServiceFactoryQt::GetForBrowserContext(m_profile.data());
+
+ if (!service->HistoryService()) {
+ callbackOnIconAvailableForPageURL(iconAvailableCallback, pageUrl,
+ favicon_base::FaviconRawBitmapResult());
+ return;
+ }
+
+ favicon_base::IconTypeSet types = { favicon_base::IconType::kFavicon };
+ if (touchIconsEnabled) {
+ types.insert(favicon_base::IconType::kTouchIcon);
+ types.insert(favicon_base::IconType::kTouchPrecomposedIcon);
+ types.insert(favicon_base::IconType::kWebManifestIcon);
+ }
+ service->GetRawFaviconForPageURL(
+ toGurl(pageUrl), types, desiredSizeInPixel, true /* fallback_to_host */,
+ base::BindOnce(&callbackOnIconAvailableForPageURL, iconAvailableCallback, pageUrl),
+ m_cancelableTaskTracker.get());
+}
+
+static void callbackOnIconAvailableForIconURL(std::function<void (const QIcon &, const QUrl &)> iconAvailableCallback,
+ ProfileAdapter *profileAdapter,
+ const QUrl &iconUrl, int iconType,
+ int desiredSizeInPixel,
+ bool touchIconsEnabled,
+ const favicon_base::FaviconRawBitmapResult &result)
+{
+ if (!result.is_valid()) {
+ // If touch icons are disabled there is no need to try further icon types.
+ if (!touchIconsEnabled) {
+ iconAvailableCallback(QIcon(), iconUrl);
+ return;
+ }
+ if (static_cast<favicon_base::IconType>(iconType) != favicon_base::IconType::kMax) {
+ //Q_ASSERT(profileAdapter->profile());
+ favicon::FaviconService *service = FaviconServiceFactoryQt::GetForBrowserContext(profileAdapter->profile());
+ service->GetRawFavicon(toGurl(iconUrl),
+ static_cast<favicon_base::IconType>(iconType + 1),
+ desiredSizeInPixel,
+ base::BindOnce(&callbackOnIconAvailableForIconURL, iconAvailableCallback,
+ profileAdapter, iconUrl, iconType + 1, desiredSizeInPixel,
+ touchIconsEnabled),
+ profileAdapter->cancelableTaskTracker());
+ return;
+ }
+ iconAvailableCallback(QIcon(), iconUrl);
+ return;
+ }
+ QPixmap pixmap(toQt(result.pixel_size));
+ pixmap.loadFromData(result.bitmap_data->data(), result.bitmap_data->size());
+ iconAvailableCallback(QIcon(pixmap), toQt(result.icon_url));
+}
+
+void ProfileAdapter::requestIconForIconURL(const QUrl &iconUrl,
+ int desiredSizeInPixel,
+ bool touchIconsEnabled,
+ std::function<void (const QIcon &, const QUrl &)> iconAvailableCallback)
+{
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ favicon::FaviconService *service = FaviconServiceFactoryQt::GetForBrowserContext(m_profile.data());
+
+ if (!service->HistoryService()) {
+ callbackOnIconAvailableForIconURL(iconAvailableCallback,
+ this,
+ iconUrl,
+ static_cast<int>(favicon_base::IconType::kMax), 0,
+ touchIconsEnabled,
+ favicon_base::FaviconRawBitmapResult());
+ return;
+ }
+ service->GetRawFavicon(
+ toGurl(iconUrl), favicon_base::IconType::kFavicon, desiredSizeInPixel,
+ base::BindOnce(&callbackOnIconAvailableForIconURL, iconAvailableCallback, this, iconUrl,
+ static_cast<int>(favicon_base::IconType::kFavicon), desiredSizeInPixel,
+ touchIconsEnabled),
+ m_cancelableTaskTracker.get());
+}
+
} // namespace QtWebEngineCore
diff --git a/src/core/profile_adapter.h b/src/core/profile_adapter.h
index 979316b4a..d88834d7c 100644
--- a/src/core/profile_adapter.h
+++ b/src/core/profile_adapter.h
@@ -67,6 +67,10 @@
QT_FORWARD_DECLARE_CLASS(QObject)
+namespace base {
+class CancelableTaskTracker;
+}
+
namespace QtWebEngineCore {
class UserNotificationController;
@@ -215,6 +219,12 @@ public:
QString determineDownloadPath(const QString &downloadDirectory, const QString &suggestedFilename, const time_t &startTime);
+ void requestIconForPageURL(const QUrl &pageUrl, int desiredSizeInPixel, bool touchIconsEnabled,
+ std::function<void (const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback);
+ void requestIconForIconURL(const QUrl &iconUrl, int desiredSizeInPixel, bool touchIconsEnabled,
+ std::function<void (const QIcon &, const QUrl &)> iconAvailableCallback);
+ base::CancelableTaskTracker *cancelableTaskTracker() { return m_cancelableTaskTracker.get(); }
+
static QPointer<ProfileAdapter> s_profileForGlobalCertificateVerification;
private:
void updateCustomUrlSchemeHandlers();
@@ -251,6 +261,7 @@ private:
QList<WebContentsAdapterClient *> m_webContentsAdapterClients;
int m_httpCacheMaxSize;
QrcUrlSchemeHandler m_qrcHandler;
+ std::unique_ptr<base::CancelableTaskTracker> m_cancelableTaskTracker;
Q_DISABLE_COPY(ProfileAdapter)
};
diff --git a/src/webenginequick/api/qquickwebenginefaviconprovider.cpp b/src/webenginequick/api/qquickwebenginefaviconprovider.cpp
index 23397003e..d19954620 100644
--- a/src/webenginequick/api/qquickwebenginefaviconprovider.cpp
+++ b/src/webenginequick/api/qquickwebenginefaviconprovider.cpp
@@ -39,10 +39,13 @@
#include "qquickwebenginefaviconprovider_p_p.h"
-#include "qquickwebengineview_p.h"
+#include "profile_adapter.h"
+#include "qquickwebenginesettings_p.h"
#include "qquickwebengineview_p_p.h"
#include "web_contents_adapter.h"
+#include <QtCore/QMimeDatabase>
+#include <QtCore/QTimer>
#include <QtGui/QIcon>
#include <QtGui/QPixmap>
@@ -95,14 +98,155 @@ static QPixmap extractPixmap(const QIcon &icon, const QSize &requestedSize)
return iconPixmap.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation).copy();
}
+static bool isIconURL(const QUrl &url)
+{
+ QMimeType mimeType = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension);
+
+ // Check file extension.
+ if (mimeType.name().startsWith(QLatin1String("image")))
+ return true;
+
+ // Check if it is an image data: URL.
+ if (url.scheme() == QLatin1String("data") && url.path().startsWith(QLatin1String("image")))
+ return true;
+
+ return false;
+}
+
static QQuickWebEngineView *findViewById(const QString &id, QList<QQuickWebEngineView *> *views)
{
+ QQuickWebEngineView *result = nullptr;
for (QQuickWebEngineView *view : *views) {
- if (view->icon() == QQuickWebEngineFaviconProvider::faviconProviderUrl(QUrl(id)))
- return view;
+ if (isIconURL(QUrl(id))) {
+ if (view->icon() == QQuickWebEngineFaviconProvider::faviconProviderUrl(QUrl(id))) {
+ result = view;
+ break;
+ }
+ } else if (view->url() == QUrl(id)) {
+ result = view;
+ break;
+ }
+ }
+
+ return result;
+}
+
+FaviconImageResponseRunnable::FaviconImageResponseRunnable(const QString &id,
+ const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views)
+ : m_id(id), m_requestedSize(requestedSize), m_views(views)
+{
+}
+
+void FaviconImageResponseRunnable::run()
+{
+ if (tryNextView() == -1) {
+ // There is no non-otr view to access icon database.
+ Q_EMIT done(QPixmap());
+ }
+}
+
+void FaviconImageResponseRunnable::iconRequestDone(const QIcon &icon)
+{
+ if (icon.isNull()) {
+ if (tryNextView() == -1) {
+ // Ran out of views.
+ Q_EMIT done(QPixmap());
+ }
+ return;
+ }
+
+ Q_EMIT done(extractPixmap(icon, m_requestedSize).copy());
+}
+
+int FaviconImageResponseRunnable::tryNextView()
+{
+ for (; m_nextViewIndex < m_views->size(); ++m_nextViewIndex) {
+ QQuickWebEngineView *view = m_views->at(m_nextViewIndex);
+ if (view->profile()->isOffTheRecord())
+ continue;
+
+ requestIconOnUIThread(view);
+
+ return m_nextViewIndex++;
}
- return nullptr;
+ return -1;
+}
+
+void FaviconImageResponseRunnable::requestIconOnUIThread(QQuickWebEngineView *view)
+{
+ QTimer *timer = new QTimer();
+ timer->moveToThread(qApp->thread());
+ timer->setSingleShot(true);
+ QObject::connect(timer, &QTimer::timeout, [=]() {
+ QtWebEngineCore::ProfileAdapter *profileAdapter = view->d_ptr->profileAdapter();
+ bool touchIconsEnabled = view->profile()->settings()->touchIconsEnabled();
+ if (isIconURL(QUrl(m_id))) {
+ profileAdapter->requestIconForIconURL(QUrl(m_id),
+ qMax(m_requestedSize.width(), m_requestedSize.height()),
+ touchIconsEnabled,
+ [this](const QIcon &icon, const QUrl &) { iconRequestDone(icon); });
+ } else {
+ profileAdapter->requestIconForPageURL(QUrl(m_id),
+ qMax(m_requestedSize.width(), m_requestedSize.height()),
+ touchIconsEnabled,
+ [this](const QIcon &icon, const QUrl &, const QUrl &) { iconRequestDone(icon); });
+ }
+ timer->deleteLater();
+ });
+ QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
+}
+
+FaviconImageResponse::FaviconImageResponse()
+{
+ Q_EMIT finished();
+}
+
+FaviconImageResponse::FaviconImageResponse(const QString &id, const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views, QThreadPool *pool)
+{
+ if (QQuickWebEngineView *view = findViewById(id, views)) {
+ QTimer *timer = new QTimer();
+ timer->moveToThread(qApp->thread());
+ timer->setSingleShot(true);
+ QObject::connect(timer, &QTimer::timeout, [=]() {
+ QIcon icon = view->d_ptr->adapter->icon();
+ if (icon.isNull())
+ startRunnable(id, requestedSize, views, pool);
+ else
+ handleDone(extractPixmap(icon, requestedSize).copy());
+ timer->deleteLater();
+ });
+ QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
+ } else {
+ startRunnable(id, requestedSize, views, pool);
+ }
+}
+
+FaviconImageResponse::~FaviconImageResponse() { }
+
+void FaviconImageResponse::handleDone(QPixmap pixmap)
+{
+ if (m_runnable)
+ delete m_runnable;
+ m_image = pixmap.toImage();
+ Q_EMIT finished();
+}
+
+QQuickTextureFactory *FaviconImageResponse::textureFactory() const
+{
+ return QQuickTextureFactory::textureFactoryForImage(m_image);
+}
+
+void FaviconImageResponse::startRunnable(const QString &id, const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views, QThreadPool *pool)
+{
+ m_runnable = new FaviconImageResponseRunnable(id, requestedSize, views);
+ m_runnable->setAutoDelete(false);
+ connect(m_runnable, &FaviconImageResponseRunnable::done, this,
+ &FaviconImageResponse::handleDone);
+ pool->start(m_runnable);
}
QString QQuickWebEngineFaviconProvider::identifier()
@@ -128,31 +272,18 @@ QUrl QQuickWebEngineFaviconProvider::faviconProviderUrl(const QUrl &url)
return providerUrl;
}
-QQuickWebEngineFaviconProvider::QQuickWebEngineFaviconProvider()
- : QQuickImageProvider(QQuickImageProvider::Pixmap)
-{
-}
+QQuickWebEngineFaviconProvider::QQuickWebEngineFaviconProvider() { }
QQuickWebEngineFaviconProvider::~QQuickWebEngineFaviconProvider() { }
-QPixmap QQuickWebEngineFaviconProvider::requestPixmap(const QString &id, QSize *size,
- const QSize &requestedSize)
+QQuickImageResponse *
+QQuickWebEngineFaviconProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
- Q_UNUSED(size);
- Q_UNUSED(requestedSize);
-
- if (m_views.isEmpty())
- return QPixmap();
-
- QQuickWebEngineView *view = findViewById(id, &m_views);
- if (!view)
- return QPixmap();
-
- QIcon icon = view->d_ptr->adapter->icon();
- if (icon.isNull())
- return QPixmap();
+ if (m_views.empty())
+ return new FaviconImageResponse;
- return extractPixmap(icon, requestedSize).copy();
+ FaviconImageResponse *response = new FaviconImageResponse(id, requestedSize, &m_views, &m_pool);
+ return response;
}
QT_END_NAMESPACE
diff --git a/src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h b/src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h
index 922f8b3ac..68e23fbcf 100644
--- a/src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h
+++ b/src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h
@@ -53,13 +53,57 @@
#include <QtWebEngineQuick/private/qtwebenginequickglobal_p.h>
#include <QtCore/QList>
+#include <QtCore/QRunnable>
+#include <QtCore/QThreadPool>
+#include <QtGui/QImage>
#include <QtQuick/QQuickImageProvider>
QT_BEGIN_NAMESPACE
class QQuickWebEngineView;
-class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineFaviconProvider : public QQuickImageProvider
+class FaviconImageResponseRunnable : public QObject, public QRunnable
+{
+ Q_OBJECT
+
+public:
+ FaviconImageResponseRunnable(const QString &id, const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views);
+ void run() override;
+ void iconRequestDone(const QIcon &icon);
+
+signals:
+ void done(QPixmap pixmap);
+
+private:
+ int tryNextView();
+ void requestIconOnUIThread(QQuickWebEngineView *view);
+
+ QString m_id;
+ QSize m_requestedSize;
+ QList<QQuickWebEngineView *> *m_views;
+ int m_nextViewIndex = 0;
+};
+
+class FaviconImageResponse : public QQuickImageResponse
+{
+public:
+ FaviconImageResponse();
+ FaviconImageResponse(const QString &id, const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views, QThreadPool *pool);
+ ~FaviconImageResponse();
+ void handleDone(QPixmap pixmap);
+ QQuickTextureFactory *textureFactory() const override;
+
+private:
+ void startRunnable(const QString &id, const QSize &requestedSize,
+ QList<QQuickWebEngineView *> *views, QThreadPool *pool);
+
+ FaviconImageResponseRunnable *m_runnable = nullptr;
+ QImage m_image;
+};
+
+class Q_WEBENGINE_PRIVATE_EXPORT QQuickWebEngineFaviconProvider : public QQuickAsyncImageProvider
{
public:
static QString identifier();
@@ -71,9 +115,11 @@ public:
void attach(QQuickWebEngineView *view) { m_views.append(view); }
void detach(QQuickWebEngineView *view) { m_views.removeAll(view); }
- QPixmap requestPixmap(const QString &, QSize *, const QSize &) override;
+ QQuickImageResponse *requestImageResponse(const QString &id,
+ const QSize &requestedSize) override;
private:
+ QThreadPool m_pool;
QList<QQuickWebEngineView *> m_views;
};
diff --git a/src/webenginequick/api/qquickwebengineprofile.h b/src/webenginequick/api/qquickwebengineprofile.h
index f93c3a5d4..8147f0432 100644
--- a/src/webenginequick/api/qquickwebengineprofile.h
+++ b/src/webenginequick/api/qquickwebengineprofile.h
@@ -177,6 +177,7 @@ private:
QQuickWebEngineProfile(QQuickWebEngineProfilePrivate *, QObject *parent = nullptr);
QQuickWebEngineSettings *settings() const;
+ friend class FaviconImageResponseRunnable;
friend class QQuickWebEngineSettings;
friend class QQuickWebEngineSingleton;
friend class QQuickWebEngineViewPrivate;
diff --git a/src/webenginequick/api/qquickwebengineview_p.h b/src/webenginequick/api/qquickwebengineview_p.h
index b3c092856..2719fa448 100644
--- a/src/webenginequick/api/qquickwebengineview_p.h
+++ b/src/webenginequick/api/qquickwebengineview_p.h
@@ -563,7 +563,8 @@ private:
friend class QtWebEngineCore::RenderWidgetHostViewQtDelegateQuick;
friend class QQuickContextMenuBuilder;
- friend class QQuickWebEngineFaviconProvider;
+ friend class FaviconImageResponse;
+ friend class FaviconImageResponseRunnable;
#ifndef QT_NO_ACCESSIBILITY
friend class QQuickWebEngineViewAccessible;
#endif // QT_NO_ACCESSIBILITY
diff --git a/src/webenginequick/doc/src/qtwebengine-features.qdoc b/src/webenginequick/doc/src/qtwebengine-features.qdoc
index 431367765..24bc9d7aa 100644
--- a/src/webenginequick/doc/src/qtwebengine-features.qdoc
+++ b/src/webenginequick/doc/src/qtwebengine-features.qdoc
@@ -54,6 +54,7 @@
\li \l{View Source}
\li \l{webrtc_feature}{WebRTC}
\li \l{Web Notifications}
+ \li \l{Favicon Handling}
\endlist
\section1 Audio and Video Codecs
@@ -641,4 +642,42 @@
{WebEngineView.Notifications}.
Support for this feature was added in Qt 5.13.0.
+
+ \section1 Favicon Handling
+
+ For accessing icons a \c QQuickImageProvider is registered. This provider can be
+ accessed by a special URL where the scheme is "image:" and the host is "favicon".
+ For example,
+ \qml
+ Image {
+ source: "image://favicon/url"
+ }
+ \endqml
+
+ The \c url can be the URL of the favicon. For example,
+ \qml
+ Image {
+ source: "image://favicon/https://www.qt.io/hubfs/2016_Qt_Logo/qt_logo_green_rgb_16x16.png"
+ }
+ \endqml
+
+ The \c url also can be a page URL to access its icon. For example,
+ \qml
+ Image {
+ source: "image://favicon/https://www.qt.io/"
+ }
+ \endqml
+
+ If more than one icon is available, the \l {Image::sourceSize} property can be
+ specified to choose the icon with the desired size. If \l {Image::sourceSize}
+ is not specified or 0, the largest available icon will be chosen.
+
+ The image provider looks up the requested icon in the existing \l {WebEngineView}
+ instances. First, it tries to match the currently displayed icons. If no match
+ has been found it requests the icon from the database. Each profile has its
+ own icon database and it is stored in the persistent storage thus the stored icons
+ can be accessed without network connection too. The icon must be previously loaded
+ to be stored in the database.
+
+ \note The icon database is not available for off-the-record profiles.
*/
diff --git a/src/webenginequick/doc/src/webengineview_lgpl.qdoc b/src/webenginequick/doc/src/webengineview_lgpl.qdoc
index 1ea484703..11dceae69 100644
--- a/src/webenginequick/doc/src/webengineview_lgpl.qdoc
+++ b/src/webenginequick/doc/src/webengineview_lgpl.qdoc
@@ -212,8 +212,7 @@
\readonly
An internal URL for accessing the currently displayed web site icon,
- also known as favicon or shortcut icon. The icon is already downloaded
- and stored by the \QWE's favicon manager.
+ also known as favicon or shortcut icon.
This read-only URL corresponds to the image used within a mobile browser
application to represent a bookmarked page on the device's home screen.
@@ -229,11 +228,10 @@
}
\endqml
- Specifying the \c{sourceSize} property of the \c{Image} element informs
- the \QWE's favicon provider about the requested size. The
- favicon provider tries to find the best fit among the web page candidate
- icons. If \c{sourceSize} property is not specified, the provider provides
- the icon with the largest resolution.
+ Specifying the \l {Image::sourceSize} property informs
+ the \QWE's favicon provider about the requested size and resizes the
+ icon to it. If \l {Image::sourceSize} property is not specified,
+ the provider provides the icon with the largest available resolution.
*/
/*!
diff --git a/tests/auto/quick/qmltests/CMakeLists.txt b/tests/auto/quick/qmltests/CMakeLists.txt
index 56ba60ebb..a05cd9fd3 100644
--- a/tests/auto/quick/qmltests/CMakeLists.txt
+++ b/tests/auto/quick/qmltests/CMakeLists.txt
@@ -18,6 +18,7 @@ set(testList
tst_desktopBehaviorLoadHtml.qml
tst_download.qml
tst_favicon.qml
+ tst_faviconDatabase.qml
tst_filePicker.qml
tst_findText.qml
tst_focusOnNavigation.qml
diff --git a/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml b/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml
new file mode 100644
index 000000000..181c652d7
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_faviconDatabase.qml
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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
+import QtTest
+import QtWebEngine
+import Test.util
+import "../../qmltests/data"
+
+TestWebEngineView {
+ id: webEngineView
+ width: 200
+ height: 400
+
+ TempDir { id: tempDir }
+
+ property QtObject defaultProfile: WebEngineProfile {
+ offTheRecord: true
+ }
+
+ property QtObject nonOTRProfile: WebEngineProfile {
+ persistentStoragePath: tempDir.path() + '/WebEngineFavicon'
+ offTheRecord: false
+ }
+
+ function getFaviconPixel(faviconImage) {
+ var grabImage = Qt.createQmlObject("
+ import QtQuick\n
+ Image { }", testCase)
+ var faviconCanvas = Qt.createQmlObject("
+ import QtQuick\n
+ Canvas { }", testCase)
+
+ testCase.tryVerify(function() { return faviconImage.status == Image.Ready });
+ faviconImage.grabToImage(function(result) {
+ grabImage.source = result.url
+ });
+ testCase.tryVerify(function() { return grabImage.status == Image.Ready });
+
+ faviconCanvas.width = faviconImage.width;
+ faviconCanvas.height = faviconImage.height;
+ var ctx = faviconCanvas.getContext("2d");
+ ctx.drawImage(grabImage, 0, 0, grabImage.width, grabImage.height);
+ var imageData = ctx.getImageData(Math.round(faviconCanvas.width/2),
+ Math.round(faviconCanvas.height/2),
+ faviconCanvas.width,
+ faviconCanvas.height);
+
+ grabImage.destroy();
+ faviconCanvas.destroy();
+
+ return imageData.data;
+ }
+
+ SignalSpy {
+ id: iconChangedSpy
+ target: webEngineView
+ signalName: "iconChanged"
+ }
+
+ TestCase {
+ id: testCase
+ name: "WebEngineFaviconDatabase"
+ when: windowShown
+
+ function init() {
+ // It is worth to restore the initial state with loading a blank page before all test functions.
+ webEngineView.url = 'about:blank';
+ verify(webEngineView.waitForLoadSucceeded());
+ iconChangedSpy.clear();
+ webEngineView.settings.touchIconsEnabled = false;
+ webEngineView.settings.autoLoadIconsForPage = true;
+ }
+
+ function cleanupTestCase() {
+ tempDir.removeRecursive(nonOTRProfile.persistentStoragePath);
+ }
+
+ function test_iconDatabase_data() {
+ return [
+ { tag: "OTR", profile: defaultProfile },
+ { tag: "non-OTR", profile: nonOTRProfile },
+ ];
+ }
+
+ function test_iconDatabase(row)
+ {
+ webEngineView.profile = row.profile;
+ compare(iconChangedSpy.count, 0);
+
+ var faviconImage = Qt.createQmlObject("
+ import QtQuick\n
+ Image { width: 16; height: 16; sourceSize: Qt.size(width, height); cache: false; }", testCase);
+
+ var pixel;
+ compare(iconChangedSpy.count, 0);
+
+ webEngineView.url = Qt.resolvedUrl("favicon.html"); // favicon.png -> 165
+ verify(webEngineView.waitForLoadSucceeded());
+
+ iconChangedSpy.wait();
+ compare(iconChangedSpy.count, 1);
+
+ var previousIcon = webEngineView.icon;
+ iconChangedSpy.clear();
+
+ webEngineView.url = Qt.resolvedUrl("favicon-shortcut.html"); // qt32.ico -> 251
+ verify(webEngineView.waitForLoadSucceeded());
+
+ tryCompare(iconChangedSpy, "count", 2);
+
+ // Icon database is not accessible with OTR profile.
+ faviconImage.source = previousIcon;
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], webEngineView.profile.offTheRecord ? 0 : 165);
+
+ // This should pass with OTR too because icon is requested for the current page.
+ faviconImage.source = "image://favicon/" + Qt.resolvedUrl("favicon-shortcut.html");
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 251);
+
+ faviconImage.source = "image://favicon/" + Qt.resolvedUrl("favicon.html");
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], webEngineView.profile.offTheRecord ? 0 : 165);
+
+ faviconImage.destroy();
+ webEngineView.profile = defaultProfile;
+ }
+
+ function test_iconDatabaseMultiView()
+ {
+ var pixel;
+
+ var faviconImage = Qt.createQmlObject("
+ import QtQuick\n
+ Image { width: 16; height: 16; sourceSize: Qt.size(width, height); cache: false; }", testCase);
+
+ var webEngineView1 = Qt.createQmlObject("
+ import QtWebEngine\n
+ import Test.util\n
+ import '../../qmltests/data'\n
+ TestWebEngineView {\n
+ TempDir { id: tempDir }
+ profile: WebEngineProfile {\n
+ persistentStoragePath: tempDir.path() + '/WebEngineFavicon1'\n
+ offTheRecord: false\n
+ }\n
+ }", testCase);
+
+ var webEngineView2 = Qt.createQmlObject("
+ import QtWebEngine\n
+ import Test.util\n
+ import '../../qmltests/data'\n
+ TestWebEngineView {\n
+ TempDir { id: tempDir }
+ profile: WebEngineProfile {\n
+ persistentStoragePath: tempDir.path() + '/WebEngineFavicon2'\n
+ offTheRecord: false\n
+ }\n
+ }", testCase);
+
+ // Moke sure the icons have not been stored in the database yet.
+ var icon1 = "image://favicon/" + Qt.resolvedUrl("icons/favicon.png");
+ faviconImage.source = icon1;
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 0);
+
+ var icon2 = "image://favicon/" + Qt.resolvedUrl("icons/qt32.ico");
+ faviconImage.source = icon2;
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 0);
+
+ webEngineView1.url = Qt.resolvedUrl("favicon.html"); // favicon.png -> 165
+ verify(webEngineView1.waitForLoadSucceeded());
+ tryCompare(webEngineView1, "icon", icon1);
+ webEngineView1.url = "about:blank";
+ verify(webEngineView1.waitForLoadSucceeded());
+
+ webEngineView2.url = Qt.resolvedUrl("favicon-shortcut.html"); // qt32.ico -> 251
+ verify(webEngineView2.waitForLoadSucceeded());
+ tryCompare(webEngineView2, "icon", icon2);
+ webEngineView2.url = "about:blank";
+ verify(webEngineView2.waitForLoadSucceeded());
+
+ faviconImage.source = "";
+ compare(webEngineView1.icon, "");
+ compare(webEngineView2.icon, "");
+
+ faviconImage.source = icon1;
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 165);
+
+ faviconImage.source = icon2;
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 251);
+
+ faviconImage.source = "image://favicon/file:///does.not.exist.ico";
+ pixel = getFaviconPixel(faviconImage);
+ compare(pixel[0], 0);
+
+ webEngineView1.destroy();
+ webEngineView2.destroy();
+ faviconImage.destroy();
+
+ tempDir.removeRecursive(webEngineView1.profile.persistentStoragePath)
+ tempDir.removeRecursive(webEngineView2.profile.persistentStoragePath)
+ }
+ }
+}
+
diff --git a/tests/auto/quick/qmltests/tst_qmltests.cpp b/tests/auto/quick/qmltests/tst_qmltests.cpp
index bb6c3628c..653972dfd 100644
--- a/tests/auto/quick/qmltests/tst_qmltests.cpp
+++ b/tests/auto/quick/qmltests/tst_qmltests.cpp
@@ -125,6 +125,19 @@ public:
return tempDir.isValid() ? tempDir.path() : QString();
}
+ Q_INVOKABLE void removeRecursive(const QString dirname)
+ {
+ QDir dir(dirname);
+ QFileInfoList entries(dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot));
+ for (int i = 0; i < entries.count(); ++i) {
+ if (entries[i].isDir())
+ removeRecursive(entries[i].filePath());
+ else
+ dir.remove(entries[i].fileName());
+ }
+ QDir().rmdir(dirname);
+ }
+
private:
QTemporaryDir tempDir;
};
diff --git a/tests/auto/widgets/favicon/tst_favicon.cpp b/tests/auto/widgets/favicon/tst_favicon.cpp
index 377699aaa..d8b4803c0 100644
--- a/tests/auto/widgets/favicon/tst_favicon.cpp
+++ b/tests/auto/widgets/favicon/tst_favicon.cpp
@@ -63,6 +63,13 @@ private Q_SLOTS:
void dynamicFavicon();
void touchIconWithSameURL();
+ void iconDatabaseOTR();
+ void requestIconForIconURL_data();
+ void requestIconForIconURL();
+ void requestIconForPageURL_data();
+ void requestIconForPageURL();
+ void desiredSize();
+
private:
QWebEngineView *m_view;
QWebEnginePage *m_page;
@@ -560,6 +567,291 @@ void tst_Favicon::touchIconWithSameURL()
QTRY_COMPARE(iconChangedSpy.count(), 1);
}
+void tst_Favicon::iconDatabaseOTR()
+{
+ QWebEngineProfile profile;
+ QWebEngineView view;
+ QWebEnginePage *page = new QWebEnginePage(&profile, &view);
+ view.setPage(page);
+
+ QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool)));
+ QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl)));
+ QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon)));
+
+ page->load(QUrl("qrc:/resources/favicon-misc.html"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 1);
+ QTRY_COMPARE(iconChangedSpy.count(), 1);
+
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForIconURL(page->iconUrl(), 0,
+ [page, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl) {
+ QVERIFY(icon.isNull());
+ QCOMPARE(iconUrl, page->iconUrl());
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForPageURL(page->url(), 0,
+ [page, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) {
+ QVERIFY(icon.isNull());
+ QVERIFY(iconUrl.isEmpty());
+ QCOMPARE(pageUrl, page->url());
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+}
+
+void tst_Favicon::requestIconForIconURL_data()
+{
+ QTest::addColumn<bool>("touchIconsEnabled");
+ QTest::newRow("touch icons enabled") << true;
+ QTest::newRow("touch icons disabled") << false;
+}
+
+void tst_Favicon::requestIconForIconURL()
+{
+ QFETCH(bool, touchIconsEnabled);
+
+ QTemporaryDir tmpDir;
+ QWebEngineProfile profile("iconDatabase-iconurl");
+ profile.setPersistentStoragePath(tmpDir.path());
+ profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
+ profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, touchIconsEnabled);
+
+ QWebEngineView view;
+ QWebEnginePage *page = new QWebEnginePage(&profile, &view);
+ view.setPage(page);
+
+ QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool)));
+ QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl)));
+ QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon)));
+
+ page->load(QUrl("qrc:/resources/favicon-misc.html"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 1);
+ QTRY_COMPARE(iconChangedSpy.count(), 1);
+
+ page->load(QUrl("about:blank"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 2);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 2);
+ QTRY_COMPARE(iconChangedSpy.count(), 2);
+ QVERIFY(page->icon().isNull());
+ QVERIFY(page->iconUrl().isEmpty());
+
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForIconURL(QUrl("qrc:/resources/icons/qt144.png"), 0,
+ [touchIconsEnabled, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl) {
+ if (touchIconsEnabled) {
+ QVERIFY(!icon.isNull());
+ QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xfff2f9ec);
+ } else {
+ QVERIFY(icon.isNull());
+ }
+
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt144.png"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForIconURL(QUrl("qrc:/resources/icons/qt32.ico"), 0,
+ [&iconRequestDone](const QIcon &icon, const QUrl &iconUrl) {
+ QVERIFY(!icon.isNull());
+ QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xffeef7e6);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+}
+
+void tst_Favicon::requestIconForPageURL_data()
+{
+ QTest::addColumn<bool>("touchIconsEnabled");
+ QTest::newRow("touch icons enabled") << true;
+ QTest::newRow("touch icons disabled") << false;
+}
+
+void tst_Favicon::requestIconForPageURL()
+{
+ QFETCH(bool, touchIconsEnabled);
+
+ QTemporaryDir tmpDir;
+ QWebEngineProfile profile("iconDatabase-pageurl");
+ profile.setPersistentStoragePath(tmpDir.path());
+ profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
+ profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, touchIconsEnabled);
+
+
+ QWebEngineView view;
+ QWebEnginePage *page = new QWebEnginePage(&profile, &view);
+ view.setPage(page);
+
+ QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool)));
+ QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl)));
+ QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon)));
+
+ page->load(QUrl("qrc:/resources/favicon-misc.html"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 1);
+ QTRY_COMPARE(iconChangedSpy.count(), 1);
+
+ page->load(QUrl("about:blank"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 2);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 2);
+ QTRY_COMPARE(iconChangedSpy.count(), 2);
+ QVERIFY(page->icon().isNull());
+ QVERIFY(page->iconUrl().isEmpty());
+
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-misc.html"), 0,
+ [touchIconsEnabled, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) {
+ QVERIFY(!icon.isNull());
+ if (touchIconsEnabled) {
+ QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xfff2f9ec);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt144.png"));
+ } else {
+ QCOMPARE(icon.pixmap(QSize(32, 32), 1.0).toImage().pixel(16, 16), 0xffeef7e6);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qt32.ico"));
+ }
+
+ QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-misc.html"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+}
+
+void tst_Favicon::desiredSize()
+{
+ QTemporaryDir tmpDir;
+ QWebEngineProfile profile("iconDatabase-desiredsize");
+ profile.setPersistentStoragePath(tmpDir.path());
+ profile.settings()->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
+
+ QWebEngineView view;
+ QWebEnginePage *page = new QWebEnginePage(&profile, &view);
+ view.setPage(page);
+
+ // Disable touch icons: icon with size 16x16 will be loaded.
+ {
+ profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, false);
+
+ QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool)));
+ QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl)));
+ QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon)));
+
+ page->load(QUrl("qrc:/resources/favicon-multi.html"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 1);
+ QTRY_COMPARE(iconChangedSpy.count(), 1);
+
+ page->load(QUrl("about:blank"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 2);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 2);
+ QTRY_COMPARE(iconChangedSpy.count(), 2);
+ QVERIFY(page->icon().isNull());
+ QVERIFY(page->iconUrl().isEmpty());
+ }
+
+ int desiredSizeInPixel = 16;
+ QRgb expectedPixel = 0xfffdfefc;
+
+ // Request icon with size 16x16 (desiredSizeInPixel).
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), desiredSizeInPixel,
+ [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) {
+ QVERIFY(!icon.isNull());
+ QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0)
+ .toImage()
+ .pixel(8, 8);
+ QCOMPARE(pixel, expectedPixel);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico"));
+ QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+
+ // Enable touch icons: icon with the largest size (64x64) will be loaded.
+ {
+ profile.settings()->setAttribute(QWebEngineSettings::TouchIconsEnabled, true);
+
+ QSignalSpy loadFinishedSpy(page, SIGNAL(loadFinished(bool)));
+ QSignalSpy iconUrlChangedSpy(page, SIGNAL(iconUrlChanged(QUrl)));
+ QSignalSpy iconChangedSpy(page, SIGNAL(iconChanged(QIcon)));
+
+ page->load(QUrl("qrc:/resources/favicon-multi.html"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 1);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 1);
+ QTRY_COMPARE(iconChangedSpy.count(), 1);
+
+ page->load(QUrl("about:blank"));
+
+ QTRY_COMPARE(loadFinishedSpy.count(), 2);
+ QTRY_COMPARE(iconUrlChangedSpy.count(), 2);
+ QTRY_COMPARE(iconChangedSpy.count(), 2);
+ QVERIFY(page->icon().isNull());
+ QVERIFY(page->iconUrl().isEmpty());
+ }
+
+ // Request icon with size 16x16.
+ // The icon is stored with two sizes in the database. This request should result same pixel
+ // as the first one.
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), desiredSizeInPixel,
+ [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) {
+ QVERIFY(!icon.isNull());
+ QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0)
+ .toImage()
+ .pixel(8, 8);
+ QCOMPARE(pixel, expectedPixel);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico"));
+ QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+
+ // Request icon with size 64x64.
+ // This requests the another size from the database. The pixel should differ.
+ {
+ bool iconRequestDone = false;
+ profile.requestIconForPageURL(QUrl("qrc:/resources/favicon-multi.html"), 64,
+ [desiredSizeInPixel, expectedPixel, &iconRequestDone](const QIcon &icon, const QUrl &iconUrl, const QUrl &pageUrl) {
+ QVERIFY(!icon.isNull());
+ QRgb pixel = icon.pixmap(QSize(desiredSizeInPixel, desiredSizeInPixel), 1.0)
+ .toImage()
+ .pixel(8, 8);
+ QVERIFY(pixel != expectedPixel);
+ QCOMPARE(iconUrl, QUrl("qrc:/resources/icons/qtmulti.ico"));
+ QCOMPARE(pageUrl, QUrl("qrc:/resources/favicon-multi.html"));
+ iconRequestDone = true;
+ });
+ QTRY_VERIFY(iconRequestDone);
+ }
+}
+
QTEST_MAIN(tst_Favicon)
#include "tst_favicon.moc"