From d411328f2a7ff9993bcce5b1db74280a39c90981 Mon Sep 17 00:00:00 2001 From: Peter Varga Date: Fri, 19 Jun 2020 11:17:50 +0200 Subject: 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 (cherry picked from commit 2ad450018e8ae22f4c426a421fa5c0995feb1e16) Reviewed-by: Qt Cherry-pick Bot --- tests/auto/quick/qmltests/CMakeLists.txt | 1 + .../quick/qmltests/data/tst_faviconDatabase.qml | 235 +++++++++++++++++ tests/auto/quick/qmltests/tst_qmltests.cpp | 13 + tests/auto/widgets/favicon/tst_favicon.cpp | 292 +++++++++++++++++++++ 4 files changed, 541 insertions(+) create mode 100644 tests/auto/quick/qmltests/data/tst_faviconDatabase.qml (limited to 'tests') 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("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("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" -- cgit v1.2.3