diff options
author | Peter Varga <pvarga@inf.u-szeged.hu> | 2020-06-19 11:17:50 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-06-28 11:52:05 +0000 |
commit | d411328f2a7ff9993bcce5b1db74280a39c90981 (patch) | |
tree | 2b9ccabfa204ce5796c20e8f90c99b577e1a7154 | |
parent | 45099f1e9e51aeec2266c46b63fa1ecf8670be5a (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.cpp | 9 | ||||
-rw-r--r-- | src/core/api/qwebengineprofile.cpp | 66 | ||||
-rw-r--r-- | src/core/api/qwebengineprofile.h | 3 | ||||
-rw-r--r-- | src/core/profile_adapter.cpp | 99 | ||||
-rw-r--r-- | src/core/profile_adapter.h | 11 | ||||
-rw-r--r-- | src/webenginequick/api/qquickwebenginefaviconprovider.cpp | 179 | ||||
-rw-r--r-- | src/webenginequick/api/qquickwebenginefaviconprovider_p_p.h | 50 | ||||
-rw-r--r-- | src/webenginequick/api/qquickwebengineprofile.h | 1 | ||||
-rw-r--r-- | src/webenginequick/api/qquickwebengineview_p.h | 3 | ||||
-rw-r--r-- | src/webenginequick/doc/src/qtwebengine-features.qdoc | 39 | ||||
-rw-r--r-- | src/webenginequick/doc/src/webengineview_lgpl.qdoc | 12 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/data/tst_faviconDatabase.qml | 235 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/tst_qmltests.cpp | 13 | ||||
-rw-r--r-- | tests/auto/widgets/favicon/tst_favicon.cpp | 292 |
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" |