From e9c6cdefff00daffb8b07e72c47f8e8f845ba436 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Tue, 15 Dec 2020 11:51:08 +0200 Subject: Move src/webengine to lib/webengine --- CMakeLists.txt | 1 + lib/webengine/CMakeLists.txt | 22 +++ lib/webengine/test/form.html | 10 ++ lib/webengine/test/icon.svg | 7 + lib/webengine/test/profile.cpp | 125 ++++++++++++++++ lib/webengine/test/profilemanager.cpp | 120 +++++++++++++++ lib/webengine/test/sample.html | 7 + lib/webengine/test/testing.profile | 8 + lib/webengine/test/view.cpp | 92 ++++++++++++ lib/webengine/urlinterceptor.cpp | 32 ++++ lib/webengine/urlinterceptor.h | 31 ++++ lib/webengine/webpage.cpp | 127 ++++++++++++++++ lib/webengine/webpage.h | 30 ++++ lib/webengine/webprofile.cpp | 138 ++++++++++++++++++ lib/webengine/webprofile.h | 242 +++++++++++++++++++++++++++++++ lib/webengine/webprofilemanager.cpp | 83 +++++++++++ lib/webengine/webprofilemanager.h | 126 ++++++++++++++++ lib/webengine/webview.cpp | 87 +++++++++++ lib/webengine/webview.h | 67 +++++++++ lib/webengine/webviewcontextmenu.cpp | 233 +++++++++++++++++++++++++++++ lib/webengine/webviewcontextmenu.h | 21 +++ src/CMakeLists.txt | 1 - src/bookmarks/bookmarkswidget.cpp | 4 +- src/browser.cpp | 3 +- src/browser.h | 2 +- src/mainwindow/addressbar.cpp | 4 +- src/mainwindow/mainwindow.cpp | 6 +- src/mainwindow/menubar.cpp | 4 +- src/mainwindow/widgets/navigationbar.cpp | 4 +- src/session/savesessiondialog.cpp | 4 +- src/subwindow/subwindow.cpp | 6 +- src/subwindow/subwindow.h | 4 +- src/subwindow/tabwidget.cpp | 49 ++----- src/subwindow/tabwidget.h | 53 +++++-- src/webengine/CMakeLists.txt | 22 --- src/webengine/test/form.html | 10 -- src/webengine/test/icon.svg | 7 - src/webengine/test/profile.cpp | 125 ---------------- src/webengine/test/profilemanager.cpp | 120 --------------- src/webengine/test/sample.html | 7 - src/webengine/test/testing.profile | 8 - src/webengine/test/view.cpp | 92 ------------ src/webengine/urlinterceptor.cpp | 32 ---- src/webengine/urlinterceptor.h | 31 ---- src/webengine/webpage.cpp | 127 ---------------- src/webengine/webpage.h | 30 ---- src/webengine/webprofile.cpp | 138 ------------------ src/webengine/webprofile.h | 242 ------------------------------- src/webengine/webprofilemanager.cpp | 83 ----------- src/webengine/webprofilemanager.h | 126 ---------------- src/webengine/webview.cpp | 87 ----------- src/webengine/webview.h | 67 --------- src/webengine/webviewcontextmenu.cpp | 233 ----------------------------- src/webengine/webviewcontextmenu.h | 21 --- 54 files changed, 1685 insertions(+), 1676 deletions(-) create mode 100644 lib/webengine/CMakeLists.txt create mode 100644 lib/webengine/test/form.html create mode 100644 lib/webengine/test/icon.svg create mode 100644 lib/webengine/test/profile.cpp create mode 100644 lib/webengine/test/profilemanager.cpp create mode 100644 lib/webengine/test/sample.html create mode 100644 lib/webengine/test/testing.profile create mode 100644 lib/webengine/test/view.cpp create mode 100644 lib/webengine/urlinterceptor.cpp create mode 100644 lib/webengine/urlinterceptor.h create mode 100644 lib/webengine/webpage.cpp create mode 100644 lib/webengine/webpage.h create mode 100644 lib/webengine/webprofile.cpp create mode 100644 lib/webengine/webprofile.h create mode 100644 lib/webengine/webprofilemanager.cpp create mode 100644 lib/webengine/webprofilemanager.h create mode 100644 lib/webengine/webview.cpp create mode 100644 lib/webengine/webview.h create mode 100644 lib/webengine/webviewcontextmenu.cpp create mode 100644 lib/webengine/webviewcontextmenu.h delete mode 100644 src/webengine/CMakeLists.txt delete mode 100644 src/webengine/test/form.html delete mode 100644 src/webengine/test/icon.svg delete mode 100644 src/webengine/test/profile.cpp delete mode 100644 src/webengine/test/profilemanager.cpp delete mode 100644 src/webengine/test/sample.html delete mode 100644 src/webengine/test/testing.profile delete mode 100644 src/webengine/test/view.cpp delete mode 100644 src/webengine/urlinterceptor.cpp delete mode 100644 src/webengine/urlinterceptor.h delete mode 100644 src/webengine/webpage.cpp delete mode 100644 src/webengine/webpage.h delete mode 100644 src/webengine/webprofile.cpp delete mode 100644 src/webengine/webprofile.h delete mode 100644 src/webengine/webprofilemanager.cpp delete mode 100644 src/webengine/webprofilemanager.h delete mode 100644 src/webengine/webview.cpp delete mode 100644 src/webengine/webview.h delete mode 100644 src/webengine/webviewcontextmenu.cpp delete mode 100644 src/webengine/webviewcontextmenu.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c7147c4..4320962 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ add_subdirectory(lib/downloads) add_subdirectory(lib/pluginloader) add_subdirectory(lib/session_formats) add_subdirectory(lib/smolblok) +add_subdirectory(lib/webengine) add_subdirectory(src) add_subdirectory(plugins/HostlistFilter) diff --git a/lib/webengine/CMakeLists.txt b/lib/webengine/CMakeLists.txt new file mode 100644 index 0000000..ba3017f --- /dev/null +++ b/lib/webengine/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(webengine STATIC + webprofile.h webprofile.cpp webprofilemanager.h webprofilemanager.cpp + webpage.h webpage.cpp + webview.h webview.cpp webviewcontextmenu.h webviewcontextmenu.cpp + urlinterceptor.h urlinterceptor.cpp) +target_include_directories(webengine PUBLIC ${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) +# autogen: required for context menu icons; TODO: move context menu to src/ +target_link_libraries(webengine PUBLIC Qt5::WebEngineWidgets autogen fmt) + +# tests +add_executable(profile_test test/profile.cpp) +target_link_libraries(profile_test PRIVATE webengine Catch2::Catch2) +#target_sanitize(profile_test) + +add_executable(profilemanager_test test/profilemanager.cpp) +target_link_libraries(profilemanager_test PRIVATE webengine Catch2::Catch2) +#target_sanitize(profilemanager_test) + +add_test(NAME webengine_profile COMMAND profile_test) +add_test(NAME webengine_profilemanager COMMAND profilemanager_test) +set_tests_properties(webengine_profile webengine_profilemanager PROPERTIES + ENVIRONMENT "PROFILE=${CMAKE_CURRENT_SOURCE_DIR}/test/testing.profile") diff --git a/lib/webengine/test/form.html b/lib/webengine/test/form.html new file mode 100644 index 0000000..9d8b19e --- /dev/null +++ b/lib/webengine/test/form.html @@ -0,0 +1,10 @@ + + + + Form completion test + + + +

Form completion test

+ + diff --git a/lib/webengine/test/icon.svg b/lib/webengine/test/icon.svg new file mode 100644 index 0000000..a348cab --- /dev/null +++ b/lib/webengine/test/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/webengine/test/profile.cpp b/lib/webengine/test/profile.cpp new file mode 100644 index 0000000..ae3a4e3 --- /dev/null +++ b/lib/webengine/test/profile.cpp @@ -0,0 +1,125 @@ +#define CATCH_CONFIG_RUNNER + +// clazy:excludeall=non-pod-global-static + +#include "webprofilemanager.h" +#include +#include + +TEST_CASE("loading profile settings") +{ + const QString search = GENERATE(as{}, "https://search.url/t=%1", "https://duckduckgo.com/?q=%1&ia=web", "aaabbbccc"); + const QUrl homepage = GENERATE(as{}, "https://homepage.net", "about:blank", "aaabbbccc"); + const QUrl newtab = GENERATE(as{}, "https://newtab.net", "about:blank", "aaabbbccc"); + + auto *settings = WebProfile::load(QString(), search, homepage, newtab); + + REQUIRE(settings != nullptr); + REQUIRE(settings->value("search").toString() == search); + REQUIRE(settings->value("homepage").toUrl() == homepage); + REQUIRE(settings->value("newtab").toUrl() == newtab); + + delete settings; +} + +SCENARIO("profile properties") +{ + const QString search{ "about:blank" }; + const QUrl homepage{ "about:blank" }; + const QUrl newtab{ "about:blank" }; + const QString id{ "id" }; + + REQUIRE(qEnvironmentVariableIsSet("PROFILE")); + + // create an empty settings object + const QString settings_path = GENERATE(as{}, QString(), qgetenv("PROFILE")); + auto *settings = WebProfile::load(settings_path, search, homepage, newtab); + // create the actual profile + auto *profile = WebProfile::load(id, settings, true); + + REQUIRE(profile != nullptr); + REQUIRE(profile->isOffTheRecord()); + + WHEN("id constant") + { + REQUIRE(profile->getId() == id); + REQUIRE(profile->property("id").toString() == id); + } + + WHEN("changing profile name") + { + const QString name = GENERATE(as{}, "a", "bb", "ccc"); + profile->setName(name); + THEN("the name changes") + { + REQUIRE(profile->name() == name); + REQUIRE(settings->value("name").toString() == name); + } + } + WHEN("changing search") + { + const QString search = GENERATE(as{}, "a", "bb", "ccc"); + profile->setSearch(search); + THEN("the search url changes") + { + REQUIRE(profile->search() == search); + REQUIRE(settings->value("search").toString() == search); + } + } + WHEN("changing homepage url") + { + const QUrl url = GENERATE(as{}, "a", "bb", "ccc"); + profile->setHomepage(url); + THEN("homepage changes") + { + REQUIRE(profile->homepage() == url); + REQUIRE(settings->value("homepage").toUrl() == url); + } + } + WHEN("changing newtab url") + { + const QUrl url = GENERATE(as{}, "a", "bb", "ccc"); + profile->setNewtab(url); + THEN("newtab changes") + { + REQUIRE(profile->newtab() == url); + REQUIRE(settings->value("newtab").toUrl() == url); + } + } + + WHEN("changing cookies") + { + auto list = profile->cookies(); + REQUIRE(list.isEmpty()); + list.append(QNetworkCookie("name", "value").toRawForm()); + profile->setCookies(list); + THEN("new cookie list gets applied") + { + // There is no event loop, so the signals cannot fire and update the cookie list + //REQUIRE(list == profile->cookies()); + } + } + + WHEN("changing headers") + { + QMap headers; + headers.insert("Dnt", "1"); + headers.insert("unknown", "pair"); + + profile->setHeaders(headers); + THEN("headers change") + { + REQUIRE(profile->headers() == headers); + } + } + + delete settings; + delete profile; +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + const auto r = Catch::Session().run(argc, argv); + return r; +} diff --git a/lib/webengine/test/profilemanager.cpp b/lib/webengine/test/profilemanager.cpp new file mode 100644 index 0000000..8f6a34f --- /dev/null +++ b/lib/webengine/test/profilemanager.cpp @@ -0,0 +1,120 @@ +#define CATCH_CONFIG_RUNNER + +// clazy:excludeall=non-pod-global-static + +#include "webprofilemanager.h" +#include +#include + +SCENARIO("WebProfileManager") +{ + const QString search{ "https://search.url/t=%1" }; + const QUrl homepage{ "https://homepage.net" }; + const QUrl newtab{ "https://newtab.net" }; + const QString default_id{ "default" }; + + GIVEN("an empty profile list") + { + WebProfileManager profiles({}, default_id, search, homepage, newtab); + + REQUIRE(profiles.idList().count() == 1); + REQUIRE(profiles.profile(default_id) == WebProfile::defaultProfile()); + REQUIRE(profiles.profile("not-in-list") == nullptr); + + WHEN("adding a new profile") + { + const QString id{ "id" }; + auto *settings = WebProfile::load(QString(), search, homepage, newtab); + auto *profile = WebProfile::load(id, settings, true); + + THEN("doesn't add profile without settings") + { + profiles.add(id, profile, nullptr); + REQUIRE(profiles.idList().count() == 1); + } + + THEN("doesn't add settings without profile") + { + profiles.add(id, nullptr, settings); + REQUIRE(profiles.idList().count() == 1); + } + + THEN("adds new profile with settings") + { + profiles.add(id, profile, settings); + REQUIRE(profiles.idList().count() == 2); + } + } + + WHEN("moving") + { + WebProfileManager other(std::move(profiles)); + THEN("moved has the same number of profiles") + { + REQUIRE(other.idList().count() == 1); + REQUIRE(other.profile(default_id) == WebProfile::defaultProfile()); + REQUIRE(other.profile("not-in-list") == nullptr); + } + } + } + + GIVEN("a number of profiles, default undefined") + { + REQUIRE(qEnvironmentVariableIsSet("PROFILE")); + + WebProfileManager profiles(QString::fromLatin1(qgetenv("PROFILE")).split(';'), default_id, search, homepage, newtab); + + REQUIRE(profiles.idList().count() == 2); + REQUIRE(profiles.profile(default_id) == WebProfile::defaultProfile()); + REQUIRE(profiles.profile("testing") != nullptr); + REQUIRE(profiles.profile("not-in-list") == nullptr); + + WHEN("making global") + { + profiles.make_global(); + WebProfileManager other; + + THEN("global has the same number of profiles") + { + REQUIRE(other.idList().count() == 2); + REQUIRE(other.profile(default_id) == WebProfile::defaultProfile()); + REQUIRE(other.profile("testing") != nullptr); + REQUIRE(other.profile("not-in-list") == nullptr); + } + + THEN("walking has no nullptrs") + { + other.walk([](const QString &, WebProfile *profile, const QSettings *settings) { + REQUIRE(profile != nullptr); + REQUIRE(settings != nullptr); + }); + } + } + } + + GIVEN("a number of profiles, default defined") + { + REQUIRE(qEnvironmentVariableIsSet("PROFILE")); + + WebProfileManager profiles(QString::fromLatin1(qgetenv("PROFILE")).split(';'), "testing", search, homepage, newtab); + + REQUIRE(profiles.idList().count() == 1); + REQUIRE(profiles.profile("testing") == WebProfile::defaultProfile()); + REQUIRE(profiles.profile("not-in-list") == nullptr); + + WHEN("walking") + { + profiles.walk([](const QString &, WebProfile *profile, const QSettings *settings) { + REQUIRE(profile != nullptr); + REQUIRE(settings != nullptr); + }); + } + } +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + const auto r = Catch::Session().run(argc, argv); + return r; +} diff --git a/lib/webengine/test/sample.html b/lib/webengine/test/sample.html new file mode 100644 index 0000000..54746d5 --- /dev/null +++ b/lib/webengine/test/sample.html @@ -0,0 +1,7 @@ + +sample page + +

This is a sample page

+ + + diff --git a/lib/webengine/test/testing.profile b/lib/webengine/test/testing.profile new file mode 100644 index 0000000..e345a3e --- /dev/null +++ b/lib/webengine/test/testing.profile @@ -0,0 +1,8 @@ +name=Test Profile +otr=true +search=https://duckduckgo.com/?q=%1&ia=web +homepage=about:blank +newtab=about:blank + +[headers] +Dnt=1 diff --git a/lib/webengine/test/view.cpp b/lib/webengine/test/view.cpp new file mode 100644 index 0000000..8aa639a --- /dev/null +++ b/lib/webengine/test/view.cpp @@ -0,0 +1,92 @@ +#define CATCH_CONFIG_RUNNER + +// clazy:excludeall=non-pod-global-static + +#include "webprofile.h" +#include "webview.h" +#include +#include +#include +#include +#include + +SCENARIO("WebView") +{ + const QString profile_id{ "default" }; + auto *settings = WebProfile::load(qgetenv("PROFILE"), "about:blank", QUrl{ "about:blank" }, QUrl{ "about:blank" }); + auto *profile = WebProfile::load(profile_id, settings, true); + + QMainWindow window; + auto *view = new WebView(profile, nullptr); + window.setCentralWidget(view); + window.show(); + window.resize(800, 600); + + WHEN("created") + { + THEN("using the default profile") + { + REQUIRE(view->profile() == profile); + } + THEN("serialized using default profile") + { + const auto data = view->serialize(); + REQUIRE(data.profile == profile_id); + REQUIRE(data.url.isEmpty()); + REQUIRE(!data.history.isEmpty()); + } + THEN("loading a url") + { + // block until a loadFinished signal + QEventLoop pause; + QObject::connect(view, &WebView::loadFinished, &pause, &QEventLoop::quit); + view->load(QUrl{ qgetenv("URL") }); + pause.exec(); + + REQUIRE(view->isLoaded()); + } + } + + WHEN("changing profiles") + { + const QString swap_profile_id{ "swap_profile" }; + auto *swap_settings = WebProfile::load(QString(), "about:blank", QUrl{ "about:blank" }, QUrl{ "about:blank" }); + auto *swap_profile = WebProfile::load(swap_profile_id, swap_settings, true); + + view->setProfile(swap_profile); + THEN("using the swap profile") + { + REQUIRE(view->profile() == swap_profile); + } + THEN("serialized using swap profile") + { + const auto data = view->serialize(); + REQUIRE(data.profile == swap_profile_id); + REQUIRE(data.url.isEmpty()); + REQUIRE(!data.history.isEmpty()); + } + + view->setProfile(profile); + delete swap_settings; + delete swap_profile; + } + + // cleanup + window.close(); + delete view; + delete settings; + delete profile; +} + +int main(int argc, char **argv) +{ + QtWebEngine::initialize(); + QApplication app(argc, argv); + + QTimer::singleShot(0, &app, [argc, argv, &app]() { + const auto n_failed = Catch::Session().run(argc, argv); + app.exit(n_failed); + }); + + return app.exec(); +} diff --git a/lib/webengine/urlinterceptor.cpp b/lib/webengine/urlinterceptor.cpp new file mode 100644 index 0000000..047cad4 --- /dev/null +++ b/lib/webengine/urlinterceptor.cpp @@ -0,0 +1,32 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "urlinterceptor.h" +#include "webprofile.h" + +// test DNT on https://browserleaks.com/donottrack + +UrlRequestInterceptor::UrlRequestInterceptor(WebProfile* profile) + : QWebEngineUrlRequestInterceptor(profile) +{ + Q_CHECK_PTR(profile); + m_profile = profile; +} + +void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) +{ + for(auto *filter : qAsConst(m_profile->m_filters)) { + filter->interceptRequest(info); + } + + // set headers + for(auto i = m_profile->m_headers.constBegin(); i != m_profile->m_headers.constEnd(); ++i) { + info.setHttpHeader(i.key(), i.value()); + } +} + diff --git a/lib/webengine/urlinterceptor.h b/lib/webengine/urlinterceptor.h new file mode 100644 index 0000000..eb3ce67 --- /dev/null +++ b/lib/webengine/urlinterceptor.h @@ -0,0 +1,31 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_URLREQUESTINTERCEPTOR_H +#define SMOLBOTE_URLREQUESTINTERCEPTOR_H + +#include + +class WebProfile; +class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor +{ + friend class WebProfile; + +public: + ~UrlRequestInterceptor() override = default; + + void interceptRequest(QWebEngineUrlRequestInfo &info) override; + +protected: + explicit UrlRequestInterceptor(WebProfile *profile); + +private: + WebProfile *m_profile; +}; + +#endif // SMOLBOTE_URLREQUESTINTERCEPTOR_H diff --git a/lib/webengine/webpage.cpp b/lib/webengine/webpage.cpp new file mode 100644 index 0000000..b2b19b5 --- /dev/null +++ b/lib/webengine/webpage.cpp @@ -0,0 +1,127 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "webpage.h" +#include +#include +#include +#include +#include + +[[nodiscard]] inline QString tr_terminationStatus(QWebEnginePage::RenderProcessTerminationStatus status) +{ + switch(status) { + case QWebEnginePage::NormalTerminationStatus: + return QObject::tr("The render process terminated normally."); + case QWebEnginePage::AbnormalTerminationStatus: + return QObject::tr("The render process terminated with with a non-zero exit status."); + case QWebEnginePage::CrashedTerminationStatus: + return QObject::tr("The render process crashed, for example because of a segmentation fault."); + case QWebEnginePage::KilledTerminationStatus: + return QObject::tr("The render process was killed, for example by SIGKILL or task manager kill."); + } + + return QObject::tr("The render process was terminated with an unknown status."); +} + +[[nodiscard]] inline QString feature_toString(QWebEnginePage::Feature feature) +{ + switch(feature) { + case QWebEnginePage::Notifications: + return QObject::tr("Notifications"); + case QWebEnginePage::Geolocation: + return QObject::tr("Geolocation"); + case QWebEnginePage::MediaAudioCapture: + return QObject::tr("Audio Capture"); + case QWebEnginePage::MediaVideoCapture: + return QObject::tr("Video Capture"); + case QWebEnginePage::MediaAudioVideoCapture: + return QObject::tr("Audio and Video Capture"); + case QWebEnginePage::MouseLock: + return QObject::tr("Mouse Lock"); + case QWebEnginePage::DesktopVideoCapture: + return QObject::tr("Desktop Video Capture"); + case QWebEnginePage::DesktopAudioVideoCapture: + return QObject::tr("Desktop Audio and Video Capture"); + } + + return QObject::tr("Unknown feature"); +} + +WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) + : QWebEnginePage(profile, parent) +{ + connect(this, &WebPage::fullScreenRequested, this, [](QWebEngineFullScreenRequest request) { + request.accept(); + }); + + connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::featurePermissionDialog); + connect(this, &QWebEnginePage::renderProcessTerminated, this, &WebPage::renderProcessCrashed); +} + +bool WebPage::certificateError(const QWebEngineCertificateError &certificateError) +{ + QMessageBox messageBox; + + messageBox.setWindowTitle(tr("SSL Error")); + if(certificateError.isOverridable()) + messageBox.setIcon(QMessageBox::Warning); + else + messageBox.setIcon(QMessageBox::Critical); + + messageBox.setText(tr("An SSL error has occurred on %1").arg(certificateError.url().toString())); + messageBox.setInformativeText(tr("

%1

" + "

This error %2 be overridden.

") + .arg(certificateError.errorDescription(), + certificateError.isOverridable() ? tr("can") : tr("cannot"))); + messageBox.setDetailedText(tr("Error code: %1").arg(certificateError.error())); + + if(certificateError.isOverridable()) { + messageBox.setStandardButtons(QMessageBox::Ignore | QMessageBox::Abort); + messageBox.setDefaultButton(QMessageBox::Ignore); + } else + messageBox.setStandardButtons(QMessageBox::Abort); + + auto resp = messageBox.exec(); + + return resp == QMessageBox::Ignore; +} + +void WebPage::featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage::Feature feature) +{ + QMessageBox messageBox; + + messageBox.setWindowTitle(tr("Feature permission request")); + messageBox.setIcon(QMessageBox::Question); + messageBox.setText(tr("

The webpage %1 has requested permission to access: %2

" + "

Allow this feature?

") + .arg(securityOrigin.toString(), feature_toString(feature))); + + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::No); + + if(messageBox.exec() == QMessageBox::Yes) { + setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser); + } else { + setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionDeniedByUser); + } +} + +void WebPage::renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) +{ + if(terminationStatus != QWebEnginePage::NormalTerminationStatus) { + QString page = "

This tab has crashed!

%message%"; + page.replace(QLatin1String("%message%"), QString("

%1
Exit code is %2.

" + "

Press here to reload this tab.

") + .arg(tr_terminationStatus(terminationStatus), QString::number(exitCode), this->url().toEncoded())); + + QTimer::singleShot(0, this, [this, page]() { + setHtml(page.toUtf8(), url()); + }); + } +} diff --git a/lib/webengine/webpage.h b/lib/webengine/webpage.h new file mode 100644 index 0000000..91ae4f3 --- /dev/null +++ b/lib/webengine/webpage.h @@ -0,0 +1,30 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_WEBPAGE_H +#define SMOLBOTE_WEBPAGE_H + +#include + +class WebPage : public QWebEnginePage +{ + Q_OBJECT + +public: + WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); + ~WebPage() override = default; + +protected: + bool certificateError(const QWebEngineCertificateError &certificateError) override; + +protected slots: + void featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage::Feature feature); + void renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode); +}; + +#endif // SMOLBOTE_WEBPAGE_H diff --git a/lib/webengine/webprofile.cpp b/lib/webengine/webprofile.cpp new file mode 100644 index 0000000..f1e71fb --- /dev/null +++ b/lib/webengine/webprofile.cpp @@ -0,0 +1,138 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "webprofile.h" +#include "urlinterceptor.h" +#include +#include +#include +#include +#include + +static WebProfile *s_profile = nullptr; + +void WebProfile::setDefaultProfile(WebProfile *profile) +{ + s_profile = profile; +} +WebProfile *WebProfile::defaultProfile() +{ + return s_profile; +} + +QSettings *WebProfile::load(const QString &path, const QString &search, const QUrl &homepage, const QUrl &newtab) +{ + auto *settings = new QSettings(path, QSettings::IniFormat); + + if(!settings->contains("search")) { + settings->setValue("search", search); + } + if(!settings->contains("homepage")) { + settings->setValue("homepage", homepage); + } + if(!settings->contains("newtab")) { + settings->setValue("newtab", newtab); + } + + return settings; +} + +WebProfile *WebProfile::load(const QString &id, QSettings *settings, bool isOffTheRecord) +{ + WebProfile *profile = nullptr; + + if(settings->value("otr", isOffTheRecord).toBool()) { + profile = new WebProfile(id, nullptr); + } else { + profile = new WebProfile(id, id, nullptr); + } + + profile->m_name = settings->value("name", id).toString(); + connect(profile, &WebProfile::nameChanged, profile, [settings](const QString &name) { settings->setValue("name", name); }); + profile->m_search = settings->value("search", "").toString(); + connect(profile, &WebProfile::searchChanged, settings, [settings](const QString &url) { settings->setValue("search", url); }); + profile->m_homepage = settings->value("homepage", "").toUrl(); + connect(profile, &WebProfile::homepageChanged, settings, [settings](const QUrl &url) { settings->setValue("homepage", url); }); + profile->m_newtab = settings->value("newtab", "").toUrl(); + connect(profile, &WebProfile::newtabChanged, settings, [settings](const QUrl &url) { settings->setValue("newtab", url); }); + + { + settings->beginGroup("properties"); + const auto keys = settings->childKeys(); + for(const QString &key : keys) { + profile->setProperty(qUtf8Printable(key), settings->value(key)); + } + settings->endGroup(); // properties + connect(profile, &WebProfile::propertyChanged, [settings](const QString &property, const QVariant &value) { + settings->setValue("properties/" + property, value); + }); + } + { + settings->beginGroup("attributes"); + const auto keys = settings->childKeys(); + auto *s = profile->settings(); + for(const QString &key : keys) { + auto attribute = static_cast(key.toInt()); + s->setAttribute(attribute, settings->value(key).toBool()); + } + settings->endGroup(); + connect(profile, &WebProfile::attributeChanged, [settings](const QWebEngineSettings::WebAttribute attr, const bool value) { + settings->setValue("attributes/" + QString::number(attr), value); + }); + } + { + // headers + settings->beginGroup("headers"); + const auto keys = settings->childKeys(); + for(const QString &key : keys) { + profile->setHttpHeader(key.toLatin1(), settings->value(key).toString().toLatin1()); + } + settings->endGroup(); + connect(profile, &WebProfile::headerChanged, [settings](const QString &name, const QString &value) { + settings->setValue("headers/" + name, value); + }); + connect(profile, &WebProfile::headerRemoved, [settings](const QString &name) { + settings->remove("headers/" + name); + }); + } + return profile; +} + +// off-the-record constructor +WebProfile::WebProfile(const QString &id, QObject *parent) + : QWebEngineProfile(parent) + , m_id(id) +{ + QWebEngineProfile::setUrlRequestInterceptor(new UrlRequestInterceptor(this)); + connect(this->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, [this](const QNetworkCookie &cookie) { + spdlog::debug("[{}]: +cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); + m_cookies.append(cookie); + }); + connect(this->cookieStore(), &QWebEngineCookieStore::cookieRemoved, this, [this](const QNetworkCookie &cookie) { + spdlog::debug("[{}]: -cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); + m_cookies.removeOne(cookie); + }); + cookieStore()->loadAllCookies(); +} + +// default constructor +WebProfile::WebProfile(const QString &id, const QString &storageName, QObject *parent) + : QWebEngineProfile(storageName, parent) + , m_id(id) +{ + QWebEngineProfile::setUrlRequestInterceptor(new UrlRequestInterceptor(this)); + connect(this->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, [this](const QNetworkCookie &cookie) { + spdlog::debug("[{}]: +cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); + m_cookies.append(cookie); + }); + connect(this->cookieStore(), &QWebEngineCookieStore::cookieRemoved, this, [this](const QNetworkCookie &cookie) { + spdlog::debug("[{}]: -cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); + m_cookies.removeOne(cookie); + }); + cookieStore()->loadAllCookies(); +} diff --git a/lib/webengine/webprofile.h b/lib/webengine/webprofile.h new file mode 100644 index 0000000..894463f --- /dev/null +++ b/lib/webengine/webprofile.h @@ -0,0 +1,242 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_WEBENGINEPROFILE_H +#define SMOLBOTE_WEBENGINEPROFILE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class UrlRequestInterceptor; +class WebProfile : public QWebEngineProfile +{ + friend class UrlRequestInterceptor; + + Q_OBJECT + + Q_PROPERTY(QString id READ getId CONSTANT) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString search READ search WRITE setSearch NOTIFY searchChanged) + Q_PROPERTY(QUrl homepage READ homepage WRITE setHomepage NOTIFY homepageChanged) + Q_PROPERTY(QUrl newtab READ newtab WRITE setNewtab NOTIFY newtabChanged) + + // QWebEngineProfile should-be properties + Q_PROPERTY(QString cachePath READ cachePath WRITE setCachePath NOTIFY propertyChanged) + Q_PROPERTY(QString persistentStoragePath READ persistentStoragePath WRITE setPersistentStoragePath NOTIFY propertyChanged) + Q_PROPERTY(int persistentCookiesPolicy READ persistentCookiesPolicy WRITE setPersistentCookiesPolicy NOTIFY propertyChanged) + + Q_PROPERTY(QString httpAcceptLanguage READ httpAcceptLanguage WRITE setHttpAcceptLanguage NOTIFY propertyChanged) + Q_PROPERTY(int httpCacheMaximumSize READ httpCacheMaximumSize WRITE setHttpCacheMaximumSize NOTIFY propertyChanged) + Q_PROPERTY(int httpCacheType READ httpCacheType WRITE setHttpCacheType NOTIFY propertyChanged) + Q_PROPERTY(QString httpUserAgent READ httpUserAgent WRITE setHttpUserAgent NOTIFY propertyChanged) + + Q_PROPERTY(bool spellCheckEnabled READ isSpellCheckEnabled WRITE setSpellCheckEnabled NOTIFY propertyChanged) + + // more custom properties + Q_PROPERTY(QList cookies READ cookies WRITE setCookies NOTIFY cookiesChanged) + Q_PROPERTY(QMap headers READ headers WRITE setHeaders NOTIFY headersChanged) + +signals: + void nameChanged(const QString &name); + void searchChanged(const QString &url); + void homepageChanged(const QUrl &url); + void newtabChanged(const QUrl &url); + + void propertyChanged(const QString &name, const QVariant &value); + void attributeChanged(QWebEngineSettings::WebAttribute attribute, bool value); + + void cookiesChanged(); + void headersChanged(); + void headerChanged(const QString &name, const QString &value); + void headerRemoved(const QString &name); + +public: + [[nodiscard]] static QSettings *load(const QString &path, const QString &search = QString(), const QUrl &homepage = QUrl(), const QUrl &newtab = QUrl()); + [[nodiscard]] static WebProfile *load(const QString &id, QSettings *settings, bool isOffTheRecord = true); + + WebProfile(const WebProfile &) = delete; + WebProfile &operator=(const WebProfile &) = delete; + WebProfile(WebProfile &&) = delete; + WebProfile &operator=(WebProfile &&) = delete; + + static WebProfile *defaultProfile(); + static void setDefaultProfile(WebProfile *profile); + + ~WebProfile() override = default; + + [[nodiscard]] QString getId() const + { + return m_id; + } + [[nodiscard]] QString name() const + { + return m_name; + } + void setName(const QString &name) + { + m_name = name; + emit nameChanged(name); + } + + [[nodiscard]] QList cookies() const + { + QList r; + for(const auto &cookie : m_cookies) { + r.append(cookie.toRawForm()); + } + return r; + } + + [[nodiscard]] QString search() const + { + return m_search; + } + void setSearch(const QString &url) + { + m_search = url; + emit searchChanged(m_search); + } + + [[nodiscard]] QUrl homepage() const + { + return m_homepage; + } + void setHomepage(const QUrl &url) + { + m_homepage = url; + emit homepageChanged(m_homepage); + } + + [[nodiscard]] QUrl newtab() const + { + return m_newtab; + } + void setNewtab(const QUrl &url) + { + m_newtab = url; + emit newtabChanged(m_newtab); + } + + void setCachePath(const QString &path) + { + QWebEngineProfile::setCachePath(path); + emit propertyChanged("cachePath", path); + } + void setPersistentStoragePath(const QString &path) + { + QWebEngineProfile::setPersistentStoragePath(path); + emit propertyChanged("persistentStoragePath", path); + } + void setPersistentCookiesPolicy(int policy) + { + QWebEngineProfile::setPersistentCookiesPolicy(static_cast(policy)); + emit propertyChanged("persistentCookiesPolicy", policy); + } + + void setHttpAcceptLanguage(const QString &httpAcceptLanguage) + { + QWebEngineProfile::setHttpAcceptLanguage(httpAcceptLanguage); + emit propertyChanged("httpAcceptLanguage", httpAcceptLanguage); + } + void setHttpCacheMaximumSize(int maxSize) + { + QWebEngineProfile::setHttpCacheMaximumSize(maxSize); + emit propertyChanged("httpCacheMaximumSize", maxSize); + } + void setHttpCacheType(int type) + { + QWebEngineProfile::setHttpCacheType(static_cast(type)); + emit propertyChanged("httpCacheType", type); + } + void setHttpUserAgent(const QString &userAgent) + { + QWebEngineProfile::setHttpUserAgent(userAgent); + emit propertyChanged("httpUserAgent", userAgent); + } + void setHttpHeader(const QString &name, const QString &value) + { + m_headers[name.toLatin1()] = value.toLatin1(); + emit headerChanged(name, value); + } + void removeHttpHeader(const QString &name) + { + if(m_headers.contains(name.toLatin1())) { + m_headers.remove(name.toLatin1()); + emit headerRemoved(name); + } + } + [[nodiscard]] QMap headers() const + { + QMap r; + auto it = m_headers.constBegin(); + while(it != m_headers.constEnd()) { + r.insert(QString(it.key()), QVariant(it.value())); + ++it; + } + return r; + } + void setSpellCheckEnabled(bool enable) + { + QWebEngineProfile::setSpellCheckEnabled(enable); + emit propertyChanged("spellCheckEnabed", enable); + } + + void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor) + { + m_filters.append(interceptor); + } + +public slots: + void setCookies(const QList &cookies) + { + auto *store = cookieStore(); + store->deleteAllCookies(); + for(const auto &data : cookies) { + for(auto &cookie : QNetworkCookie::parseCookies(data.toByteArray())) { + store->setCookie(cookie); + } + } + emit cookiesChanged(); + } + void setHeaders(const QMap &headers) + { + m_headers.clear(); + auto it = headers.constBegin(); + while(it != headers.constEnd()) { + m_headers.insert(it.key().toLatin1(), it.value().toByteArray()); + ++it; + } + emit headersChanged(); + } + +protected: + // off-the-record constructor + explicit WebProfile(const QString &id, QObject *parent = nullptr); + // default constructor + explicit WebProfile(const QString &id, const QString &storageName, QObject *parent = nullptr); + + const QString m_id; + QString m_name; + QString m_search = QString("about:blank"); + QUrl m_homepage = QUrl("about:blank"); + QUrl m_newtab = QUrl("about:blank"); + + QVector m_filters; + QList m_cookies; + QMap m_headers; +}; + +#endif // SMOLBOTE_WEBENGINEPROFILE_H diff --git a/lib/webengine/webprofilemanager.cpp b/lib/webengine/webprofilemanager.cpp new file mode 100644 index 0000000..5cc83f8 --- /dev/null +++ b/lib/webengine/webprofilemanager.cpp @@ -0,0 +1,83 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "webprofilemanager.h" +#include "webprofile.h" + +static WebProfileManager *s_instance = nullptr; + +template <> +void WebProfileManager::make_global() +{ + if(s_instance == nullptr) { + s_instance = this; + } +} + +template <> +WebProfileManager::~WebProfileManager() = default; + +template <> +WebProfileManager::~WebProfileManager() +{ + for(Profile p : qAsConst(profiles)) { + if(p.selfDestruct && p.settings != nullptr) { + if(!p.ptr->isOffTheRecord()) { + if(!p.ptr->persistentStoragePath().isEmpty()) + QDir(p.ptr->persistentStoragePath()).removeRecursively(); + if(!p.ptr->cachePath().isEmpty()) + QDir(p.ptr->cachePath()).removeRecursively(); + } + const QString filename = p.settings->fileName(); + delete p.settings; + QFile::remove(filename); + } else if(p.settings != nullptr) { + p.settings->sync(); + delete p.settings; + } + } +} + +template <> +WebProfile *WebProfileManager::profile(const QString &id) const +{ + return s_instance->profile(id); +} + +template <> +QStringList WebProfileManager::idList() const +{ + return s_instance->idList(); +} + +template <> +void WebProfileManager::walk(std::function f) const +{ + for(auto iter = profiles.begin(); iter != profiles.end(); ++iter) { + f(iter.key(), iter.value().ptr, iter.value().settings); + } +} + +template <> +void WebProfileManager::walk(std::function f) const +{ + s_instance->walk(f); +} + +void profileMenu(QMenu *menu, const std::function &callback, WebProfile *current, bool checkable) +{ + auto *group = new QActionGroup(menu); + QObject::connect(menu, &QMenu::aboutToHide, group, &QActionGroup::deleteLater); + + s_instance->walk([=](const QString &, WebProfile *profile, const QSettings *) { + auto *action = menu->addAction(profile->name(), profile, [=]() { callback(profile); }); + action->setCheckable(checkable); + action->setChecked(profile == current); + group->addAction(action); + }); +} diff --git a/lib/webengine/webprofilemanager.h b/lib/webengine/webprofilemanager.h new file mode 100644 index 0000000..e5df6d5 --- /dev/null +++ b/lib/webengine/webprofilemanager.h @@ -0,0 +1,126 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_WEBPROFILEMANAGER_H +#define SMOLBOTE_WEBPROFILEMANAGER_H + +#include "webprofile.h" +#include +#include +#include +#include +#include +#include + +#if defined(__clang__) +#define consumable(X) [[clang::consumable(X)]] +#define return_typestate(X) [[clang::return_typestate(X)]] +#define set_typestate(X) [[clang::set_typestate(X)]] +#define callable_when(X) [[clang::callable_when(X)]] +#define param_typestate(X) [[clang::param_typestate(X)]] +#else +#define consumable(X) +#define return_typestate(X) +#define set_typestate(X) +#define callable_when(X) +#define param_typestate(X) +#endif + +void profileMenu(QMenu *menu, const std::function &callback, WebProfile *current = nullptr, bool checkable = false); + +template +class consumable(unconsumed) WebProfileManager +{ +public: + return_typestate(unconsumed) WebProfileManager() + { + static_assert(use_global); + } + return_typestate(unconsumed) WebProfileManager(const QStringList &paths, const QString &default_id, + const QString &search = QString(), const QUrl &homepage = QUrl(), const QUrl &newtab = QUrl()) + { + static_assert(!use_global); + for(const auto &path : paths) { + const auto id = QFileInfo(path).baseName(); + Profile profile; + profile.settings = WebProfile::load(path, search, homepage, newtab); + profile.ptr = WebProfile::load(id, profile.settings, true); + profiles[id] = profile; + } + + if(!profiles.contains(default_id)) { + auto *settings = WebProfile::load(QString(), search, homepage, newtab); + profiles[default_id] = Profile{ + .settings = settings, + .ptr = WebProfile::load(default_id, settings, true), + }; + } + WebProfile::setDefaultProfile(profiles[default_id].ptr); + } + ~WebProfileManager(); + + WebProfileManager(const WebProfileManager &) = delete; + WebProfileManager &operator=(const WebProfileManager &) = delete; + + return_typestate(unconsumed) WebProfileManager(WebProfileManager && other param_typestate(unconsumed)) + { + static_assert(!use_global); + profiles = std::move(other.profiles); + other.consume(); + } + WebProfileManager &operator=(WebProfileManager &&) = delete; + + callable_when(unconsumed) [[nodiscard]] WebProfile *profile(const QString &id) const + { + return profiles.value(id).ptr; + } + + callable_when(unconsumed) void add(const QString &id, WebProfile *profile, QSettings *settings) + { + if constexpr(use_global) { + return; + } + + if(profile != nullptr && settings != nullptr) { + profiles[id] = Profile{ settings, profile, false }; + } + } + + callable_when(unconsumed) void deleteProfile(const QString &id) + { + if constexpr(use_global) { + return; + } + + if(profiles.contains(id)) { + profiles[id].selfDestruct = true; + } + } + + callable_when(unconsumed) [[nodiscard]] QStringList idList() const + { + return profiles.keys(); + } + + callable_when(unconsumed) void walk(std::function) const; + + callable_when(unconsumed) void make_global(); + +private: + set_typestate(consumed) void consume() {} + + struct Profile { + QSettings *settings = nullptr; + WebProfile *ptr = nullptr; + bool selfDestruct = false; + }; + + QMap profiles; +}; + +#endif // SMOLBOTE_PROFILEMANAGER_H diff --git a/lib/webengine/webview.cpp b/lib/webengine/webview.cpp new file mode 100644 index 0000000..bc52102 --- /dev/null +++ b/lib/webengine/webview.cpp @@ -0,0 +1,87 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "webview.h" +#include "webpage.h" +#include "webprofile.h" +#include "webprofilemanager.h" +#include "webviewcontextmenu.h" +#include +#include + +WebView::WebView(QWidget *parent) + : QWebEngineView(parent) +{ + // load status and progress + connect(this, &QWebEngineView::loadStarted, this, [this]() { + m_loaded = false; + }); + connect(this, &QWebEngineView::loadFinished, this, [this]() { + m_loaded = true; + }); + + // TODO for Qt 5.15, check for fix on QTBUG 65223 + connect(this, &QWebEngineView::loadProgress, this, [this](int progress) { + if(progress == 100) { + emit loadFinished(true); + } + }); +} + +WebView::WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent) + : WebView(parent) +{ + cb_createWindow = cb; + setProfile(profile); +} + +WebView::WebView(const Session::WebView &webview_data, cb_createWindow_t cb, QWidget *parent) + : WebView(parent) +{ + cb_createWindow = cb; + WebProfileManager profileManager; + setProfile(profileManager.profile(webview_data.profile)); + + if(!webview_data.url.isEmpty()) + load(QUrl::fromUserInput(webview_data.url)); + else { + QByteArray copy(webview_data.history); + QDataStream historyStream(©, QIODevice::ReadOnly); + historyStream >> *history(); + } +} + +void WebView::setProfile(WebProfile *profile) +{ + m_profile = (profile == nullptr) ? WebProfile::defaultProfile() : profile; + const auto url = this->url(); + setPage(new WebPage(m_profile, this)); + this->load(url); +} + +Session::WebView WebView::serialize() const +{ + QByteArray historyData; + QDataStream historyStream(&historyData, QIODevice::WriteOnly); + historyStream << *history(); + + return { profile()->getId(), QString(), historyData }; +} + +void WebView::search(const QString &term) +{ + const QString searchUrl = m_profile->search().arg(QString(QUrl::toPercentEncoding(term))); + load(searchUrl); +} + +void WebView::contextMenuEvent(QContextMenuEvent *event) +{ + auto *menu = new WebViewContextMenu(this); + //const auto ctxdata = page()->contextMenuData(); + menu->exec(event->globalPos()); +} diff --git a/lib/webengine/webview.h b/lib/webengine/webview.h new file mode 100644 index 0000000..538ffa9 --- /dev/null +++ b/lib/webengine/webview.h @@ -0,0 +1,67 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_WEBVIEW_H +#define SMOLBOTE_WEBVIEW_H + +#include "smolbote/session.hpp" +#include "webpage.h" +#include +#include + +class WebProfile; +class WebViewContextMenu; +class WebView final : public QWebEngineView +{ + friend class WebViewContextMenu; + + Q_OBJECT + + explicit WebView(QWidget *parent = nullptr); + +public: + typedef std::function cb_createWindow_t; + + WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent = nullptr); + WebView(const Session::WebView &webview_data, cb_createWindow_t cb, QWidget *parent = nullptr); + ~WebView() = default; + + [[nodiscard]] WebProfile *profile() const + { + return m_profile; + } + void setProfile(WebProfile *profile); + + [[nodiscard]] Session::WebView serialize() const; + + bool isLoaded() const + { + return m_loaded; + } + +public slots: + void search(const QString &term); + +signals: + void newBookmark(const QString &title, const QUrl &url); + +protected: + WebView *createWindow(QWebEnginePage::WebWindowType type) override + { + return cb_createWindow ? cb_createWindow(type) : nullptr; + } + void contextMenuEvent(QContextMenuEvent *event) override; + +private: + cb_createWindow_t cb_createWindow; + WebProfile *m_profile = nullptr; + + bool m_loaded = false; +}; + +#endif // SMOLBOTE_WEBVIEW_H diff --git a/lib/webengine/webviewcontextmenu.cpp b/lib/webengine/webviewcontextmenu.cpp new file mode 100644 index 0000000..c9d809f --- /dev/null +++ b/lib/webengine/webviewcontextmenu.cpp @@ -0,0 +1,233 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#include "webviewcontextmenu.h" +#include "webprofilemanager.h" +#include "webview.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr int min_width = 250; +constexpr QSize button_size(32, 32); + +inline QAction *historyAction(QWebEngineView *view, const QWebEngineHistoryItem &item) +{ + auto *action = new QAction(view); + if(item.title().isEmpty()) { + action->setText(item.url().toString()); + } else { + action->setText(QObject::tr("%1 (%2)").arg(item.title(), item.url().toString())); + } + + QObject::connect(action, &QAction::triggered, view, [view, item]() { + view->history()->goToItem(item); + }); + return action; +} + +WebViewContextMenu::WebViewContextMenu(WebView *view) + : QMenu(view) +{ + setMinimumWidth(min_width); + + auto *navButtons = new QWidgetAction(this); + + auto *buttons = new QWidget(this); + auto *buttonsLayout = new QHBoxLayout; + buttonsLayout->setContentsMargins(8, 0, 8, 0); + buttonsLayout->setSpacing(2); + + auto *backButton = new QToolButton(this); + backButton->setMinimumSize(button_size); + backButton->setEnabled(view->history()->canGoBack()); + backButton->setIcon(Util::icon()); + connect(backButton, &QToolButton::clicked, view, [this, view]() { + view->back(); + this->close(); + }); + buttonsLayout->addWidget(backButton); + + auto *forwardButton = new QToolButton(this); + forwardButton->setMinimumSize(button_size); + forwardButton->setEnabled(view->history()->canGoForward()); + forwardButton->setIcon(Util::icon()); + connect(forwardButton, &QToolButton::clicked, view, [this, view]() { + view->forward(); + this->close(); + }); + buttonsLayout->addWidget(forwardButton); + + auto *refreshButton = new QToolButton(this); + refreshButton->setMinimumSize(button_size); + refreshButton->setIcon(Util::icon()); + connect(refreshButton, &QToolButton::clicked, view, [view, this]() { + view->reload(); + this->close(); + }); + buttonsLayout->addWidget(refreshButton); + + buttonsLayout->addStretch(); + + auto *muteButton = new QToolButton(this); + muteButton->setMinimumSize(button_size); + muteButton->setCheckable(true); + muteButton->setChecked(view->page()->isAudioMuted()); + muteButton->setIcon(Util::icon()); + connect(muteButton, &QToolButton::clicked, view, [view, this](bool checked) { + view->page()->setAudioMuted(checked); + this->close(); + }); + buttonsLayout->addWidget(muteButton); + + buttons->setLayout(buttonsLayout); + navButtons->setDefaultWidget(buttons); + + this->addAction(navButtons); + this->addSeparator(); + + const auto ctxdata = view->page()->contextMenuData(); + + if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeNone) { + auto *backMenu = this->addMenu(tr("Back")); + if(!view->history()->canGoBack()) { + backMenu->setEnabled(false); + } else { + connect(backMenu, &QMenu::aboutToShow, view, [view, backMenu]() { + backMenu->clear(); + const auto backItems = view->history()->backItems(10); + for(const QWebEngineHistoryItem &item : backItems) { + backMenu->addAction(historyAction(view, item)); + } + }); + } + + auto *forwardMenu = this->addMenu(tr("Forward")); + if(!view->history()->canGoForward()) { + forwardMenu->setEnabled(false); + } else { + connect(forwardMenu, &QMenu::aboutToShow, view, [view, forwardMenu]() { + forwardMenu->clear(); + const auto forwardItems = view->history()->forwardItems(10); + for(const QWebEngineHistoryItem &item : forwardItems) { + forwardMenu->addAction(historyAction(view, item)); + } + }); + } + + connect(this->addAction(tr("Reload")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::Reload); + }); + connect(this->addAction(tr("Reload and bypass Cache")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::ReloadAndBypassCache); + }); + + this->addSeparator(); + + connect(this->addAction(tr("Select All")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::SelectAll); + }); + connect(this->addAction(tr("Clear Selection")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::Unselect); + }); + connect(this->addAction(tr("Copy to clipboard")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::Copy); + }); + + } else if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeImage) { + connect(this->addAction(tr("Copy image to clipboard")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::CopyImageToClipboard); + }); + connect(this->addAction(tr("Copy image URL to clipboard")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::CopyImageUrlToClipboard); + }); + if(!ctxdata.mediaUrl().isEmpty()) { + if(view->url() != ctxdata.mediaUrl()) { + connect(this->addAction(tr("Open image")), &QAction::triggered, view, [view, ctxdata]() { + view->load(ctxdata.mediaUrl()); + }); + connect(this->addAction(tr("Open image in new tab")), &QAction::triggered, view, [view, ctxdata]() { + view->createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.mediaUrl()); + }); + } + connect(this->addAction(tr("Save image")), &QAction::triggered, view, [view, ctxdata]() { + view->page()->download(ctxdata.mediaUrl()); + }); + } + + } else { + addMenu(view->page()->createStandardContextMenu()); + } + + if(!ctxdata.linkUrl().isEmpty()) { + this->addSeparator(); + connect(this->addAction(tr("Open link in new tab")), &QAction::triggered, view, [view, ctxdata]() { + view->createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.linkUrl()); + }); + + auto *newTabMenu = this->addMenu(tr("Open link in new tab with profile")); + profileMenu(newTabMenu, [view, ctxdata](WebProfile *profile) { + auto *v = view->createWindow(QWebEnginePage::WebBrowserTab); + v->setProfile(profile); + v->load(ctxdata.linkUrl()); + }); + + connect(this->addAction(tr("Open link in new window")), &QAction::triggered, view, [view, ctxdata]() { + view->createWindow(QWebEnginePage::WebBrowserWindow)->load(ctxdata.linkUrl()); + }); + + connect(this->addAction(tr("Copy link address")), &QAction::triggered, view, [view]() { + view->page()->triggerAction(QWebEnginePage::CopyLinkToClipboard); + }); + } + + // zoom widget + { + this->addSeparator(); + + auto *zoomSlider = new QSlider(Qt::Horizontal); + zoomSlider->setMinimum(5); + zoomSlider->setMaximum(50); + zoomSlider->setValue(static_cast(view->zoomFactor() * 10)); + + auto *zoomAction = this->addAction(tr("Zoom: %1x").arg(view->zoomFactor())); + connect(zoomAction, &QAction::triggered, view, [zoomSlider]() { + zoomSlider->setValue(10); + }); + + connect(zoomSlider, &QSlider::valueChanged, view, [view, zoomAction](int value) { + zoomAction->setText(tr("Zoom: %1x").arg(static_cast(value) / 10)); + view->setZoomFactor(static_cast(value) / 10); + }); + + auto *zoomWidgetAction = new QWidgetAction(this); + zoomWidgetAction->setDefaultWidget(zoomSlider); + + this->addAction(zoomWidgetAction); + } + +#ifndef NDEBUG + /* + { + this->addSeparator(); + auto *autofillAction = this->addAction(tr("Autofill form")); + connect(autofillAction, &QAction::triggered, view, [view]() { + Wallet::autocompleteForm(view); + }); + }; + */ +#endif +} diff --git a/lib/webengine/webviewcontextmenu.h b/lib/webengine/webviewcontextmenu.h new file mode 100644 index 0000000..881670a --- /dev/null +++ b/lib/webengine/webviewcontextmenu.h @@ -0,0 +1,21 @@ +/* + * This file is part of smolbote. It's copyrighted by the contributors recorded + * in the version control history of the file, available from its original + * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote + * + * SPDX-License-Identifier: GPL-3.0 + */ + +#ifndef SMOLBOTE_WEBVIEWCONTEXTMENU_H +#define SMOLBOTE_WEBVIEWCONTEXTMENU_H + +#include + +class WebView; +class WebViewContextMenu : public QMenu +{ +public: + explicit WebViewContextMenu(WebView *view); +}; + +#endif // SMOLBOTE_WEBVIEWCONTEXTMENU_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 902fe6f..865c3f7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ add_subdirectory(autogen) add_subdirectory(about) add_subdirectory(cmd) -add_subdirectory(webengine) add_executable(poi bookmarks/builtin.cpp diff --git a/src/bookmarks/bookmarkswidget.cpp b/src/bookmarks/bookmarkswidget.cpp index c7bc5f0..969f19e 100644 --- a/src/bookmarks/bookmarkswidget.cpp +++ b/src/bookmarks/bookmarkswidget.cpp @@ -12,8 +12,8 @@ #include "mainwindow/mainwindow.h" #include "subwindow/subwindow.h" #include "ui_bookmarksform.h" -#include "webengine/webprofilemanager.h" -#include "webengine/webview.h" +#include "webprofilemanager.h" +#include "webview.h" #include #include #include diff --git a/src/browser.cpp b/src/browser.cpp index de5cdcf..000fd3a 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -17,8 +17,7 @@ #include "session_json.hpp" #include "settings.h" #include "util.h" -#include "webengine/webprofile.h" -#include "webengine/webprofilemanager.h" +#include "webprofile.h" #include #include #include diff --git a/src/browser.h b/src/browser.h index 63b280b..6a92d8b 100644 --- a/src/browser.h +++ b/src/browser.h @@ -11,7 +11,7 @@ #include "smolblok.hpp" #include "smolbote/session.hpp" -#include "webengine/webprofilemanager.h" +#include "webprofilemanager.h" #include #include #include diff --git a/src/mainwindow/addressbar.cpp b/src/mainwindow/addressbar.cpp index d37a6a7..6b2cfd8 100644 --- a/src/mainwindow/addressbar.cpp +++ b/src/mainwindow/addressbar.cpp @@ -7,10 +7,10 @@ */ #include "addressbar.h" +#include "configuration.h" #include "ui_addressbar.h" +#include "webview.h" #include -#include "configuration.h" -#include "webengine/webview.h" AddressBar::AddressBar(QWidget *parent) : QWidget(parent) diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 64c149c..0d4a978 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -13,9 +13,9 @@ #include "browser.h" #include "configuration.h" #include "menubar.h" -#include "webengine/webprofile.h" -#include "webengine/webprofilemanager.h" -#include "webengine/webview.h" +#include "webprofile.h" +#include "webprofilemanager.h" +#include "webview.h" #include "widgets/dockwidget.h" #include "widgets/navigationbar.h" #include "widgets/searchform.h" diff --git a/src/mainwindow/menubar.cpp b/src/mainwindow/menubar.cpp index 5fc039a..80cb3ea 100644 --- a/src/mainwindow/menubar.cpp +++ b/src/mainwindow/menubar.cpp @@ -13,8 +13,8 @@ #include "downloadswidget.h" #include "mainwindow.h" #include "subwindow/subwindow.h" -#include "webengine/webprofilemanager.h" -#include "webengine/webview.h" +#include "webprofilemanager.h" +#include "webview.h" #include "widgets/menusearch.h" #include #include diff --git a/src/mainwindow/widgets/navigationbar.cpp b/src/mainwindow/widgets/navigationbar.cpp index 0338bb7..dcb845c 100644 --- a/src/mainwindow/widgets/navigationbar.cpp +++ b/src/mainwindow/widgets/navigationbar.cpp @@ -10,8 +10,8 @@ #include "configuration.h" #include "urllineedit.h" #include "util.h" -#include "webengine/webprofile.h" -#include "webengine/webview.h" +#include "webprofile.h" +#include "webview.h" #include #include #include diff --git a/src/session/savesessiondialog.cpp b/src/session/savesessiondialog.cpp index e22ce3a..3bc4150 100644 --- a/src/session/savesessiondialog.cpp +++ b/src/session/savesessiondialog.cpp @@ -12,8 +12,8 @@ #include "session_json.hpp" #include "subwindow/subwindow.h" #include "ui_savesessiondialog.h" -#include "webengine/webprofile.h" -#include "webengine/webview.h" +#include "webprofile.h" +#include "webview.h" #include #include #include diff --git a/src/subwindow/subwindow.cpp b/src/subwindow/subwindow.cpp index a2d6138..60ea633 100644 --- a/src/subwindow/subwindow.cpp +++ b/src/subwindow/subwindow.cpp @@ -9,9 +9,9 @@ #include "subwindow.h" #include "browser.h" #include "configuration.h" -#include "webengine/webprofile.h" -#include "webengine/webprofilemanager.h" -#include "webengine/webview.h" +#include "webprofile.h" +#include "webprofilemanager.h" +#include "webview.h" #include #include #include diff --git a/src/subwindow/subwindow.h b/src/subwindow/subwindow.h index 80e8520..a7b4564 100644 --- a/src/subwindow/subwindow.h +++ b/src/subwindow/subwindow.h @@ -11,7 +11,7 @@ #include "smolbote/session.hpp" #include "tabwidget.h" -#include "webengine/webview.h" +#include "webview.h" #include #include #include @@ -29,7 +29,7 @@ public: explicit SubWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); explicit SubWindow(const Session::SubWindow &tab_data, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - ~SubWindow() = default; + ~SubWindow() override = default; [[nodiscard]] Session::SubWindow serialize() const; [[nodiscard]] int currentTabIndex() const diff --git a/src/subwindow/tabwidget.cpp b/src/subwindow/tabwidget.cpp index 69f3b8a..1e1d939 100644 --- a/src/subwindow/tabwidget.cpp +++ b/src/subwindow/tabwidget.cpp @@ -8,29 +8,22 @@ #include "tabwidget.h" #include "browser.h" -#include "webengine/webview.h" +#include "subwindow.h" +#include "webprofile.h" +#include "webview.h" #include #include #include #include -#include "webengine/webprofile.h" #include -#include "subwindow.h" -inline WebView *createViewFromInfo(TabWidget::TabInformation &tab, SubWindow *parent) -{ - auto *view = new WebView(tab.profile, std::bind(&SubWindow::createView, parent, std::placeholders::_1), parent); - QDataStream stream(&tab.historyBuffer, QIODevice::ReadOnly); - stream >> *view->history(); - view->history()->goToItem(view->history()->itemAt(tab.historyIndex)); - return view; -} +const QLatin1String stylesheet("QTabBar::tab { width: 200px; }"); TabWidget::TabWidget(SubWindow *parent) : QTabWidget(parent) , m_parent(parent) { - setStyleSheet("QTabBar::tab { width: 200px; }"); + setStyleSheet(stylesheet); setTabsClosable(true); setTabBarAutoHide(true); @@ -80,29 +73,6 @@ TabWidget::~TabWidget() } } -int TabWidget::addTab(WebView *view) -{ - if(view == nullptr) { - return -1; - } - - const int idx = QTabWidget::addTab(view, view->title()); - connect(view, &WebView::titleChanged, [this, view](const QString &title) { - const int current_idx = indexOf(view); - if(current_idx != -1) { - setTabText(current_idx, title); - } - }); - connect(view, &WebView::iconChanged, [this, view](const QIcon &icon) { - const int current_idx = indexOf(view); - if(current_idx != -1) { - setTabIcon(current_idx, icon); - } - }); - tabBar()->setTabData(idx, QVariant::fromValue(SubWindow::TabData{})); - return idx; -} - void TabWidget::removeTab(int index) { // deleting the widget automatically removes the tab? @@ -180,3 +150,12 @@ void TabWidget::mousePressEvent(QMouseEvent *event) QTabWidget::mousePressEvent(event); } + +WebView *TabWidget::createViewFromInfo(TabWidget::TabInformation &tab, SubWindow *parent) +{ + auto *view = new WebView(tab.profile, std::bind(&SubWindow::createView, parent, std::placeholders::_1), parent); + QDataStream stream(&tab.historyBuffer, QIODevice::ReadOnly); + stream >> *view->history(); + view->history()->goToItem(view->history()->itemAt(tab.historyIndex)); + return view; +} diff --git a/src/subwindow/tabwidget.h b/src/subwindow/tabwidget.h index de5e6fb..2a5f360 100644 --- a/src/subwindow/tabwidget.h +++ b/src/subwindow/tabwidget.h @@ -9,9 +9,17 @@ #ifndef SMOLBOTE_TABWIDGET_H #define SMOLBOTE_TABWIDGET_H -#include -#include #include +#include +#include + +template +concept c_WebView = requires(T *a) +{ + a->title(); + a->titleChanged(QString()); + a->iconChanged(QIcon()); +}; class QAction; class QMenu; @@ -24,19 +32,34 @@ class TabWidget : public QTabWidget Q_OBJECT public: - struct TabInformation - { - WebProfile *profile; - QString title; - int historyIndex; - QByteArray historyBuffer; - }; - explicit TabWidget(SubWindow *parent = nullptr); ~TabWidget() override; + template + int addTab(T *view) + { + if(view == nullptr) { + return -1; + } + + const int idx = QTabWidget::addTab(view, view->title()); + connect(view, &T::titleChanged, [this, view](const QString &title) { + const int current_idx = indexOf(view); + if(current_idx != -1) { + setTabText(current_idx, title); + } + }); + connect(view, &T::iconChanged, [this, view](const QIcon &icon) { + const int current_idx = indexOf(view); + if(current_idx != -1) { + setTabIcon(current_idx, icon); + } + }); + //tabBar()->setTabData(idx, QVariant::fromValue(SubWindow::TabData{})); + return idx; + } + public slots: - int addTab(WebView *view); void removeTab(int index); int restoreLastTab(); @@ -47,6 +70,14 @@ protected: void mousePressEvent(QMouseEvent *event) override; private: + struct TabInformation { + WebProfile *profile; + QString title; + int historyIndex; + QByteArray historyBuffer; + }; + [[nodiscard]] WebView *createViewFromInfo(TabInformation &tab, SubWindow *parent); + SubWindow *m_parent; int current = -1; int previous = -1; diff --git a/src/webengine/CMakeLists.txt b/src/webengine/CMakeLists.txt deleted file mode 100644 index 156c64a..0000000 --- a/src/webengine/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -add_library(webengine INTERFACE) -target_sources(webengine INTERFACE - webprofile.h webprofile.cpp webprofilemanager.h webprofilemanager.cpp - webpage.h webpage.cpp - webview.h webview.cpp webviewcontextmenu.h webviewcontextmenu.cpp - urlinterceptor.h urlinterceptor.cpp) -target_include_directories(webengine INTERFACE ${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(webengine INTERFACE Qt5::WebEngineWidgets autogen fmt) - -# tests -add_executable(profile_test test/profile.cpp) -target_link_libraries(profile_test PRIVATE webengine Catch2::Catch2) -#target_sanitize(profile_test) - -add_executable(profilemanager_test test/profilemanager.cpp) -target_link_libraries(profilemanager_test PRIVATE webengine Catch2::Catch2) -#target_sanitize(profilemanager_test) - -add_test(NAME webengine_profile COMMAND profile_test) -add_test(NAME webengine_profilemanager COMMAND profilemanager_test) -set_tests_properties(webengine_profile webengine_profilemanager PROPERTIES - ENVIRONMENT "PROFILE=${CMAKE_CURRENT_SOURCE_DIR}/test/testing.profile") \ No newline at end of file diff --git a/src/webengine/test/form.html b/src/webengine/test/form.html deleted file mode 100644 index 9d8b19e..0000000 --- a/src/webengine/test/form.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Form completion test - - - -

Form completion test

- - diff --git a/src/webengine/test/icon.svg b/src/webengine/test/icon.svg deleted file mode 100644 index a348cab..0000000 --- a/src/webengine/test/icon.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/webengine/test/profile.cpp b/src/webengine/test/profile.cpp deleted file mode 100644 index ae3a4e3..0000000 --- a/src/webengine/test/profile.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#define CATCH_CONFIG_RUNNER - -// clazy:excludeall=non-pod-global-static - -#include "webprofilemanager.h" -#include -#include - -TEST_CASE("loading profile settings") -{ - const QString search = GENERATE(as{}, "https://search.url/t=%1", "https://duckduckgo.com/?q=%1&ia=web", "aaabbbccc"); - const QUrl homepage = GENERATE(as{}, "https://homepage.net", "about:blank", "aaabbbccc"); - const QUrl newtab = GENERATE(as{}, "https://newtab.net", "about:blank", "aaabbbccc"); - - auto *settings = WebProfile::load(QString(), search, homepage, newtab); - - REQUIRE(settings != nullptr); - REQUIRE(settings->value("search").toString() == search); - REQUIRE(settings->value("homepage").toUrl() == homepage); - REQUIRE(settings->value("newtab").toUrl() == newtab); - - delete settings; -} - -SCENARIO("profile properties") -{ - const QString search{ "about:blank" }; - const QUrl homepage{ "about:blank" }; - const QUrl newtab{ "about:blank" }; - const QString id{ "id" }; - - REQUIRE(qEnvironmentVariableIsSet("PROFILE")); - - // create an empty settings object - const QString settings_path = GENERATE(as{}, QString(), qgetenv("PROFILE")); - auto *settings = WebProfile::load(settings_path, search, homepage, newtab); - // create the actual profile - auto *profile = WebProfile::load(id, settings, true); - - REQUIRE(profile != nullptr); - REQUIRE(profile->isOffTheRecord()); - - WHEN("id constant") - { - REQUIRE(profile->getId() == id); - REQUIRE(profile->property("id").toString() == id); - } - - WHEN("changing profile name") - { - const QString name = GENERATE(as{}, "a", "bb", "ccc"); - profile->setName(name); - THEN("the name changes") - { - REQUIRE(profile->name() == name); - REQUIRE(settings->value("name").toString() == name); - } - } - WHEN("changing search") - { - const QString search = GENERATE(as{}, "a", "bb", "ccc"); - profile->setSearch(search); - THEN("the search url changes") - { - REQUIRE(profile->search() == search); - REQUIRE(settings->value("search").toString() == search); - } - } - WHEN("changing homepage url") - { - const QUrl url = GENERATE(as{}, "a", "bb", "ccc"); - profile->setHomepage(url); - THEN("homepage changes") - { - REQUIRE(profile->homepage() == url); - REQUIRE(settings->value("homepage").toUrl() == url); - } - } - WHEN("changing newtab url") - { - const QUrl url = GENERATE(as{}, "a", "bb", "ccc"); - profile->setNewtab(url); - THEN("newtab changes") - { - REQUIRE(profile->newtab() == url); - REQUIRE(settings->value("newtab").toUrl() == url); - } - } - - WHEN("changing cookies") - { - auto list = profile->cookies(); - REQUIRE(list.isEmpty()); - list.append(QNetworkCookie("name", "value").toRawForm()); - profile->setCookies(list); - THEN("new cookie list gets applied") - { - // There is no event loop, so the signals cannot fire and update the cookie list - //REQUIRE(list == profile->cookies()); - } - } - - WHEN("changing headers") - { - QMap headers; - headers.insert("Dnt", "1"); - headers.insert("unknown", "pair"); - - profile->setHeaders(headers); - THEN("headers change") - { - REQUIRE(profile->headers() == headers); - } - } - - delete settings; - delete profile; -} - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - const auto r = Catch::Session().run(argc, argv); - return r; -} diff --git a/src/webengine/test/profilemanager.cpp b/src/webengine/test/profilemanager.cpp deleted file mode 100644 index 8f6a34f..0000000 --- a/src/webengine/test/profilemanager.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#define CATCH_CONFIG_RUNNER - -// clazy:excludeall=non-pod-global-static - -#include "webprofilemanager.h" -#include -#include - -SCENARIO("WebProfileManager") -{ - const QString search{ "https://search.url/t=%1" }; - const QUrl homepage{ "https://homepage.net" }; - const QUrl newtab{ "https://newtab.net" }; - const QString default_id{ "default" }; - - GIVEN("an empty profile list") - { - WebProfileManager profiles({}, default_id, search, homepage, newtab); - - REQUIRE(profiles.idList().count() == 1); - REQUIRE(profiles.profile(default_id) == WebProfile::defaultProfile()); - REQUIRE(profiles.profile("not-in-list") == nullptr); - - WHEN("adding a new profile") - { - const QString id{ "id" }; - auto *settings = WebProfile::load(QString(), search, homepage, newtab); - auto *profile = WebProfile::load(id, settings, true); - - THEN("doesn't add profile without settings") - { - profiles.add(id, profile, nullptr); - REQUIRE(profiles.idList().count() == 1); - } - - THEN("doesn't add settings without profile") - { - profiles.add(id, nullptr, settings); - REQUIRE(profiles.idList().count() == 1); - } - - THEN("adds new profile with settings") - { - profiles.add(id, profile, settings); - REQUIRE(profiles.idList().count() == 2); - } - } - - WHEN("moving") - { - WebProfileManager other(std::move(profiles)); - THEN("moved has the same number of profiles") - { - REQUIRE(other.idList().count() == 1); - REQUIRE(other.profile(default_id) == WebProfile::defaultProfile()); - REQUIRE(other.profile("not-in-list") == nullptr); - } - } - } - - GIVEN("a number of profiles, default undefined") - { - REQUIRE(qEnvironmentVariableIsSet("PROFILE")); - - WebProfileManager profiles(QString::fromLatin1(qgetenv("PROFILE")).split(';'), default_id, search, homepage, newtab); - - REQUIRE(profiles.idList().count() == 2); - REQUIRE(profiles.profile(default_id) == WebProfile::defaultProfile()); - REQUIRE(profiles.profile("testing") != nullptr); - REQUIRE(profiles.profile("not-in-list") == nullptr); - - WHEN("making global") - { - profiles.make_global(); - WebProfileManager other; - - THEN("global has the same number of profiles") - { - REQUIRE(other.idList().count() == 2); - REQUIRE(other.profile(default_id) == WebProfile::defaultProfile()); - REQUIRE(other.profile("testing") != nullptr); - REQUIRE(other.profile("not-in-list") == nullptr); - } - - THEN("walking has no nullptrs") - { - other.walk([](const QString &, WebProfile *profile, const QSettings *settings) { - REQUIRE(profile != nullptr); - REQUIRE(settings != nullptr); - }); - } - } - } - - GIVEN("a number of profiles, default defined") - { - REQUIRE(qEnvironmentVariableIsSet("PROFILE")); - - WebProfileManager profiles(QString::fromLatin1(qgetenv("PROFILE")).split(';'), "testing", search, homepage, newtab); - - REQUIRE(profiles.idList().count() == 1); - REQUIRE(profiles.profile("testing") == WebProfile::defaultProfile()); - REQUIRE(profiles.profile("not-in-list") == nullptr); - - WHEN("walking") - { - profiles.walk([](const QString &, WebProfile *profile, const QSettings *settings) { - REQUIRE(profile != nullptr); - REQUIRE(settings != nullptr); - }); - } - } -} - -int main(int argc, char **argv) -{ - QApplication app(argc, argv); - const auto r = Catch::Session().run(argc, argv); - return r; -} diff --git a/src/webengine/test/sample.html b/src/webengine/test/sample.html deleted file mode 100644 index 54746d5..0000000 --- a/src/webengine/test/sample.html +++ /dev/null @@ -1,7 +0,0 @@ - -sample page - -

This is a sample page

- - - diff --git a/src/webengine/test/testing.profile b/src/webengine/test/testing.profile deleted file mode 100644 index e345a3e..0000000 --- a/src/webengine/test/testing.profile +++ /dev/null @@ -1,8 +0,0 @@ -name=Test Profile -otr=true -search=https://duckduckgo.com/?q=%1&ia=web -homepage=about:blank -newtab=about:blank - -[headers] -Dnt=1 diff --git a/src/webengine/test/view.cpp b/src/webengine/test/view.cpp deleted file mode 100644 index 8aa639a..0000000 --- a/src/webengine/test/view.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#define CATCH_CONFIG_RUNNER - -// clazy:excludeall=non-pod-global-static - -#include "webprofile.h" -#include "webview.h" -#include -#include -#include -#include -#include - -SCENARIO("WebView") -{ - const QString profile_id{ "default" }; - auto *settings = WebProfile::load(qgetenv("PROFILE"), "about:blank", QUrl{ "about:blank" }, QUrl{ "about:blank" }); - auto *profile = WebProfile::load(profile_id, settings, true); - - QMainWindow window; - auto *view = new WebView(profile, nullptr); - window.setCentralWidget(view); - window.show(); - window.resize(800, 600); - - WHEN("created") - { - THEN("using the default profile") - { - REQUIRE(view->profile() == profile); - } - THEN("serialized using default profile") - { - const auto data = view->serialize(); - REQUIRE(data.profile == profile_id); - REQUIRE(data.url.isEmpty()); - REQUIRE(!data.history.isEmpty()); - } - THEN("loading a url") - { - // block until a loadFinished signal - QEventLoop pause; - QObject::connect(view, &WebView::loadFinished, &pause, &QEventLoop::quit); - view->load(QUrl{ qgetenv("URL") }); - pause.exec(); - - REQUIRE(view->isLoaded()); - } - } - - WHEN("changing profiles") - { - const QString swap_profile_id{ "swap_profile" }; - auto *swap_settings = WebProfile::load(QString(), "about:blank", QUrl{ "about:blank" }, QUrl{ "about:blank" }); - auto *swap_profile = WebProfile::load(swap_profile_id, swap_settings, true); - - view->setProfile(swap_profile); - THEN("using the swap profile") - { - REQUIRE(view->profile() == swap_profile); - } - THEN("serialized using swap profile") - { - const auto data = view->serialize(); - REQUIRE(data.profile == swap_profile_id); - REQUIRE(data.url.isEmpty()); - REQUIRE(!data.history.isEmpty()); - } - - view->setProfile(profile); - delete swap_settings; - delete swap_profile; - } - - // cleanup - window.close(); - delete view; - delete settings; - delete profile; -} - -int main(int argc, char **argv) -{ - QtWebEngine::initialize(); - QApplication app(argc, argv); - - QTimer::singleShot(0, &app, [argc, argv, &app]() { - const auto n_failed = Catch::Session().run(argc, argv); - app.exit(n_failed); - }); - - return app.exec(); -} diff --git a/src/webengine/urlinterceptor.cpp b/src/webengine/urlinterceptor.cpp deleted file mode 100644 index 047cad4..0000000 --- a/src/webengine/urlinterceptor.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "urlinterceptor.h" -#include "webprofile.h" - -// test DNT on https://browserleaks.com/donottrack - -UrlRequestInterceptor::UrlRequestInterceptor(WebProfile* profile) - : QWebEngineUrlRequestInterceptor(profile) -{ - Q_CHECK_PTR(profile); - m_profile = profile; -} - -void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) -{ - for(auto *filter : qAsConst(m_profile->m_filters)) { - filter->interceptRequest(info); - } - - // set headers - for(auto i = m_profile->m_headers.constBegin(); i != m_profile->m_headers.constEnd(); ++i) { - info.setHttpHeader(i.key(), i.value()); - } -} - diff --git a/src/webengine/urlinterceptor.h b/src/webengine/urlinterceptor.h deleted file mode 100644 index eb3ce67..0000000 --- a/src/webengine/urlinterceptor.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_URLREQUESTINTERCEPTOR_H -#define SMOLBOTE_URLREQUESTINTERCEPTOR_H - -#include - -class WebProfile; -class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor -{ - friend class WebProfile; - -public: - ~UrlRequestInterceptor() override = default; - - void interceptRequest(QWebEngineUrlRequestInfo &info) override; - -protected: - explicit UrlRequestInterceptor(WebProfile *profile); - -private: - WebProfile *m_profile; -}; - -#endif // SMOLBOTE_URLREQUESTINTERCEPTOR_H diff --git a/src/webengine/webpage.cpp b/src/webengine/webpage.cpp deleted file mode 100644 index b2b19b5..0000000 --- a/src/webengine/webpage.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "webpage.h" -#include -#include -#include -#include -#include - -[[nodiscard]] inline QString tr_terminationStatus(QWebEnginePage::RenderProcessTerminationStatus status) -{ - switch(status) { - case QWebEnginePage::NormalTerminationStatus: - return QObject::tr("The render process terminated normally."); - case QWebEnginePage::AbnormalTerminationStatus: - return QObject::tr("The render process terminated with with a non-zero exit status."); - case QWebEnginePage::CrashedTerminationStatus: - return QObject::tr("The render process crashed, for example because of a segmentation fault."); - case QWebEnginePage::KilledTerminationStatus: - return QObject::tr("The render process was killed, for example by SIGKILL or task manager kill."); - } - - return QObject::tr("The render process was terminated with an unknown status."); -} - -[[nodiscard]] inline QString feature_toString(QWebEnginePage::Feature feature) -{ - switch(feature) { - case QWebEnginePage::Notifications: - return QObject::tr("Notifications"); - case QWebEnginePage::Geolocation: - return QObject::tr("Geolocation"); - case QWebEnginePage::MediaAudioCapture: - return QObject::tr("Audio Capture"); - case QWebEnginePage::MediaVideoCapture: - return QObject::tr("Video Capture"); - case QWebEnginePage::MediaAudioVideoCapture: - return QObject::tr("Audio and Video Capture"); - case QWebEnginePage::MouseLock: - return QObject::tr("Mouse Lock"); - case QWebEnginePage::DesktopVideoCapture: - return QObject::tr("Desktop Video Capture"); - case QWebEnginePage::DesktopAudioVideoCapture: - return QObject::tr("Desktop Audio and Video Capture"); - } - - return QObject::tr("Unknown feature"); -} - -WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) - : QWebEnginePage(profile, parent) -{ - connect(this, &WebPage::fullScreenRequested, this, [](QWebEngineFullScreenRequest request) { - request.accept(); - }); - - connect(this, &QWebEnginePage::featurePermissionRequested, this, &WebPage::featurePermissionDialog); - connect(this, &QWebEnginePage::renderProcessTerminated, this, &WebPage::renderProcessCrashed); -} - -bool WebPage::certificateError(const QWebEngineCertificateError &certificateError) -{ - QMessageBox messageBox; - - messageBox.setWindowTitle(tr("SSL Error")); - if(certificateError.isOverridable()) - messageBox.setIcon(QMessageBox::Warning); - else - messageBox.setIcon(QMessageBox::Critical); - - messageBox.setText(tr("An SSL error has occurred on %1").arg(certificateError.url().toString())); - messageBox.setInformativeText(tr("

%1

" - "

This error %2 be overridden.

") - .arg(certificateError.errorDescription(), - certificateError.isOverridable() ? tr("can") : tr("cannot"))); - messageBox.setDetailedText(tr("Error code: %1").arg(certificateError.error())); - - if(certificateError.isOverridable()) { - messageBox.setStandardButtons(QMessageBox::Ignore | QMessageBox::Abort); - messageBox.setDefaultButton(QMessageBox::Ignore); - } else - messageBox.setStandardButtons(QMessageBox::Abort); - - auto resp = messageBox.exec(); - - return resp == QMessageBox::Ignore; -} - -void WebPage::featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage::Feature feature) -{ - QMessageBox messageBox; - - messageBox.setWindowTitle(tr("Feature permission request")); - messageBox.setIcon(QMessageBox::Question); - messageBox.setText(tr("

The webpage %1 has requested permission to access: %2

" - "

Allow this feature?

") - .arg(securityOrigin.toString(), feature_toString(feature))); - - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::No); - - if(messageBox.exec() == QMessageBox::Yes) { - setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser); - } else { - setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionDeniedByUser); - } -} - -void WebPage::renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) -{ - if(terminationStatus != QWebEnginePage::NormalTerminationStatus) { - QString page = "

This tab has crashed!

%message%"; - page.replace(QLatin1String("%message%"), QString("

%1
Exit code is %2.

" - "

Press here to reload this tab.

") - .arg(tr_terminationStatus(terminationStatus), QString::number(exitCode), this->url().toEncoded())); - - QTimer::singleShot(0, this, [this, page]() { - setHtml(page.toUtf8(), url()); - }); - } -} diff --git a/src/webengine/webpage.h b/src/webengine/webpage.h deleted file mode 100644 index 91ae4f3..0000000 --- a/src/webengine/webpage.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_WEBPAGE_H -#define SMOLBOTE_WEBPAGE_H - -#include - -class WebPage : public QWebEnginePage -{ - Q_OBJECT - -public: - WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); - ~WebPage() override = default; - -protected: - bool certificateError(const QWebEngineCertificateError &certificateError) override; - -protected slots: - void featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage::Feature feature); - void renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode); -}; - -#endif // SMOLBOTE_WEBPAGE_H diff --git a/src/webengine/webprofile.cpp b/src/webengine/webprofile.cpp deleted file mode 100644 index f1e71fb..0000000 --- a/src/webengine/webprofile.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "webprofile.h" -#include "urlinterceptor.h" -#include -#include -#include -#include -#include - -static WebProfile *s_profile = nullptr; - -void WebProfile::setDefaultProfile(WebProfile *profile) -{ - s_profile = profile; -} -WebProfile *WebProfile::defaultProfile() -{ - return s_profile; -} - -QSettings *WebProfile::load(const QString &path, const QString &search, const QUrl &homepage, const QUrl &newtab) -{ - auto *settings = new QSettings(path, QSettings::IniFormat); - - if(!settings->contains("search")) { - settings->setValue("search", search); - } - if(!settings->contains("homepage")) { - settings->setValue("homepage", homepage); - } - if(!settings->contains("newtab")) { - settings->setValue("newtab", newtab); - } - - return settings; -} - -WebProfile *WebProfile::load(const QString &id, QSettings *settings, bool isOffTheRecord) -{ - WebProfile *profile = nullptr; - - if(settings->value("otr", isOffTheRecord).toBool()) { - profile = new WebProfile(id, nullptr); - } else { - profile = new WebProfile(id, id, nullptr); - } - - profile->m_name = settings->value("name", id).toString(); - connect(profile, &WebProfile::nameChanged, profile, [settings](const QString &name) { settings->setValue("name", name); }); - profile->m_search = settings->value("search", "").toString(); - connect(profile, &WebProfile::searchChanged, settings, [settings](const QString &url) { settings->setValue("search", url); }); - profile->m_homepage = settings->value("homepage", "").toUrl(); - connect(profile, &WebProfile::homepageChanged, settings, [settings](const QUrl &url) { settings->setValue("homepage", url); }); - profile->m_newtab = settings->value("newtab", "").toUrl(); - connect(profile, &WebProfile::newtabChanged, settings, [settings](const QUrl &url) { settings->setValue("newtab", url); }); - - { - settings->beginGroup("properties"); - const auto keys = settings->childKeys(); - for(const QString &key : keys) { - profile->setProperty(qUtf8Printable(key), settings->value(key)); - } - settings->endGroup(); // properties - connect(profile, &WebProfile::propertyChanged, [settings](const QString &property, const QVariant &value) { - settings->setValue("properties/" + property, value); - }); - } - { - settings->beginGroup("attributes"); - const auto keys = settings->childKeys(); - auto *s = profile->settings(); - for(const QString &key : keys) { - auto attribute = static_cast(key.toInt()); - s->setAttribute(attribute, settings->value(key).toBool()); - } - settings->endGroup(); - connect(profile, &WebProfile::attributeChanged, [settings](const QWebEngineSettings::WebAttribute attr, const bool value) { - settings->setValue("attributes/" + QString::number(attr), value); - }); - } - { - // headers - settings->beginGroup("headers"); - const auto keys = settings->childKeys(); - for(const QString &key : keys) { - profile->setHttpHeader(key.toLatin1(), settings->value(key).toString().toLatin1()); - } - settings->endGroup(); - connect(profile, &WebProfile::headerChanged, [settings](const QString &name, const QString &value) { - settings->setValue("headers/" + name, value); - }); - connect(profile, &WebProfile::headerRemoved, [settings](const QString &name) { - settings->remove("headers/" + name); - }); - } - return profile; -} - -// off-the-record constructor -WebProfile::WebProfile(const QString &id, QObject *parent) - : QWebEngineProfile(parent) - , m_id(id) -{ - QWebEngineProfile::setUrlRequestInterceptor(new UrlRequestInterceptor(this)); - connect(this->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, [this](const QNetworkCookie &cookie) { - spdlog::debug("[{}]: +cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); - m_cookies.append(cookie); - }); - connect(this->cookieStore(), &QWebEngineCookieStore::cookieRemoved, this, [this](const QNetworkCookie &cookie) { - spdlog::debug("[{}]: -cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); - m_cookies.removeOne(cookie); - }); - cookieStore()->loadAllCookies(); -} - -// default constructor -WebProfile::WebProfile(const QString &id, const QString &storageName, QObject *parent) - : QWebEngineProfile(storageName, parent) - , m_id(id) -{ - QWebEngineProfile::setUrlRequestInterceptor(new UrlRequestInterceptor(this)); - connect(this->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, [this](const QNetworkCookie &cookie) { - spdlog::debug("[{}]: +cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); - m_cookies.append(cookie); - }); - connect(this->cookieStore(), &QWebEngineCookieStore::cookieRemoved, this, [this](const QNetworkCookie &cookie) { - spdlog::debug("[{}]: -cookie {}", qUtf8Printable(m_name), qUtf8Printable(cookie.name())); - m_cookies.removeOne(cookie); - }); - cookieStore()->loadAllCookies(); -} diff --git a/src/webengine/webprofile.h b/src/webengine/webprofile.h deleted file mode 100644 index 894463f..0000000 --- a/src/webengine/webprofile.h +++ /dev/null @@ -1,242 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_WEBENGINEPROFILE_H -#define SMOLBOTE_WEBENGINEPROFILE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class UrlRequestInterceptor; -class WebProfile : public QWebEngineProfile -{ - friend class UrlRequestInterceptor; - - Q_OBJECT - - Q_PROPERTY(QString id READ getId CONSTANT) - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - Q_PROPERTY(QString search READ search WRITE setSearch NOTIFY searchChanged) - Q_PROPERTY(QUrl homepage READ homepage WRITE setHomepage NOTIFY homepageChanged) - Q_PROPERTY(QUrl newtab READ newtab WRITE setNewtab NOTIFY newtabChanged) - - // QWebEngineProfile should-be properties - Q_PROPERTY(QString cachePath READ cachePath WRITE setCachePath NOTIFY propertyChanged) - Q_PROPERTY(QString persistentStoragePath READ persistentStoragePath WRITE setPersistentStoragePath NOTIFY propertyChanged) - Q_PROPERTY(int persistentCookiesPolicy READ persistentCookiesPolicy WRITE setPersistentCookiesPolicy NOTIFY propertyChanged) - - Q_PROPERTY(QString httpAcceptLanguage READ httpAcceptLanguage WRITE setHttpAcceptLanguage NOTIFY propertyChanged) - Q_PROPERTY(int httpCacheMaximumSize READ httpCacheMaximumSize WRITE setHttpCacheMaximumSize NOTIFY propertyChanged) - Q_PROPERTY(int httpCacheType READ httpCacheType WRITE setHttpCacheType NOTIFY propertyChanged) - Q_PROPERTY(QString httpUserAgent READ httpUserAgent WRITE setHttpUserAgent NOTIFY propertyChanged) - - Q_PROPERTY(bool spellCheckEnabled READ isSpellCheckEnabled WRITE setSpellCheckEnabled NOTIFY propertyChanged) - - // more custom properties - Q_PROPERTY(QList cookies READ cookies WRITE setCookies NOTIFY cookiesChanged) - Q_PROPERTY(QMap headers READ headers WRITE setHeaders NOTIFY headersChanged) - -signals: - void nameChanged(const QString &name); - void searchChanged(const QString &url); - void homepageChanged(const QUrl &url); - void newtabChanged(const QUrl &url); - - void propertyChanged(const QString &name, const QVariant &value); - void attributeChanged(QWebEngineSettings::WebAttribute attribute, bool value); - - void cookiesChanged(); - void headersChanged(); - void headerChanged(const QString &name, const QString &value); - void headerRemoved(const QString &name); - -public: - [[nodiscard]] static QSettings *load(const QString &path, const QString &search = QString(), const QUrl &homepage = QUrl(), const QUrl &newtab = QUrl()); - [[nodiscard]] static WebProfile *load(const QString &id, QSettings *settings, bool isOffTheRecord = true); - - WebProfile(const WebProfile &) = delete; - WebProfile &operator=(const WebProfile &) = delete; - WebProfile(WebProfile &&) = delete; - WebProfile &operator=(WebProfile &&) = delete; - - static WebProfile *defaultProfile(); - static void setDefaultProfile(WebProfile *profile); - - ~WebProfile() override = default; - - [[nodiscard]] QString getId() const - { - return m_id; - } - [[nodiscard]] QString name() const - { - return m_name; - } - void setName(const QString &name) - { - m_name = name; - emit nameChanged(name); - } - - [[nodiscard]] QList cookies() const - { - QList r; - for(const auto &cookie : m_cookies) { - r.append(cookie.toRawForm()); - } - return r; - } - - [[nodiscard]] QString search() const - { - return m_search; - } - void setSearch(const QString &url) - { - m_search = url; - emit searchChanged(m_search); - } - - [[nodiscard]] QUrl homepage() const - { - return m_homepage; - } - void setHomepage(const QUrl &url) - { - m_homepage = url; - emit homepageChanged(m_homepage); - } - - [[nodiscard]] QUrl newtab() const - { - return m_newtab; - } - void setNewtab(const QUrl &url) - { - m_newtab = url; - emit newtabChanged(m_newtab); - } - - void setCachePath(const QString &path) - { - QWebEngineProfile::setCachePath(path); - emit propertyChanged("cachePath", path); - } - void setPersistentStoragePath(const QString &path) - { - QWebEngineProfile::setPersistentStoragePath(path); - emit propertyChanged("persistentStoragePath", path); - } - void setPersistentCookiesPolicy(int policy) - { - QWebEngineProfile::setPersistentCookiesPolicy(static_cast(policy)); - emit propertyChanged("persistentCookiesPolicy", policy); - } - - void setHttpAcceptLanguage(const QString &httpAcceptLanguage) - { - QWebEngineProfile::setHttpAcceptLanguage(httpAcceptLanguage); - emit propertyChanged("httpAcceptLanguage", httpAcceptLanguage); - } - void setHttpCacheMaximumSize(int maxSize) - { - QWebEngineProfile::setHttpCacheMaximumSize(maxSize); - emit propertyChanged("httpCacheMaximumSize", maxSize); - } - void setHttpCacheType(int type) - { - QWebEngineProfile::setHttpCacheType(static_cast(type)); - emit propertyChanged("httpCacheType", type); - } - void setHttpUserAgent(const QString &userAgent) - { - QWebEngineProfile::setHttpUserAgent(userAgent); - emit propertyChanged("httpUserAgent", userAgent); - } - void setHttpHeader(const QString &name, const QString &value) - { - m_headers[name.toLatin1()] = value.toLatin1(); - emit headerChanged(name, value); - } - void removeHttpHeader(const QString &name) - { - if(m_headers.contains(name.toLatin1())) { - m_headers.remove(name.toLatin1()); - emit headerRemoved(name); - } - } - [[nodiscard]] QMap headers() const - { - QMap r; - auto it = m_headers.constBegin(); - while(it != m_headers.constEnd()) { - r.insert(QString(it.key()), QVariant(it.value())); - ++it; - } - return r; - } - void setSpellCheckEnabled(bool enable) - { - QWebEngineProfile::setSpellCheckEnabled(enable); - emit propertyChanged("spellCheckEnabed", enable); - } - - void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor) - { - m_filters.append(interceptor); - } - -public slots: - void setCookies(const QList &cookies) - { - auto *store = cookieStore(); - store->deleteAllCookies(); - for(const auto &data : cookies) { - for(auto &cookie : QNetworkCookie::parseCookies(data.toByteArray())) { - store->setCookie(cookie); - } - } - emit cookiesChanged(); - } - void setHeaders(const QMap &headers) - { - m_headers.clear(); - auto it = headers.constBegin(); - while(it != headers.constEnd()) { - m_headers.insert(it.key().toLatin1(), it.value().toByteArray()); - ++it; - } - emit headersChanged(); - } - -protected: - // off-the-record constructor - explicit WebProfile(const QString &id, QObject *parent = nullptr); - // default constructor - explicit WebProfile(const QString &id, const QString &storageName, QObject *parent = nullptr); - - const QString m_id; - QString m_name; - QString m_search = QString("about:blank"); - QUrl m_homepage = QUrl("about:blank"); - QUrl m_newtab = QUrl("about:blank"); - - QVector m_filters; - QList m_cookies; - QMap m_headers; -}; - -#endif // SMOLBOTE_WEBENGINEPROFILE_H diff --git a/src/webengine/webprofilemanager.cpp b/src/webengine/webprofilemanager.cpp deleted file mode 100644 index 5cc83f8..0000000 --- a/src/webengine/webprofilemanager.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "webprofilemanager.h" -#include "webprofile.h" - -static WebProfileManager *s_instance = nullptr; - -template <> -void WebProfileManager::make_global() -{ - if(s_instance == nullptr) { - s_instance = this; - } -} - -template <> -WebProfileManager::~WebProfileManager() = default; - -template <> -WebProfileManager::~WebProfileManager() -{ - for(Profile p : qAsConst(profiles)) { - if(p.selfDestruct && p.settings != nullptr) { - if(!p.ptr->isOffTheRecord()) { - if(!p.ptr->persistentStoragePath().isEmpty()) - QDir(p.ptr->persistentStoragePath()).removeRecursively(); - if(!p.ptr->cachePath().isEmpty()) - QDir(p.ptr->cachePath()).removeRecursively(); - } - const QString filename = p.settings->fileName(); - delete p.settings; - QFile::remove(filename); - } else if(p.settings != nullptr) { - p.settings->sync(); - delete p.settings; - } - } -} - -template <> -WebProfile *WebProfileManager::profile(const QString &id) const -{ - return s_instance->profile(id); -} - -template <> -QStringList WebProfileManager::idList() const -{ - return s_instance->idList(); -} - -template <> -void WebProfileManager::walk(std::function f) const -{ - for(auto iter = profiles.begin(); iter != profiles.end(); ++iter) { - f(iter.key(), iter.value().ptr, iter.value().settings); - } -} - -template <> -void WebProfileManager::walk(std::function f) const -{ - s_instance->walk(f); -} - -void profileMenu(QMenu *menu, const std::function &callback, WebProfile *current, bool checkable) -{ - auto *group = new QActionGroup(menu); - QObject::connect(menu, &QMenu::aboutToHide, group, &QActionGroup::deleteLater); - - s_instance->walk([=](const QString &, WebProfile *profile, const QSettings *) { - auto *action = menu->addAction(profile->name(), profile, [=]() { callback(profile); }); - action->setCheckable(checkable); - action->setChecked(profile == current); - group->addAction(action); - }); -} diff --git a/src/webengine/webprofilemanager.h b/src/webengine/webprofilemanager.h deleted file mode 100644 index e5df6d5..0000000 --- a/src/webengine/webprofilemanager.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_WEBPROFILEMANAGER_H -#define SMOLBOTE_WEBPROFILEMANAGER_H - -#include "webprofile.h" -#include -#include -#include -#include -#include -#include - -#if defined(__clang__) -#define consumable(X) [[clang::consumable(X)]] -#define return_typestate(X) [[clang::return_typestate(X)]] -#define set_typestate(X) [[clang::set_typestate(X)]] -#define callable_when(X) [[clang::callable_when(X)]] -#define param_typestate(X) [[clang::param_typestate(X)]] -#else -#define consumable(X) -#define return_typestate(X) -#define set_typestate(X) -#define callable_when(X) -#define param_typestate(X) -#endif - -void profileMenu(QMenu *menu, const std::function &callback, WebProfile *current = nullptr, bool checkable = false); - -template -class consumable(unconsumed) WebProfileManager -{ -public: - return_typestate(unconsumed) WebProfileManager() - { - static_assert(use_global); - } - return_typestate(unconsumed) WebProfileManager(const QStringList &paths, const QString &default_id, - const QString &search = QString(), const QUrl &homepage = QUrl(), const QUrl &newtab = QUrl()) - { - static_assert(!use_global); - for(const auto &path : paths) { - const auto id = QFileInfo(path).baseName(); - Profile profile; - profile.settings = WebProfile::load(path, search, homepage, newtab); - profile.ptr = WebProfile::load(id, profile.settings, true); - profiles[id] = profile; - } - - if(!profiles.contains(default_id)) { - auto *settings = WebProfile::load(QString(), search, homepage, newtab); - profiles[default_id] = Profile{ - .settings = settings, - .ptr = WebProfile::load(default_id, settings, true), - }; - } - WebProfile::setDefaultProfile(profiles[default_id].ptr); - } - ~WebProfileManager(); - - WebProfileManager(const WebProfileManager &) = delete; - WebProfileManager &operator=(const WebProfileManager &) = delete; - - return_typestate(unconsumed) WebProfileManager(WebProfileManager && other param_typestate(unconsumed)) - { - static_assert(!use_global); - profiles = std::move(other.profiles); - other.consume(); - } - WebProfileManager &operator=(WebProfileManager &&) = delete; - - callable_when(unconsumed) [[nodiscard]] WebProfile *profile(const QString &id) const - { - return profiles.value(id).ptr; - } - - callable_when(unconsumed) void add(const QString &id, WebProfile *profile, QSettings *settings) - { - if constexpr(use_global) { - return; - } - - if(profile != nullptr && settings != nullptr) { - profiles[id] = Profile{ settings, profile, false }; - } - } - - callable_when(unconsumed) void deleteProfile(const QString &id) - { - if constexpr(use_global) { - return; - } - - if(profiles.contains(id)) { - profiles[id].selfDestruct = true; - } - } - - callable_when(unconsumed) [[nodiscard]] QStringList idList() const - { - return profiles.keys(); - } - - callable_when(unconsumed) void walk(std::function) const; - - callable_when(unconsumed) void make_global(); - -private: - set_typestate(consumed) void consume() {} - - struct Profile { - QSettings *settings = nullptr; - WebProfile *ptr = nullptr; - bool selfDestruct = false; - }; - - QMap profiles; -}; - -#endif // SMOLBOTE_PROFILEMANAGER_H diff --git a/src/webengine/webview.cpp b/src/webengine/webview.cpp deleted file mode 100644 index bc52102..0000000 --- a/src/webengine/webview.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "webview.h" -#include "webpage.h" -#include "webprofile.h" -#include "webprofilemanager.h" -#include "webviewcontextmenu.h" -#include -#include - -WebView::WebView(QWidget *parent) - : QWebEngineView(parent) -{ - // load status and progress - connect(this, &QWebEngineView::loadStarted, this, [this]() { - m_loaded = false; - }); - connect(this, &QWebEngineView::loadFinished, this, [this]() { - m_loaded = true; - }); - - // TODO for Qt 5.15, check for fix on QTBUG 65223 - connect(this, &QWebEngineView::loadProgress, this, [this](int progress) { - if(progress == 100) { - emit loadFinished(true); - } - }); -} - -WebView::WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent) - : WebView(parent) -{ - cb_createWindow = cb; - setProfile(profile); -} - -WebView::WebView(const Session::WebView &webview_data, cb_createWindow_t cb, QWidget *parent) - : WebView(parent) -{ - cb_createWindow = cb; - WebProfileManager profileManager; - setProfile(profileManager.profile(webview_data.profile)); - - if(!webview_data.url.isEmpty()) - load(QUrl::fromUserInput(webview_data.url)); - else { - QByteArray copy(webview_data.history); - QDataStream historyStream(©, QIODevice::ReadOnly); - historyStream >> *history(); - } -} - -void WebView::setProfile(WebProfile *profile) -{ - m_profile = (profile == nullptr) ? WebProfile::defaultProfile() : profile; - const auto url = this->url(); - setPage(new WebPage(m_profile, this)); - this->load(url); -} - -Session::WebView WebView::serialize() const -{ - QByteArray historyData; - QDataStream historyStream(&historyData, QIODevice::WriteOnly); - historyStream << *history(); - - return { profile()->getId(), QString(), historyData }; -} - -void WebView::search(const QString &term) -{ - const QString searchUrl = m_profile->search().arg(QString(QUrl::toPercentEncoding(term))); - load(searchUrl); -} - -void WebView::contextMenuEvent(QContextMenuEvent *event) -{ - auto *menu = new WebViewContextMenu(this); - //const auto ctxdata = page()->contextMenuData(); - menu->exec(event->globalPos()); -} diff --git a/src/webengine/webview.h b/src/webengine/webview.h deleted file mode 100644 index 538ffa9..0000000 --- a/src/webengine/webview.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_WEBVIEW_H -#define SMOLBOTE_WEBVIEW_H - -#include "smolbote/session.hpp" -#include "webpage.h" -#include -#include - -class WebProfile; -class WebViewContextMenu; -class WebView final : public QWebEngineView -{ - friend class WebViewContextMenu; - - Q_OBJECT - - explicit WebView(QWidget *parent = nullptr); - -public: - typedef std::function cb_createWindow_t; - - WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent = nullptr); - WebView(const Session::WebView &webview_data, cb_createWindow_t cb, QWidget *parent = nullptr); - ~WebView() = default; - - [[nodiscard]] WebProfile *profile() const - { - return m_profile; - } - void setProfile(WebProfile *profile); - - [[nodiscard]] Session::WebView serialize() const; - - bool isLoaded() const - { - return m_loaded; - } - -public slots: - void search(const QString &term); - -signals: - void newBookmark(const QString &title, const QUrl &url); - -protected: - WebView *createWindow(QWebEnginePage::WebWindowType type) override - { - return cb_createWindow ? cb_createWindow(type) : nullptr; - } - void contextMenuEvent(QContextMenuEvent *event) override; - -private: - cb_createWindow_t cb_createWindow; - WebProfile *m_profile = nullptr; - - bool m_loaded = false; -}; - -#endif // SMOLBOTE_WEBVIEW_H diff --git a/src/webengine/webviewcontextmenu.cpp b/src/webengine/webviewcontextmenu.cpp deleted file mode 100644 index c9d809f..0000000 --- a/src/webengine/webviewcontextmenu.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#include "webviewcontextmenu.h" -#include "webprofilemanager.h" -#include "webview.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr int min_width = 250; -constexpr QSize button_size(32, 32); - -inline QAction *historyAction(QWebEngineView *view, const QWebEngineHistoryItem &item) -{ - auto *action = new QAction(view); - if(item.title().isEmpty()) { - action->setText(item.url().toString()); - } else { - action->setText(QObject::tr("%1 (%2)").arg(item.title(), item.url().toString())); - } - - QObject::connect(action, &QAction::triggered, view, [view, item]() { - view->history()->goToItem(item); - }); - return action; -} - -WebViewContextMenu::WebViewContextMenu(WebView *view) - : QMenu(view) -{ - setMinimumWidth(min_width); - - auto *navButtons = new QWidgetAction(this); - - auto *buttons = new QWidget(this); - auto *buttonsLayout = new QHBoxLayout; - buttonsLayout->setContentsMargins(8, 0, 8, 0); - buttonsLayout->setSpacing(2); - - auto *backButton = new QToolButton(this); - backButton->setMinimumSize(button_size); - backButton->setEnabled(view->history()->canGoBack()); - backButton->setIcon(Util::icon()); - connect(backButton, &QToolButton::clicked, view, [this, view]() { - view->back(); - this->close(); - }); - buttonsLayout->addWidget(backButton); - - auto *forwardButton = new QToolButton(this); - forwardButton->setMinimumSize(button_size); - forwardButton->setEnabled(view->history()->canGoForward()); - forwardButton->setIcon(Util::icon()); - connect(forwardButton, &QToolButton::clicked, view, [this, view]() { - view->forward(); - this->close(); - }); - buttonsLayout->addWidget(forwardButton); - - auto *refreshButton = new QToolButton(this); - refreshButton->setMinimumSize(button_size); - refreshButton->setIcon(Util::icon()); - connect(refreshButton, &QToolButton::clicked, view, [view, this]() { - view->reload(); - this->close(); - }); - buttonsLayout->addWidget(refreshButton); - - buttonsLayout->addStretch(); - - auto *muteButton = new QToolButton(this); - muteButton->setMinimumSize(button_size); - muteButton->setCheckable(true); - muteButton->setChecked(view->page()->isAudioMuted()); - muteButton->setIcon(Util::icon()); - connect(muteButton, &QToolButton::clicked, view, [view, this](bool checked) { - view->page()->setAudioMuted(checked); - this->close(); - }); - buttonsLayout->addWidget(muteButton); - - buttons->setLayout(buttonsLayout); - navButtons->setDefaultWidget(buttons); - - this->addAction(navButtons); - this->addSeparator(); - - const auto ctxdata = view->page()->contextMenuData(); - - if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeNone) { - auto *backMenu = this->addMenu(tr("Back")); - if(!view->history()->canGoBack()) { - backMenu->setEnabled(false); - } else { - connect(backMenu, &QMenu::aboutToShow, view, [view, backMenu]() { - backMenu->clear(); - const auto backItems = view->history()->backItems(10); - for(const QWebEngineHistoryItem &item : backItems) { - backMenu->addAction(historyAction(view, item)); - } - }); - } - - auto *forwardMenu = this->addMenu(tr("Forward")); - if(!view->history()->canGoForward()) { - forwardMenu->setEnabled(false); - } else { - connect(forwardMenu, &QMenu::aboutToShow, view, [view, forwardMenu]() { - forwardMenu->clear(); - const auto forwardItems = view->history()->forwardItems(10); - for(const QWebEngineHistoryItem &item : forwardItems) { - forwardMenu->addAction(historyAction(view, item)); - } - }); - } - - connect(this->addAction(tr("Reload")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::Reload); - }); - connect(this->addAction(tr("Reload and bypass Cache")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::ReloadAndBypassCache); - }); - - this->addSeparator(); - - connect(this->addAction(tr("Select All")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::SelectAll); - }); - connect(this->addAction(tr("Clear Selection")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::Unselect); - }); - connect(this->addAction(tr("Copy to clipboard")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::Copy); - }); - - } else if(ctxdata.mediaType() == QWebEngineContextMenuData::MediaTypeImage) { - connect(this->addAction(tr("Copy image to clipboard")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::CopyImageToClipboard); - }); - connect(this->addAction(tr("Copy image URL to clipboard")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::CopyImageUrlToClipboard); - }); - if(!ctxdata.mediaUrl().isEmpty()) { - if(view->url() != ctxdata.mediaUrl()) { - connect(this->addAction(tr("Open image")), &QAction::triggered, view, [view, ctxdata]() { - view->load(ctxdata.mediaUrl()); - }); - connect(this->addAction(tr("Open image in new tab")), &QAction::triggered, view, [view, ctxdata]() { - view->createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.mediaUrl()); - }); - } - connect(this->addAction(tr("Save image")), &QAction::triggered, view, [view, ctxdata]() { - view->page()->download(ctxdata.mediaUrl()); - }); - } - - } else { - addMenu(view->page()->createStandardContextMenu()); - } - - if(!ctxdata.linkUrl().isEmpty()) { - this->addSeparator(); - connect(this->addAction(tr("Open link in new tab")), &QAction::triggered, view, [view, ctxdata]() { - view->createWindow(QWebEnginePage::WebBrowserTab)->load(ctxdata.linkUrl()); - }); - - auto *newTabMenu = this->addMenu(tr("Open link in new tab with profile")); - profileMenu(newTabMenu, [view, ctxdata](WebProfile *profile) { - auto *v = view->createWindow(QWebEnginePage::WebBrowserTab); - v->setProfile(profile); - v->load(ctxdata.linkUrl()); - }); - - connect(this->addAction(tr("Open link in new window")), &QAction::triggered, view, [view, ctxdata]() { - view->createWindow(QWebEnginePage::WebBrowserWindow)->load(ctxdata.linkUrl()); - }); - - connect(this->addAction(tr("Copy link address")), &QAction::triggered, view, [view]() { - view->page()->triggerAction(QWebEnginePage::CopyLinkToClipboard); - }); - } - - // zoom widget - { - this->addSeparator(); - - auto *zoomSlider = new QSlider(Qt::Horizontal); - zoomSlider->setMinimum(5); - zoomSlider->setMaximum(50); - zoomSlider->setValue(static_cast(view->zoomFactor() * 10)); - - auto *zoomAction = this->addAction(tr("Zoom: %1x").arg(view->zoomFactor())); - connect(zoomAction, &QAction::triggered, view, [zoomSlider]() { - zoomSlider->setValue(10); - }); - - connect(zoomSlider, &QSlider::valueChanged, view, [view, zoomAction](int value) { - zoomAction->setText(tr("Zoom: %1x").arg(static_cast(value) / 10)); - view->setZoomFactor(static_cast(value) / 10); - }); - - auto *zoomWidgetAction = new QWidgetAction(this); - zoomWidgetAction->setDefaultWidget(zoomSlider); - - this->addAction(zoomWidgetAction); - } - -#ifndef NDEBUG - /* - { - this->addSeparator(); - auto *autofillAction = this->addAction(tr("Autofill form")); - connect(autofillAction, &QAction::triggered, view, [view]() { - Wallet::autocompleteForm(view); - }); - }; - */ -#endif -} diff --git a/src/webengine/webviewcontextmenu.h b/src/webengine/webviewcontextmenu.h deleted file mode 100644 index 881670a..0000000 --- a/src/webengine/webviewcontextmenu.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is part of smolbote. It's copyrighted by the contributors recorded - * in the version control history of the file, available from its original - * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote - * - * SPDX-License-Identifier: GPL-3.0 - */ - -#ifndef SMOLBOTE_WEBVIEWCONTEXTMENU_H -#define SMOLBOTE_WEBVIEWCONTEXTMENU_H - -#include - -class WebView; -class WebViewContextMenu : public QMenu -{ -public: - explicit WebViewContextMenu(WebView *view); -}; - -#endif // SMOLBOTE_WEBVIEWCONTEXTMENU_H -- cgit v1.2.1