aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/about/aboutplugin.cpp77
-rw-r--r--src/about/aboutplugin.h5
-rw-r--r--src/about/aboutplugin.ui181
-rw-r--r--src/applicationmenu.cpp68
-rw-r--r--src/applicationmenu.h31
-rw-r--r--src/browser.cpp99
-rw-r--r--src/browser.h18
-rw-r--r--src/mainwindow/mainwindow.cpp11
-rw-r--r--src/mainwindow/mainwindow.h4
-rw-r--r--src/mainwindow/menubar.cpp102
-rw-r--r--src/mainwindow/menubar.h5
-rw-r--r--src/meson.build15
-rw-r--r--src/session/savesessiondialog.cpp6
-rw-r--r--src/subwindow/subwindow.cpp21
-rw-r--r--src/subwindow/subwindow.h2
-rw-r--r--src/subwindow/tabwidget.cpp11
-rw-r--r--src/subwindow/tabwidget.h4
-rw-r--r--src/webengine/meson.build27
-rw-r--r--src/webengine/test/form.html10
-rw-r--r--src/webengine/test/icon.svg7
-rw-r--r--src/webengine/test/profile.cpp124
-rw-r--r--src/webengine/test/profilemanager.cpp120
-rw-r--r--src/webengine/test/sample.html7
-rw-r--r--src/webengine/test/testing.profile8
-rw-r--r--src/webengine/test/view.cpp92
-rw-r--r--src/webengine/urlinterceptor.cpp45
-rw-r--r--src/webengine/urlinterceptor.h17
-rw-r--r--src/webengine/webpage.cpp8
-rw-r--r--src/webengine/webpage.h6
-rw-r--r--src/webengine/webprofile.cpp198
-rw-r--r--src/webengine/webprofile.h167
-rw-r--r--src/webengine/webprofilemanager.cpp147
-rw-r--r--src/webengine/webprofilemanager.h102
-rw-r--r--src/webengine/webview.cpp54
-rw-r--r--src/webengine/webview.h20
-rw-r--r--src/webengine/webviewcontextmenu.cpp3
36 files changed, 1015 insertions, 807 deletions
diff --git a/src/about/aboutplugin.cpp b/src/about/aboutplugin.cpp
index 99c04ec..92e55bb 100644
--- a/src/about/aboutplugin.cpp
+++ b/src/about/aboutplugin.cpp
@@ -10,6 +10,7 @@
#include "ui_aboutplugin.h"
#include <QJsonArray>
#include <QPluginLoader>
+#include <QToolButton>
QTreeWidgetItem *createItem(const QString &key, const QJsonValue &json, QTreeWidgetItem *parent)
{
@@ -54,58 +55,50 @@ QTreeWidgetItem *createItem(const QString &key, const QJsonValue &json, QTreeWid
return item;
}
-AboutPluginDialog::AboutPluginDialog(QPluginLoader *loader, QWidget *parent)
+AboutPluginDialog::AboutPluginDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::AboutPluginDialog)
{
setAttribute(Qt::WA_DeleteOnClose, true);
ui->setupUi(this);
- // load button icon
- {
- QIcon load_icon;
- load_icon.addPixmap(style()->standardPixmap(QStyle::SP_MediaPlay), QIcon::Normal, QIcon::On);
- load_icon.addPixmap(style()->standardPixmap(QStyle::SP_MediaStop), QIcon::Normal, QIcon::Off);
- ui->load->setIcon(load_icon);
- }
-
- auto metaData = loader->metaData()["MetaData"].toObject();
-
- this->setWindowTitle(metaData["name"].toString());
-
- ui->path->setText(loader->fileName());
- ui->load->setChecked(loader->isLoaded());
-
- connect(ui->load, &QToolButton::clicked, this, [this, loader](bool checked) {
- if(checked) {
- // load plugin
- if(!loader->load()) {
- ui->load->setChecked(false);
- ui->error->setText(loader->errorString());
- }
- } else {
- // unload plugin
- if(!loader->unload()) {
- ui->load->setChecked(true);
- ui->error->setText(loader->errorString());
- }
- }
- });
-
- ui->name->setText(metaData[QLatin1String("name")].toString());
- ui->author->setText(metaData[QLatin1String("author")].toString());
- ui->license->setText(metaData[QLatin1String("license")].toString());
- ui->shortcut->setText(metaData[QLatin1String("shortcut")].toString());
- for(const QString &key : loader->metaData().keys()) {
- auto *i = createItem(key, loader->metaData()[key], nullptr);
-
- if(i != nullptr)
- ui->details_treeWidget->insertTopLevelItem(0, i);
- }
}
AboutPluginDialog::~AboutPluginDialog()
{
delete ui;
}
+
+void AboutPluginDialog::add(QPluginLoader *loader)
+{
+ const auto index = ui->tableWidget->rowCount();
+ ui->tableWidget->setRowCount(index + 1);
+
+ const auto metadata = loader->metaData()["MetaData"].toObject();
+ ui->tableWidget->setItem(index, 0, new QTableWidgetItem(metadata.value("name").toString()));
+ ui->tableWidget->setItem(index, 1, new QTableWidgetItem(metadata.value("author").toString()));
+ ui->tableWidget->setItem(index, 2, new QTableWidgetItem(metadata.value("license").toString()));
+ ui->tableWidget->setItem(index, 3, new QTableWidgetItem(metadata.value("shortcut").toString()));
+ ui->tableWidget->setItem(index, 5, new QTableWidgetItem(loader->fileName()));
+
+ auto *enable_btn = new QToolButton(this);
+ enable_btn->setCheckable(true);
+ enable_btn->setChecked(loader->isLoaded());
+ ui->tableWidget->setCellWidget(index, 4, enable_btn);
+
+ connect(enable_btn, &QToolButton::clicked, this, [ loader, enable_btn ](bool checked) {
+ const bool success = checked ? loader->load() : loader->unload();
+ if(!success) {
+ enable_btn->setChecked(!checked);
+ //ui->error->setText(loader->errorString());
+ }
+ });
+
+ for(const QString &key : metadata.keys()) {
+ auto *i = createItem(key, metadata.value(key), nullptr);
+ if(i != nullptr) {
+ ui->details_treeWidget->insertTopLevelItem(0, i);
+ }
+ }
+}
diff --git a/src/about/aboutplugin.h b/src/about/aboutplugin.h
index 9651060..b01cc78 100644
--- a/src/about/aboutplugin.h
+++ b/src/about/aboutplugin.h
@@ -21,9 +21,12 @@ class AboutPluginDialog : public QDialog
Q_OBJECT
public:
- explicit AboutPluginDialog(QPluginLoader *loader, QWidget *parent = nullptr);
+ explicit AboutPluginDialog(QWidget *parent = nullptr);
~AboutPluginDialog() override;
+public slots:
+ void add(QPluginLoader *loader);
+
private:
Ui::AboutPluginDialog *ui;
};
diff --git a/src/about/aboutplugin.ui b/src/about/aboutplugin.ui
index 5df1c0d..d4291e5 100644
--- a/src/about/aboutplugin.ui
+++ b/src/about/aboutplugin.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>500</width>
- <height>400</height>
+ <width>600</width>
+ <height>420</height>
</rect>
</property>
<property name="windowTitle">
@@ -15,135 +15,54 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="general_tab">
- <attribute name="title">
- <string>General</string>
- </attribute>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="name_label">
- <property name="text">
- <string>Name</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="name">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="author_label">
- <property name="text">
- <string>Author</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="author">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="shortcut_label">
- <property name="text">
- <string>Shortcut</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="shortcut">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <layout class="QHBoxLayout" name="controls_layout"/>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="license">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="license_label">
- <property name="text">
- <string>License</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="details_tab">
- <attribute name="title">
- <string>Details</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QLabel" name="path">
- <property name="text">
- <string>TextLabel</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTreeWidget" name="details_treeWidget">
- <column>
- <property name="text">
- <string>Key</string>
- </property>
- </column>
- <column>
- <property name="text">
- <string>Value</string>
- </property>
- </column>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="controls_tab">
- <attribute name="title">
- <string>Controls</string>
- </attribute>
- <layout class="QFormLayout" name="formLayout_2">
- <item row="0" column="1">
- <widget class="QToolButton" name="load">
- <property name="text">
- <string>Load</string>
- </property>
- <property name="checkable">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="error">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="error_label">
- <property name="text">
- <string>Status</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
+ <widget class="QTableWidget" name="tableWidget">
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Author</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>License</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Shortcut</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Enable</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Path</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeWidget" name="details_treeWidget">
+ <column>
+ <property name="text">
+ <string>Key</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </column>
</widget>
</item>
<item>
diff --git a/src/applicationmenu.cpp b/src/applicationmenu.cpp
new file mode 100644
index 0000000..b7acd57
--- /dev/null
+++ b/src/applicationmenu.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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/cgit/smolbote
+ *
+ * SPDX-License-Identifier: GPL-3.0
+ */
+
+#include "applicationmenu.h"
+#include "browser.h"
+#include "configuration.h"
+#include "session/savesessiondialog.h"
+#include "session/sessiondialog.h"
+#include "smolbote/plugininterface.hpp"
+#include <QPluginLoader>
+
+ApplicationMenu::ApplicationMenu(Browser *app, QWidget *parent)
+ : QMenu(parent)
+{
+ setTitle(qApp->applicationName());
+ Configuration conf;
+
+ const auto sessionPath = conf.value<QString>("session.path").value();
+ auto *actionSaveSession = addAction(tr("Save Session"), this, [sessionPath]() {
+ auto *sessionDialog = new SaveSessionDialog(nullptr);
+ if(sessionDialog->exec() == QDialog::Accepted) {
+ sessionDialog->save(sessionPath);
+ }
+ });
+ conf.shortcut<QAction>(*actionSaveSession, "shortcuts.session.save");
+
+ auto *actionOpenSession = addAction(tr("Open Session"), this, []() {
+ auto *sessionDialog = new SessionDialog(nullptr);
+ sessionDialog->exec();
+ });
+ conf.shortcut<QAction>(*actionOpenSession, "shortcuts.session.open");
+
+ bottom_pluginSeparator = addSeparator();
+
+ auto *actionAbout = addAction(tr("About"), app, &Browser::about);
+ conf.shortcut<QAction>(*actionAbout, "shortcuts.window.about");
+
+ auto *action_aboutPlugins = addAction(tr("About Plugins"), app, &Browser::aboutPlugins);
+ conf.shortcut(*action_aboutPlugins, "app.shortcuts.about.plugins");
+
+ auto *actionQuit = addAction(tr("Quit"), app, &Browser::quit);
+ conf.shortcut<QAction>(*actionQuit, "shortcuts.window.quit");
+}
+
+void ApplicationMenu::addPlugin(QPluginLoader *plugin)
+{
+ if(top_pluginSeparator == nullptr) {
+ top_pluginSeparator = insertSeparator(bottom_pluginSeparator);
+ }
+
+ const auto metadata = plugin->metaData().value("MetaData").toObject();
+ auto *action = addAction(metadata.value("name").toString());
+ action->setShortcut(QKeySequence::fromString(metadata.value("shortcut").toString()));
+
+ connect(action, &QAction::triggered, this, [this, plugin]() {
+ if(plugin->isLoaded()) {
+ if(const auto *interface = qobject_cast<PluginInterface *>(plugin->instance())) {
+ interface->createWidget(this->parentWidget())->exec();
+ }
+ }
+ });
+ insertAction(bottom_pluginSeparator, action);
+}
diff --git a/src/applicationmenu.h b/src/applicationmenu.h
new file mode 100644
index 0000000..7e1fe47
--- /dev/null
+++ b/src/applicationmenu.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/cgit/smolbote
+ *
+ * SPDX-License-Identifier: GPL-3.0
+ */
+
+#ifndef SMOLBOTE_APPLICATION_MENU_H
+#define SMOLBOTE_APPLICATION_MENU_H
+
+#include <QMenu>
+
+class Browser;
+class QPluginLoader;
+class ApplicationMenu : public QMenu
+{
+ Q_OBJECT
+
+public:
+ ApplicationMenu(Browser *app, QWidget *parent = nullptr);
+ ~ApplicationMenu() = default;
+
+public slots:
+ void addPlugin(QPluginLoader *plugin);
+
+private:
+ QAction *top_pluginSeparator = nullptr, *bottom_pluginSeparator = nullptr;
+};
+
+#endif // SMOLBOTE_APPLICATION_MENU_H
diff --git a/src/browser.cpp b/src/browser.cpp
index 61be3b0..2277e54 100644
--- a/src/browser.cpp
+++ b/src/browser.cpp
@@ -10,6 +10,7 @@
#include "aboutdialog.h"
#include "aboutplugin.h"
#include "adblock/adblocklist.h"
+#include "applicationmenu.h"
#include "bookmarks/bookmarkswidget.h"
#include "configuration.h"
#include "downloadswidget.h"
@@ -19,19 +20,12 @@
#include "mainwindow/menubar.h"
#include "smolbote/plugininterface.hpp"
#include "subwindow/subwindow.h"
-#include "urlfilter.h"
#include "util.h"
#include "webengine/urlinterceptor.h"
#include "webengine/webprofile.h"
#include "webengine/webprofilemanager.h"
#include "webengine/webview.h"
#include <QAction>
-#include <QDir>
-#include <QFileDialog>
-#include <QFileInfo>
-#include <QFileInfoList>
-#include <QJsonArray>
-#include <QJsonDocument>
#include <QLibraryInfo>
#include <QPluginLoader>
#include <QTimer>
@@ -89,31 +83,13 @@ void Browser::about()
dlg->exec();
}
-void Browser::loadProfiles(const QStringList &profilePaths)
+void Browser::aboutPlugins()
{
- Configuration conf;
-
- for(const QString &path : profilePaths) {
- const QString id = QFileInfo(path).baseName();
-
- auto *profile = m_profileManager->add(id, path);
- connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload);
-
- auto *interceptor = new UrlRequestInterceptor(profile, profile);
- for(UrlFilter *filter : qAsConst(m_filters)) {
- interceptor->addFilter(filter);
- }
-
- const auto headers = conf.value<QStringList>("filter.header").value_or(QStringList());
- for(const QString &header : headers) {
- const auto h = header.split(QLatin1String(":"));
- if(h.length() == 2)
- interceptor->addHttpHeader(h.at(0).toLatin1(), h.at(1).toLatin1());
- }
- profile->setUrlRequestInterceptor(interceptor);
-
- spdlog::info("Added profile\t{}{}\t{}", qUtf8Printable(id), profile->isOffTheRecord() ? "*" : "", qUtf8Printable(profile->name()));
+ auto *dlg = new AboutPluginDialog;
+ for(auto *info : m_plugins) {
+ dlg->add(info->loader);
}
+ dlg->exec();
}
void Browser::loadPlugins(const QStringList &paths, const std::function<void(const QPluginLoader *)> &callback)
@@ -161,38 +137,23 @@ void Browser::setup()
// downloads
m_downloads = std::make_unique<DownloadsWidget>(conf.value<QString>("downloads.path").value());
- // url request filter
- for(const QString &hostlist : Util::files(conf.value<QString>("filter.hosts").value_or(QString()))) {
- QFile f(hostlist);
- if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
- m_filters.append(new HostList(&f));
- f.close();
- }
- }
- for(const QString &adblock : Util::files(conf.value<QString>("filter.adblock").value_or(QString()))) {
- QFile f(adblock);
- if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
- m_filters.append(new AdBlockList(&f));
- f.close();
- }
- }
- // cookie request filter
-
// load profiles
- m_profileManager = new WebProfileManager(this);
- WebProfileManager::setInstance(m_profileManager);
- loadProfiles(Util::files(conf.value<QString>("profile.path").value(), { "*.profile" }));
-
- // set default profile
{
- const QString id = conf.value<QString>("profile.default").value();
- auto *profile = m_profileManager->profile(id);
- if(profile == nullptr) {
- spdlog::error("Unknown default profile, conjuring one up...");
- profile = m_profileManager->add("default", QString(), true);
+ const auto profiles = Util::files(conf.value<QString>("profile.path").value(), { "*.profile" });
+ const auto search = conf.value<QString>("profile.search").value();
+ const auto homepage = QUrl::fromUserInput(conf.value<QString>("profile.homepage").value());
+ const auto newtab = QUrl::fromUserInput(conf.value<QString>("profile.newtab").value());
+ const auto default_id = conf.value<QString>("profile.default").value();
+ m_profileManager = std::make_unique<WebProfileManager<false>>(profiles, default_id, search, homepage, newtab);
+ m_profileManager->make_global();
+
+ for(const auto &id : m_profileManager->idList()) {
+ spdlog::info("Added profile\t{}", qUtf8Printable(id));
}
- spdlog::info("Default profile\t{}{}\t{}", qUtf8Printable(id), profile->isOffTheRecord() ? "*" : "", qUtf8Printable(profile->name()));
- WebProfile::setDefaultProfile(profile);
+
+ // set default profile
+ auto *profile = m_profileManager->profile(default_id);
+ spdlog::info("Default profile\t{}{}\t{}", qUtf8Printable(default_id), profile->isOffTheRecord() ? "*" : "", qUtf8Printable(profile->name()));
}
// bookmarks
@@ -243,15 +204,16 @@ void Browser::open(const QVector<Session::MainWindow> &data, bool merge)
}
for(const auto &windowData : data) {
- // the window will delete itself when it closes, so we don't need to delete it
- auto *window = new MainWindow(windowData);
- connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search);
-
+ auto *menu = new ApplicationMenu(this);
for(auto *info : qAsConst(m_plugins)) {
- addPluginTo(info, window);
+ menu->addPlugin(info->loader);
}
+ auto *window = new MainWindow(windowData, menu);
+ connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search);
+
m_windows.append(window);
+ // the window will delete itself when it closes, so we don't need to delete it
connect(window, &MainWindow::destroyed, this, [this, window]() {
m_windows.removeOne(window);
});
@@ -262,15 +224,9 @@ void Browser::addPluginTo(PluginInfo *info, MainWindow *window)
{
QPluginLoader *loader = info->loader;
auto *pluginMenu = new QMenu(loader->metaData().value("MetaData").toObject().value("name").toString());
- window->m_menuBar->insertPlugin(pluginMenu);
+ //window->m_menuBar->insertPlugin(pluginMenu);
info->menus.append(pluginMenu);
- auto *aboutAction = pluginMenu->addAction(tr("About"));
- connect(aboutAction, &QAction::triggered, this, [loader, window]() {
- auto *dlg = new AboutPluginDialog(loader, window);
- dlg->exec();
- });
-
auto *runAction = pluginMenu->addAction(tr("Run"));
runAction->setShortcut(QKeySequence::fromString(loader->metaData().value("MetaData").toObject().value("shortcut").toString()));
@@ -284,7 +240,6 @@ void Browser::addPluginTo(PluginInfo *info, MainWindow *window)
auto *removeAction = pluginMenu->addAction(tr("Remove"));
connect(removeAction, &QAction::triggered, this, [this, info]() {
- ;
m_plugins.removeOne(info);
delete info;
});
diff --git a/src/browser.h b/src/browser.h
index 95ce936..74136f1 100644
--- a/src/browser.h
+++ b/src/browser.h
@@ -10,6 +10,7 @@
#define SMOLBOTE_BROWSER_H
#include "session.hpp"
+#include "webengine/webprofilemanager.h"
#include <QJsonObject>
#include <QMenu>
#include <QPluginLoader>
@@ -18,13 +19,10 @@
#include <memory>
#include <singleapplication.h>
-class UrlFilter;
class Configuration;
class BookmarksWidget;
class DownloadsWidget;
class MainWindow;
-class WebProfile;
-class WebProfileManager;
class Browser final : public SingleApplication
{
Q_OBJECT
@@ -33,15 +31,8 @@ public:
explicit Browser(int &argc, char *argv[], bool allowSecondary = true);
~Browser() final;
-public slots:
- void about();
-
-public:
- // interface
- void loadProfiles(const QStringList &profilePaths);
void loadPlugins(
- const QStringList &paths,
- const std::function<void(const QPluginLoader *)> &callback = [](const auto) {});
+ const QStringList &paths, const std::function<void(const QPluginLoader *)> &callback = [](const auto) {});
void setup();
const QVector<MainWindow *> windows() const
@@ -59,6 +50,8 @@ public:
}
public slots:
+ void about();
+ void aboutPlugins();
void showWidget(QWidget *widget, MainWindow *where) const;
void open(const QVector<Session::MainWindow> &data, bool merge = true);
@@ -84,8 +77,7 @@ private:
std::shared_ptr<BookmarksWidget> m_bookmarks;
std::unique_ptr<DownloadsWidget> m_downloads;
- WebProfileManager *m_profileManager = nullptr;
- QVector<UrlFilter *> m_filters;
+ std::unique_ptr<WebProfileManager<false>> m_profileManager{ nullptr };
QVector<MainWindow *> m_windows;
QVector<PluginInfo *> m_plugins;
diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp
index e16f34f..4ec79b1 100644
--- a/src/mainwindow/mainwindow.cpp
+++ b/src/mainwindow/mainwindow.cpp
@@ -24,7 +24,7 @@
#include <QMessageBox>
#include <QStatusBar>
-MainWindow::MainWindow(const Session::MainWindow &data, QWidget *parent)
+MainWindow::MainWindow(const Session::MainWindow &data, QMenu *appMenu, QWidget *parent)
: QMainWindow(parent)
{
Configuration config;
@@ -50,8 +50,8 @@ MainWindow::MainWindow(const Session::MainWindow &data, QWidget *parent)
this->addToolBarBreak();
this->addToolBar(new BookmarksToolbar(app->bookmarks()->model(), this));
- m_menuBar = new MenuBar(this);
- this->setMenuBar(m_menuBar);
+ m_menuBar = new MenuBar(appMenu, this);
+ setMenuBar(m_menuBar);
// status bar
searchBox = new SearchForm(this);
@@ -125,10 +125,9 @@ void MainWindow::createTab(const Session::WebView &data)
SubWindow *MainWindow::createSubWindow(const Session::SubWindow &data)
{
- const auto *profileManager = WebProfileManager::instance();
- Q_CHECK_PTR(profileManager);
+ WebProfileManager profileManager;
- auto *profile = profileManager->profile(data.profile);
+ auto *profile = profileManager.profile(data.profile);
if(profile == nullptr) {
profile = WebProfile::defaultProfile();
}
diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h
index 2b9f82b..31f5658 100644
--- a/src/mainwindow/mainwindow.h
+++ b/src/mainwindow/mainwindow.h
@@ -34,9 +34,11 @@ public:
ToolsMenu
};
- explicit MainWindow(const Session::MainWindow &data, QWidget *parent = nullptr);
+ explicit MainWindow(const Session::MainWindow &data, QMenu *appMenu, QWidget *parent = nullptr);
+
MainWindow(const MainWindow &) = delete;
~MainWindow() = default;
+
[[nodiscard]] Session::MainWindow serialize() const
{
QVector<Session::SubWindow> subwindows(m_subwindows.size());
diff --git a/src/mainwindow/menubar.cpp b/src/mainwindow/menubar.cpp
index 83cafee..f8979de 100644
--- a/src/mainwindow/menubar.cpp
+++ b/src/mainwindow/menubar.cpp
@@ -12,23 +12,17 @@
#include "configuration.h"
#include "downloadswidget.h"
#include "mainwindow.h"
-#include "session/savesessiondialog.h"
-#include "session/sessiondialog.h"
#include "subwindow/subwindow.h"
#include "webengine/webprofilemanager.h"
#include "webengine/webview.h"
#include "widgets/menusearch.h"
-#include <QApplication>
#include <QDir>
#include <QFileDialog>
-#include <QLineEdit>
-#include <QMdiArea>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrinterInfo>
#include <QToolBar>
#include <QVBoxLayout>
-#include <QWidgetAction>
#include <functional>
inline void run_if(SubWindow *_subwindow, const std::function<void(SubWindow *, int)> &f)
@@ -70,7 +64,7 @@ inline QDialog *createDevToolsDialog(QWebEnginePage *page)
return popup;
}
-MenuBar::MenuBar(MainWindow *parent)
+MenuBar::MenuBar(QMenu *appMenu, MainWindow *parent)
: QMenuBar(parent)
{
m_parent = parent;
@@ -78,86 +72,21 @@ MenuBar::MenuBar(MainWindow *parent)
Q_CHECK_PTR(browser);
Configuration conf;
- smolbote = this->addMenu(qApp->applicationName());
- {
- auto *findMenu = smolbote->addMenu(tr("Find in menus"));
-
- auto *findWidget = new QWidgetAction(this);
- auto *find_lineEdit = new MenuSearch(this);
- findWidget->setDefaultWidget(find_lineEdit);
- findMenu->addAction(findWidget);
-
- connect(findMenu, &QMenu::aboutToShow, [findMenu, find_lineEdit]() {
- find_lineEdit->clear();
- const auto actions = findMenu->actions();
- for(int i = 1; i < actions.length(); i++) {
- findMenu->removeAction(actions.at(i));
- }
- find_lineEdit->setFocus();
- });
-
- connect(find_lineEdit, &QLineEdit::textEdited, [this, findMenu](const QString &text) {
- // clear menu
- const auto actions = findMenu->actions();
- for(int i = 1; i < actions.length(); i++)
- findMenu->removeAction(actions.at(i));
-
- if(text.isEmpty())
- return;
-
- // findChildren
- for(QAction *a : findChildren<QAction *>()) {
- if(a->text().contains(text))
- findMenu->addAction(a);
- }
- });
-
- smolbote->addSeparator();
-
- const QString sessionPath = conf.value<QString>("session.path").value();
- auto *actionSaveSession = smolbote->addAction(tr("Save Session"), parent, [sessionPath]() {
- auto *sessionDialog = new SaveSessionDialog(nullptr);
- if(sessionDialog->exec() == QDialog::Accepted)
- sessionDialog->save(sessionPath);
- });
- conf.shortcut<QAction>(*actionSaveSession, "shortcuts.session.save");
-
- auto *actionOpenSession = smolbote->addAction(tr("Open Session"), parent, [parent]() {
- auto *sessionDialog = new SessionDialog(parent);
- sessionDialog->exec();
- });
- conf.shortcut<QAction>(*actionOpenSession, "shortcuts.session.open");
-
- smolbote->addSeparator();
- auto *actionBookmarks = smolbote->addAction(tr("Bookmarks"), browser, [browser, parent]() {
- browser->showWidget(browser->bookmarks(), parent);
- });
- conf.shortcut<QAction>(*actionBookmarks, "shortcuts.window.bookmarks.show");
-
- auto *actionDownloads = smolbote->addAction(tr("Downloads"), browser, [browser, parent]() {
- browser->showWidget(browser->downloads(), parent);
- });
- conf.shortcut<QAction>(*actionDownloads, "shortcuts.window.downloads.show");
-
- smolbote->addSeparator();
- smolbote->addAction(tr("Load Plugin"), browser, [browser]() {
- const QString path = QFileDialog::getOpenFileName(nullptr, tr("Select Plugin"), QDir::homePath(), tr("Plugins (*.so)"));
- browser->loadPlugins(QStringList(path));
- });
-
- pluginInsertLocation = smolbote->addSeparator();
-
- auto *actionAbout = smolbote->addAction(tr("About"), browser, &Browser::about);
- conf.shortcut<QAction>(*actionAbout, "shortcuts.window.about");
-
- smolbote->addSeparator();
-
- auto *actionQuit = smolbote->addAction(tr("Quit"), qApp, &QApplication::quit);
- conf.shortcut<QAction>(*actionQuit, "shortcuts.window.quit");
- }
+ addMenu(appMenu);
window = this->addMenu(tr("&Window"));
{
+ auto *actionBookmarks = window->addAction(tr("Bookmarks"), browser, [browser, parent]() {
+ browser->showWidget(browser->bookmarks(), parent);
+ });
+ conf.shortcut<QAction>(*actionBookmarks, "shortcuts.window.bookmarks.show");
+
+ auto *actionDownloads = window->addAction(tr("Downloads"), browser, [browser, parent]() {
+ browser->showWidget(browser->downloads(), parent);
+ });
+ conf.shortcut<QAction>(*actionDownloads, "shortcuts.window.downloads.show");
+ window->addSeparator();
+
auto *actionNewWindow = window->addAction(tr("New Window"), browser, [browser]() {
const Session::MainWindow window;
browser->open({ window }, false);
@@ -532,11 +461,6 @@ MenuBar::MenuBar(MainWindow *parent)
}
}
-QAction *MenuBar::insertPlugin(QMenu *menu)
-{
- return smolbote->insertMenu(pluginInsertLocation, menu);
-}
-
void MenuBar::insertSubWindow(SubWindow *subwindow)
{
auto *action = window->addAction(subwindow->windowTitle());
diff --git a/src/mainwindow/menubar.h b/src/mainwindow/menubar.h
index f369bef..7795816 100644
--- a/src/mainwindow/menubar.h
+++ b/src/mainwindow/menubar.h
@@ -18,14 +18,11 @@ class MenuBar : public QMenuBar
Q_OBJECT
public:
- MenuBar(MainWindow *parent = nullptr);
-
- QAction *insertPlugin(QMenu *menu);
+ MenuBar(QMenu *appMenu, MainWindow *parent = nullptr);
void insertSubWindow(SubWindow *subwindow);
private:
MainWindow *m_parent = nullptr;
- QMenu *smolbote = nullptr;
QMenu *window = nullptr;
QAction *pluginInsertLocation = nullptr;
};
diff --git a/src/meson.build b/src/meson.build
index 1101b7a..6a4abd8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -8,14 +8,14 @@ poi_settings_h = custom_target('default_config_value',
)
subdir('about')
+subdir('webengine')
poi_sourceset.add(mod_qt5.preprocess(
- moc_headers: ['browser.h',
+ moc_headers: ['browser.h', 'applicationmenu.h',
'mainwindow/mainwindow.h', 'mainwindow/addressbar.h', 'mainwindow/menubar.h', 'mainwindow/widgets/completer.h', 'mainwindow/widgets/urllineedit.h', 'mainwindow/widgets/dockwidget.h', 'mainwindow/widgets/navigationbar.h', 'mainwindow/widgets/searchform.h',
'bookmarks/bookmarkswidget.h', 'bookmarks/editbookmarkdialog.h',
'session/savesessiondialog.h', 'session/sessiondialog.h',
- 'subwindow/subwindow.h', 'subwindow/tabwidget.h',
- 'webengine/urlinterceptor.h', 'webengine/webpage.h', 'webengine/webview.h', 'webengine/webprofilemanager.h', 'webengine/webprofile.h'],
+ 'subwindow/subwindow.h', 'subwindow/tabwidget.h' ],
ui_files: [
'mainwindow/addressbar.ui', 'mainwindow/widgets/searchform.ui',
'bookmarks/bookmarksform.ui', 'bookmarks/editbookmarkdialog.ui',
@@ -27,7 +27,7 @@ poi_sourceset.add(mod_qt5.preprocess(
poi_sourceset.add(files(
'main.cpp', 'builtins.cpp',
- 'browser.cpp',
+ 'browser.cpp', 'applicationmenu.cpp',
'util.cpp', 'util.h',
'mainwindow/mainwindow.cpp',
@@ -48,13 +48,6 @@ poi_sourceset.add(files(
'subwindow/subwindow.cpp',
'subwindow/tabwidget.cpp',
- 'webengine/urlinterceptor.cpp',
- 'webengine/webpage.cpp',
- 'webengine/webview.cpp',
- 'webengine/webviewcontextmenu.cpp',
- 'webengine/webprofile.cpp',
- 'webengine/webprofilemanager.cpp',
-
'wallet/wallet.cpp', 'wallet/wallet.h'
),
version_h, poi_settings_h
diff --git a/src/session/savesessiondialog.cpp b/src/session/savesessiondialog.cpp
index ff5e696..e22ce3a 100644
--- a/src/session/savesessiondialog.cpp
+++ b/src/session/savesessiondialog.cpp
@@ -12,7 +12,7 @@
#include "session_json.hpp"
#include "subwindow/subwindow.h"
#include "ui_savesessiondialog.h"
-#include "webengine/webprofilemanager.h"
+#include "webengine/webprofile.h"
#include "webengine/webview.h"
#include <QFileDialog>
#include <QPointer>
@@ -37,7 +37,7 @@ SaveSessionDialog::SaveSessionDialog(QWidget *parent)
for(const SubWindow *subwindow : window->subWindows()) {
auto *subwindowItem = new QTreeWidgetItem(windowItem);
subwindowItem->setText(0, tr("Subwindow"));
- subwindowItem->setText(1, WebProfileManager::instance()->id(subwindow->profile()));
+ subwindowItem->setText(1, subwindow->profile()->getId());
ui->treeWidget->expandItem(subwindowItem);
@@ -45,7 +45,7 @@ SaveSessionDialog::SaveSessionDialog(QWidget *parent)
auto *tabItem = new QTreeWidgetItem(subwindowItem);
auto *view = subwindow->view(i);
tabItem->setText(0, view->title());
- tabItem->setText(1, WebProfileManager::instance()->id(view->profile()));
+ tabItem->setText(1, view->profile()->getId());
}
}
}
diff --git a/src/subwindow/subwindow.cpp b/src/subwindow/subwindow.cpp
index 8c2e3e7..588a070 100644
--- a/src/subwindow/subwindow.cpp
+++ b/src/subwindow/subwindow.cpp
@@ -81,10 +81,9 @@ SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags flags)
SubWindow::SubWindow(const Session::SubWindow &data, QWidget *parent, Qt::WindowFlags flags)
: SubWindow(parent, flags)
{
- const auto *profileManager = WebProfileManager::instance();
- Q_CHECK_PTR(profileManager);
+ WebProfileManager profileManager;
- auto *profile = profileManager->profile(data.profile);
+ auto *profile = profileManager.profile(data.profile);
if(profile != nullptr) {
setProfile(profile);
}
@@ -96,15 +95,12 @@ SubWindow::SubWindow(const Session::SubWindow &data, QWidget *parent, Qt::Window
Session::SubWindow SubWindow::serialize() const
{
- const auto *profileManager = WebProfileManager::instance();
- Q_CHECK_PTR(profileManager);
-
QVector<Session::WebView> tabs(tabCount());
for(int i = 0; i < tabCount(); ++i) {
tabs[i] = view(i)->serialize();
}
- return { profileManager->id(profile()), tabs };
+ return { profile()->getId(), tabs };
}
void SubWindow::setProfile(WebProfile *profile)
@@ -131,13 +127,20 @@ SubWindow::TabData SubWindow::tabData(int index) const
return tabWidget->tabBar()->tabData(index).value<TabData>();
}
+WebView *SubWindow::createView(QWebEnginePage::WebWindowType type)
+{
+ auto *view = new WebView(m_profile, std::bind(&SubWindow::createView, this, std::placeholders::_1), this);
+ tabWidget->addTab(view);
+ return view;
+}
+
int SubWindow::addTab(const QUrl &url, WebProfile *profile)
{
Q_CHECK_PTR(m_profile);
auto *_profile = (profile == nullptr) ? m_profile : profile;
- auto *view = new WebView(_profile, this);
+ auto *view = new WebView(_profile, std::bind(&SubWindow::createView, this, std::placeholders::_1), this);
if(url.isEmpty())
view->load(_profile->newtab());
else
@@ -148,7 +151,7 @@ int SubWindow::addTab(const QUrl &url, WebProfile *profile)
int SubWindow::addTab(const Session::WebView &data)
{
- auto *view = new WebView(data, this);
+ auto *view = new WebView(data, std::bind(&SubWindow::createView, this, std::placeholders::_1), this);
return tabWidget->addTab(view);
}
diff --git a/src/subwindow/subwindow.h b/src/subwindow/subwindow.h
index 02f50d0..88f3985 100644
--- a/src/subwindow/subwindow.h
+++ b/src/subwindow/subwindow.h
@@ -68,6 +68,8 @@ signals:
void aboutToClose();
public slots:
+ WebView *createView(QWebEnginePage::WebWindowType type);
+
int addTab(const QUrl &url = QUrl(), WebProfile *profile = nullptr);
int addTab(const Session::WebView &data);
void closeTab(int index)
diff --git a/src/subwindow/tabwidget.cpp b/src/subwindow/tabwidget.cpp
index d09fffb..efa2b6a 100644
--- a/src/subwindow/tabwidget.cpp
+++ b/src/subwindow/tabwidget.cpp
@@ -17,17 +17,18 @@
#include <QWebEngineHistory>
#include "subwindow.h"
-inline WebView *createViewFromInfo(TabWidget::TabInformation &tab, QWidget *parent)
+inline WebView *createViewFromInfo(TabWidget::TabInformation &tab, SubWindow *parent)
{
- auto *view = new WebView(tab.profile, 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;
}
-TabWidget::TabWidget(QWidget *parent)
+TabWidget::TabWidget(SubWindow *parent)
: QTabWidget(parent)
+ , m_parent(parent)
{
setStyleSheet("QTabBar::tab { width: 200px; }");
@@ -132,7 +133,7 @@ int TabWidget::restoreLastTab()
{
if(!m_closedTabs.isEmpty()) {
TabInformation tab = m_closedTabs.takeLast();
- return addTab(createViewFromInfo(tab, this));
+ return addTab(createViewFromInfo(tab, m_parent));
}
return -1;
}
@@ -147,7 +148,7 @@ void TabWidget::restoreTabMenu(QMenu *menu)
connect(openAction, &QAction::triggered, this, [this, i]() {
TabInformation tab = m_closedTabs.takeAt(i);
- addTab(createViewFromInfo(tab, this));
+ addTab(createViewFromInfo(tab, m_parent));
});
}
diff --git a/src/subwindow/tabwidget.h b/src/subwindow/tabwidget.h
index d67d2de..de5e6fb 100644
--- a/src/subwindow/tabwidget.h
+++ b/src/subwindow/tabwidget.h
@@ -18,6 +18,7 @@ class QMenu;
class WebView;
class WebProfile;
class QWebEnginePage;
+class SubWindow;
class TabWidget : public QTabWidget
{
Q_OBJECT
@@ -31,7 +32,7 @@ public:
QByteArray historyBuffer;
};
- explicit TabWidget(QWidget *parent = nullptr);
+ explicit TabWidget(SubWindow *parent = nullptr);
~TabWidget() override;
public slots:
@@ -46,6 +47,7 @@ protected:
void mousePressEvent(QMouseEvent *event) override;
private:
+ SubWindow *m_parent;
int current = -1;
int previous = -1;
QMenu *tabContextMenu;
diff --git a/src/webengine/meson.build b/src/webengine/meson.build
new file mode 100644
index 0000000..db7bdc5
--- /dev/null
+++ b/src/webengine/meson.build
@@ -0,0 +1,27 @@
+webengine_moc = mod_qt5.preprocess(
+ moc_headers: [ 'webprofile.h', 'webpage.h', 'webview.h' ],
+ dependencies: dep_qt5
+)
+
+dep_webengine = declare_dependency(
+ include_directories: [ '.', smolbote_include ],
+ link_with: static_library('webengine', dependencies: dep_qt5,
+ include_directories: smolbote_include,
+ sources: [ 'webprofile.cpp', 'urlinterceptor.cpp', 'webprofilemanager.cpp', 'webpage.cpp', 'webview.cpp', 'webviewcontextmenu.cpp', webengine_moc ])
+)
+
+poi_sourceset.add(dep_webengine)
+
+test('profile', executable('profile', 'test/profile.cpp', dependencies: [ dep_qt5, dep_webengine, dep_catch ]),
+ env: { 'PROFILE' : meson.current_source_dir()/'test/testing.profile' },
+ suite: 'webengine')
+
+test('profilemanager', executable('profilemanager', 'test/profilemanager.cpp', dependencies: [ dep_qt5, dep_webengine, dep_catch ]),
+ env: { 'PROFILES' : meson.current_source_dir()/'test/testing.profile' },
+ suite: 'webengine')
+
+test('view', executable('view', 'test/view.cpp', dependencies: [ dep_qt5, dep_webengine, dep_catch ]),
+ args: [ '-platform', 'offscreen' ],
+ env: { 'PROFILE' : meson.current_source_dir()/'test/testing.profile',
+ 'URL' : meson.current_source_dir()/'test/sample.html' },
+ suite: 'webengine')
diff --git a/src/webengine/test/form.html b/src/webengine/test/form.html
new file mode 100644
index 0000000..9d8b19e
--- /dev/null
+++ b/src/webengine/test/form.html
@@ -0,0 +1,10 @@
+<html>
+
+<head>
+ <title>Form completion test</title>
+</head>
+
+<body>
+<h2>Form completion test</h2>
+</body>
+</html>
diff --git a/src/webengine/test/icon.svg b/src/webengine/test/icon.svg
new file mode 100644
index 0000000..a348cab
--- /dev/null
+++ b/src/webengine/test/icon.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" version="1.1">
+ <circle cx="150" cy="150" r="100" stroke="#000000" stroke-width="6" fill="#e60026"></circle>
+ <circle cx="150" cy="150" r="87" stroke="#000000" stroke-width="4" fill="#e5e4e2"></circle>
+ <path d="M230,150 A80,80 0 0 0 150,70 L150,150 Z" />
+ <path d="M70,150 A80,80 0 0 0 150,230 L150,150 Z" />
+</svg>
+
diff --git a/src/webengine/test/profile.cpp b/src/webengine/test/profile.cpp
new file mode 100644
index 0000000..7351f66
--- /dev/null
+++ b/src/webengine/test/profile.cpp
@@ -0,0 +1,124 @@
+#define CATCH_CONFIG_RUNNER
+
+// clazy:excludeall=non-pod-global-static
+
+#include "webprofilemanager.h"
+#include <QApplication>
+#include <catch2/catch.hpp>
+
+TEST_CASE("loading profile settings")
+{
+ const QString search = GENERATE(as<QString>{}, "https://search.url/t=%1", "https://duckduckgo.com/?q=%1&ia=web", "aaabbbccc");
+ const QUrl homepage = GENERATE(as<QUrl>{}, "https://homepage.net", "about:blank", "aaabbbccc");
+ const QUrl newtab = GENERATE(as<QUrl>{}, "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("loading individual profiles")
+{
+ GIVEN("no profile preset")
+ {
+ const QString search = GENERATE(as<QString>{}, "https://search.url/t=%1", "https://duckduckgo.com/?q=%1&ia=web", "aaabbbccc");
+ const QUrl homepage = GENERATE(as<QUrl>{}, "https://homepage.net", "about:blank", "aaabbbccc");
+ const QUrl newtab = GENERATE(as<QUrl>{}, "https://newtab.net", "about:blank", "aaabbbccc");
+
+ const QString id{ "id" };
+ auto *settings = WebProfile::load(QString(), search, homepage, newtab);
+ auto *profile = WebProfile::load(id, settings, true);
+
+ REQUIRE(profile != nullptr);
+ REQUIRE(profile->getId() == id);
+ REQUIRE(profile->property("id").toString() == id);
+ REQUIRE(profile->name() == id);
+ REQUIRE(profile->search() == search);
+ REQUIRE(profile->homepage() == homepage);
+ REQUIRE(profile->newtab() == newtab);
+
+ REQUIRE(profile->isOffTheRecord());
+ delete settings;
+ delete profile;
+ }
+
+ GIVEN("an off-the-record profile preset")
+ {
+ REQUIRE(qEnvironmentVariableIsSet("PROFILE"));
+
+ const QString id{ "id" };
+ auto *settings = WebProfile::load(qgetenv("PROFILE"), QString(), QUrl(), QUrl());
+ auto *profile = WebProfile::load(id, settings, true);
+
+ REQUIRE(profile != nullptr);
+ REQUIRE(profile->getId() == id);
+ REQUIRE(profile->isOffTheRecord());
+
+ WHEN("created")
+ {
+ THEN("uses default values")
+ {
+ REQUIRE(profile->name() == "Test Profile");
+ REQUIRE(profile->search() == "https://duckduckgo.com/?q=%1&ia=web");
+ REQUIRE(profile->homepage() == QUrl("about:blank"));
+ REQUIRE(profile->newtab() == QUrl("about:blank"));
+ }
+ }
+
+ WHEN("changing profile name")
+ {
+ const QString name = GENERATE(as<QString>{}, "a", "bb", "ccc");
+ profile->setName(name);
+ THEN("the name changes")
+ {
+ REQUIRE(profile->name() == name);
+ REQUIRE(settings->value("name").toString() == name);
+ }
+ }
+ WHEN("changing search url")
+ {
+ const QString search = GENERATE(as<QString>{}, "a", "bb", "ccc");
+ profile->setSearch(search);
+ THEN("the search url changes")
+ {
+ REQUIRE(profile->search() == search);
+ REQUIRE(settings->value("search").toString() == search);
+ }
+ }
+ WHEN("changing homepage")
+ {
+ const QUrl url = GENERATE(as<QUrl>{}, "a", "bb", "ccc");
+ profile->setHomepage(url);
+ THEN("homepage changes")
+ {
+ REQUIRE(profile->homepage() == url);
+ REQUIRE(settings->value("homepage").toUrl() == url);
+ }
+ }
+ WHEN("changing newtab")
+ {
+ const QUrl url = GENERATE(as<QUrl>{}, "a", "bb", "ccc");
+ profile->setNewtab(url);
+ THEN("newtab changes")
+ {
+ REQUIRE(profile->newtab() == url);
+ REQUIRE(settings->value("newtab").toUrl() == url);
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..dc7c903
--- /dev/null
+++ b/src/webengine/test/profilemanager.cpp
@@ -0,0 +1,120 @@
+#define CATCH_CONFIG_RUNNER
+
+// clazy:excludeall=non-pod-global-static
+
+#include "webprofilemanager.h"
+#include <QApplication>
+#include <catch2/catch.hpp>
+
+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<false> 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<false> 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("PROFILES"));
+
+ WebProfileManager<false> profiles(QString::fromLatin1(qgetenv("PROFILES")).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("PROFILES"));
+
+ WebProfileManager<false> profiles(QString::fromLatin1(qgetenv("PROFILES")).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
new file mode 100644
index 0000000..54746d5
--- /dev/null
+++ b/src/webengine/test/sample.html
@@ -0,0 +1,7 @@
+<html>
+<head><title>sample page</title></head>
+<body>
+ <h2><img src="icon.svg" />This is a sample page</h2>
+ <iframe width="100%" height="80%" src="form.html"></iframe>
+</body>
+</html>
diff --git a/src/webengine/test/testing.profile b/src/webengine/test/testing.profile
new file mode 100644
index 0000000..e345a3e
--- /dev/null
+++ b/src/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/src/webengine/test/view.cpp b/src/webengine/test/view.cpp
new file mode 100644
index 0000000..8aa639a
--- /dev/null
+++ b/src/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 <QApplication>
+#include <QMainWindow>
+#include <QTimer>
+#include <QtWebEngine>
+#include <catch2/catch.hpp>
+
+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
index 29cd869..047cad4 100644
--- a/src/webengine/urlinterceptor.cpp
+++ b/src/webengine/urlinterceptor.cpp
@@ -8,59 +8,24 @@
#include "urlinterceptor.h"
#include "webprofile.h"
-#include "urlfilter.h"
// test DNT on https://browserleaks.com/donottrack
-UrlRequestInterceptor::UrlRequestInterceptor(WebProfile* profile, QObject* parent)
- : QWebEngineUrlRequestInterceptor(parent)
+UrlRequestInterceptor::UrlRequestInterceptor(WebProfile* profile)
+ : QWebEngineUrlRequestInterceptor(profile)
{
Q_CHECK_PTR(profile);
m_profile = profile;
}
-void UrlRequestInterceptor::addHttpHeader(const QByteArray &key, const QByteArray &value)
-{
- headers.append(qMakePair(key, value));
-}
-
-void UrlRequestInterceptor::addFilter(UrlFilter *filter)
-{
- if(filter != nullptr)
- filters.append(filter);
-}
-void UrlRequestInterceptor::removeFilter(UrlFilter *filter)
-{
- if(filter != nullptr)
- filters.removeOne(filter);
-}
-
void UrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info)
{
- for(const auto *filter : qAsConst(filters)) {
- const auto match = filter->match(info.firstPartyUrl(), info.requestUrl(), info.resourceType());
-
- // skip if no match
- if(match.first == UrlFilter::NotMatched)
- continue;
-
- else {
- if(match.first == UrlFilter::Allow)
- info.block(false);
- else if(match.first == UrlFilter::Block)
- info.block(true);
- else if(match.first == UrlFilter::Redirect)
- info.redirect(QUrl::fromUserInput(match.second));
- // we found a match, skip the rest
- break;
- }
+ for(auto *filter : qAsConst(m_profile->m_filters)) {
+ filter->interceptRequest(info);
}
// set headers
- for(const auto &header : qAsConst(headers)) {
- info.setHttpHeader(header.first, header.second);
- }
- for(auto i = m_profile->headers().constBegin(); i != m_profile->headers().constEnd(); ++i) {
+ 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
index 4909586..eb3ce67 100644
--- a/src/webengine/urlinterceptor.h
+++ b/src/webengine/urlinterceptor.h
@@ -9,30 +9,23 @@
#ifndef SMOLBOTE_URLREQUESTINTERCEPTOR_H
#define SMOLBOTE_URLREQUESTINTERCEPTOR_H
-#include <QVector>
#include <QWebEngineUrlRequestInterceptor>
-#include <QByteArray>
-class UrlFilter;
class WebProfile;
class UrlRequestInterceptor : public QWebEngineUrlRequestInterceptor
{
- Q_OBJECT
+ friend class WebProfile;
+
public:
- explicit UrlRequestInterceptor(WebProfile *profile, QObject *parent = nullptr);
~UrlRequestInterceptor() override = default;
- void addHttpHeader(const QByteArray &key, const QByteArray &value);
-
- void addFilter(UrlFilter *filter);
- void removeFilter(UrlFilter *filter);
-
void interceptRequest(QWebEngineUrlRequestInfo &info) override;
+protected:
+ explicit UrlRequestInterceptor(WebProfile *profile);
+
private:
WebProfile *m_profile;
- QVector<QPair<QByteArray, QByteArray>> headers;
- QVector<UrlFilter*> filters;
};
#endif // SMOLBOTE_URLREQUESTINTERCEPTOR_H
diff --git a/src/webengine/webpage.cpp b/src/webengine/webpage.cpp
index e79db9d..8c6b8db 100644
--- a/src/webengine/webpage.cpp
+++ b/src/webengine/webpage.cpp
@@ -12,7 +12,6 @@
#include <QTimer>
#include <QWebEngineFullScreenRequest>
#include <QWebEngineCertificateError>
-#include <spdlog/spdlog.h>
QString tr_terminationStatus(QWebEnginePage::RenderProcessTerminationStatus status)
{
@@ -106,17 +105,16 @@ void WebPage::featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
messageBox.setDefaultButton(QMessageBox::No);
- if(messageBox.exec() == QMessageBox::Yes)
+ if(messageBox.exec() == QMessageBox::Yes) {
setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionGrantedByUser);
- else
+ } else {
setFeaturePermission(securityOrigin, feature, QWebEnginePage::PermissionDeniedByUser);
+ }
}
void WebPage::renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode)
{
if(terminationStatus != QWebEnginePage::NormalTerminationStatus) {
- spdlog::warn("render process terminated: [{}] {}", terminationStatus, exitCode);
-
QString page = "<html><body><h1>This tab has crashed!</h1>%message%</body></html>";
page.replace(QLatin1String("%message%"), QString("<p>%1<br>Exit code is %2.</p>"
"<p>Press <a href='%3'>here</a> to reload this tab.</p>")
diff --git a/src/webengine/webpage.h b/src/webengine/webpage.h
index 48011cb..91ae4f3 100644
--- a/src/webengine/webpage.h
+++ b/src/webengine/webpage.h
@@ -14,13 +14,15 @@
class WebPage : public QWebEnginePage
{
Q_OBJECT
+
public:
- explicit WebPage(QWebEngineProfile *profile, QObject *parent = nullptr);
+ WebPage(QWebEngineProfile *profile, QObject *parent = nullptr);
+ ~WebPage() override = default;
protected:
bool certificateError(const QWebEngineCertificateError &certificateError) override;
-private slots:
+protected slots:
void featurePermissionDialog(const QUrl &securityOrigin, QWebEnginePage::Feature feature);
void renderProcessCrashed(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode);
};
diff --git a/src/webengine/webprofile.cpp b/src/webengine/webprofile.cpp
index 5224189..f0368c9 100644
--- a/src/webengine/webprofile.cpp
+++ b/src/webengine/webprofile.cpp
@@ -11,6 +11,7 @@
#include <QSettings>
#include <QWebEngineCookieStore>
#include <QWebEngineSettings>
+#include "urlinterceptor.h"
static WebProfile *s_profile = nullptr;
@@ -25,11 +26,89 @@ WebProfile *WebProfile::defaultProfile()
return s_profile;
}
-WebProfile::WebProfile(const QString &name, QObject *parent)
- : QWebEngineProfile(parent)
+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)
{
- m_name = name;
+ WebProfile *profile = nullptr;
+ if(isOffTheRecord || 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<QWebEngineSettings::WebAttribute>(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");
+ for(const QString &key : settings->childKeys()) {
+ 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) {
m_cookies.append(cookie);
});
@@ -38,11 +117,12 @@ WebProfile::WebProfile(const QString &name, QObject *parent)
});
}
-WebProfile::WebProfile(const QString &storageName, const QString &name, QObject *parent)
+// default constructor
+WebProfile::WebProfile(const QString &id, const QString &storageName, QObject *parent)
: QWebEngineProfile(storageName, parent)
+ , m_id(id)
{
- m_name = name;
-
+ QWebEngineProfile::setUrlRequestInterceptor(new UrlRequestInterceptor(this));
connect(this->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, [this](const QNetworkCookie &cookie) {
m_cookies.append(cookie);
});
@@ -50,109 +130,3 @@ WebProfile::WebProfile(const QString &storageName, const QString &name, QObject
m_cookies.removeAll(cookie);
});
}
-
-const QString WebProfile::name() const
-{
- return m_name;
-}
-
-void WebProfile::setName(const QString &name)
-{
- m_name = name;
- emit nameChanged(name);
-}
-
-QString WebProfile::search() const
-{
- return m_search;
-}
-
-void WebProfile::setSearch(const QString &url)
-{
- m_search = url;
- emit searchChanged(m_search);
-}
-
-QUrl WebProfile::homepage() const
-{
- return m_homepage;
-}
-
-void WebProfile::setHomepage(const QUrl &url)
-{
- m_homepage = url;
- emit homepageChanged(m_homepage);
-}
-
-QUrl WebProfile::newtab() const
-{
- return m_newtab;
-}
-
-void WebProfile::setNewtab(const QUrl &url)
-{
- m_newtab = url;
- emit newtabChanged(m_newtab);
-}
-
-void WebProfile::setCachePath(const QString &path)
-{
- QWebEngineProfile::setCachePath(path);
- emit propertyChanged("cachePath", path);
-}
-
-void WebProfile::setPersistentStoragePath(const QString &path)
-{
- QWebEngineProfile::setPersistentStoragePath(path);
- emit propertyChanged("persistentStoragePath", path);
-}
-
-void WebProfile::setPersistentCookiesPolicy(int policy)
-{
- QWebEngineProfile::setPersistentCookiesPolicy(static_cast<QWebEngineProfile::PersistentCookiesPolicy>(policy));
- emit propertyChanged("persistentCookiesPolicy", policy);
-}
-
-void WebProfile::setHttpAcceptLanguage(const QString &httpAcceptLanguage)
-{
- QWebEngineProfile::setHttpAcceptLanguage(httpAcceptLanguage);
- emit propertyChanged("httpAcceptLanguage", httpAcceptLanguage);
-}
-
-void WebProfile::setHttpCacheMaximumSize(int maxSize)
-{
- QWebEngineProfile::setHttpCacheMaximumSize(maxSize);
- emit propertyChanged("httpCacheMaximumSize", maxSize);
-}
-
-void WebProfile::setHttpCacheType(int type)
-{
- QWebEngineProfile::setHttpCacheType(static_cast<QWebEngineProfile::HttpCacheType>(type));
- emit propertyChanged("httpCacheType", type);
-}
-
-void WebProfile::setHttpUserAgent(const QString &userAgent)
-{
- QWebEngineProfile::setHttpUserAgent(userAgent);
- emit propertyChanged("httpUserAgent", userAgent);
-}
-
-void WebProfile::setHttpHeader(const QString &name, const QString &value)
-{
- m_headers[name.toLatin1()] = value.toLatin1();
- emit headerChanged(name, value);
-}
-
-void WebProfile::removeHttpHeader(const QString &name)
-{
- if(m_headers.contains(name.toLatin1())) {
- m_headers.remove(name.toLatin1());
- emit headerRemoved(name);
- }
-}
-
-void WebProfile::setSpellCheckEnabled(bool enable)
-{
- QWebEngineProfile::setSpellCheckEnabled(enable);
- emit propertyChanged("spellCheckEnabed", enable);
-}
diff --git a/src/webengine/webprofile.h b/src/webengine/webprofile.h
index 66154af..180cc9d 100644
--- a/src/webengine/webprofile.h
+++ b/src/webengine/webprofile.h
@@ -9,7 +9,6 @@
#ifndef SMOLBOTE_WEBENGINEPROFILE_H
#define SMOLBOTE_WEBENGINEPROFILE_H
-#include <QHash>
#include <QMap>
#include <QNetworkCookie>
#include <QString>
@@ -17,14 +16,17 @@
#include <QVector>
#include <QWebEngineProfile>
#include <QWebEngineSettings>
+#include <QVariant>
+#include <QSettings>
-class WebProfileManager;
+class UrlRequestInterceptor;
class WebProfile : public QWebEngineProfile
{
- friend class WebProfileManager;
+ 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)
@@ -42,72 +44,153 @@ class WebProfile : public QWebEngineProfile
Q_PROPERTY(bool spellCheckEnabled READ isSpellCheckEnabled WRITE setSpellCheckEnabled NOTIFY propertyChanged)
+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 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() = default;
+ ~WebProfile() override = default;
- const QString name() const;
- void setName(const QString &name);
-
- const QVector<QNetworkCookie> cookies() const
+ [[nodiscard]] QString getId() const
{
- return qAsConst(m_cookies);
+ return m_id;
+ }
+ [[nodiscard]] QString name() const
+ {
+ return m_name;
}
- const QMap<QByteArray, QByteArray> headers() const
+ void setName(const QString &name)
{
- return qAsConst(m_headers);
+ m_name = name;
+ emit nameChanged(name);
}
- // search url
- QString search() const;
- void setSearch(const QString &url);
+ [[nodiscard]] QVector<QNetworkCookie> cookies() const
+ {
+ return qAsConst(m_cookies);
+ }
- // homepage url
- QUrl homepage() const;
- void setHomepage(const QUrl &url);
+ [[nodiscard]] QString search() const
+ {
+ return m_search;
+ }
+ void setSearch(const QString &url)
+ {
+ m_search = url;
+ emit searchChanged(m_search);
+ }
- // new tab url
- QUrl newtab() const;
- void setNewtab(const QUrl &url);
+ [[nodiscard]] QUrl homepage() const
+ {
+ return m_homepage;
+ }
+ void setHomepage(const QUrl &url)
+ {
+ m_homepage = url;
+ emit homepageChanged(m_homepage);
+ }
- void setCachePath(const QString &path);
- void setPersistentStoragePath(const QString &path);
- void setPersistentCookiesPolicy(int policy);
+ [[nodiscard]] QUrl newtab() const
+ {
+ return m_newtab;
+ }
+ void setNewtab(const QUrl &url)
+ {
+ m_newtab = url;
+ emit newtabChanged(m_newtab);
+ }
- void setHttpAcceptLanguage(const QString &httpAcceptLanguage);
- void setHttpCacheMaximumSize(int maxSize);
- void setHttpCacheType(int type);
- void setHttpUserAgent(const QString &userAgent);
- void setHttpHeader(const QString &name, const QString &value);
- void removeHttpHeader(const QString &name);
+ 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<QWebEngineProfile::PersistentCookiesPolicy>(policy));
+ emit propertyChanged("persistentCookiesPolicy", policy);
+ }
- void setSpellCheckEnabled(bool enable);
+ 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<QWebEngineProfile::HttpCacheType>(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);
+ }
+ }
-signals:
- void nameChanged(const QString &name);
- void searchChanged(const QString &url);
- void homepageChanged(const QUrl &url);
- void newtabChanged(const QUrl &url);
+ void setSpellCheckEnabled(bool enable)
+ {
+ QWebEngineProfile::setSpellCheckEnabled(enable);
+ emit propertyChanged("spellCheckEnabed", enable);
+ }
- void propertyChanged(const QString &name, const QVariant &value);
- void attributeChanged(const QWebEngineSettings::WebAttribute attribute, const bool value);
- void headerChanged(const QString &name, const QString &value);
- void headerRemoved(const QString &name);
+ void setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor)
+ {
+ m_filters.append(interceptor);
+ }
protected:
// off-the-record constructor
- explicit WebProfile(const QString &name, QObject *parent = nullptr);
+ explicit WebProfile(const QString &id, QObject *parent = nullptr);
// default constructor
- explicit WebProfile(const QString &storageName, const QString &name, QObject *parent = nullptr);
+ explicit WebProfile(const QString &id, const QString &storageName, QObject *parent = nullptr);
-private:
+ 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<QWebEngineUrlRequestInterceptor*> m_filters;
QVector<QNetworkCookie> m_cookies;
QMap<QByteArray, QByteArray> m_headers;
};
diff --git a/src/webengine/webprofilemanager.cpp b/src/webengine/webprofilemanager.cpp
index bdd10f0..785251b 100644
--- a/src/webengine/webprofilemanager.cpp
+++ b/src/webengine/webprofilemanager.cpp
@@ -7,28 +7,23 @@
*/
#include "webprofilemanager.h"
-#include "configuration.h"
#include "webprofile.h"
-#include <QFileInfo>
-#include <QWebEngineSettings>
-static WebProfileManager *s_instance = nullptr;
+static WebProfileManager<false> *s_instance = nullptr;
-auto WebProfileManager::instance() -> const WebProfileManager *
+template <>
+void WebProfileManager<false>::make_global()
{
- return s_instance;
-}
-void WebProfileManager::setInstance(WebProfileManager *ptr)
-{
- s_instance = ptr;
+ if(s_instance == nullptr) {
+ s_instance = this;
+ }
}
-WebProfileManager::WebProfileManager(QObject *parent)
- : QObject(parent)
-{
-}
+template <>
+WebProfileManager<true>::~WebProfileManager() = default;
-WebProfileManager::~WebProfileManager()
+template <>
+WebProfileManager<false>::~WebProfileManager()
{
for(Profile p : qAsConst(profiles)) {
if(p.selfDestruct && p.settings != nullptr) {
@@ -48,111 +43,30 @@ WebProfileManager::~WebProfileManager()
}
}
-WebProfile *WebProfileManager::profile(const QString &id) const
+template <>
+WebProfile *WebProfileManager<true>::profile(const QString &id) const
{
- // Check if profile exists
- if(profiles.contains(id)) {
- return profiles.value(id).ptr;
- }
-
- return nullptr;
+ return s_instance->profile(id);
}
-WebProfile *WebProfileManager::add(const QString &id, const QString &path, bool isOffTheRecord)
+template <>
+QStringList WebProfileManager<true>::idList() const
{
- if(profiles.contains(id)) {
- return nullptr;
- }
-
- Configuration conf;
- Profile profile;
-
- if(!path.isEmpty())
- profile.settings = new QSettings(path, QSettings::IniFormat);
- else
- profile.settings = new QSettings;
+ return s_instance->idList();
+}
- // QWebEngineCore cleans up profiles automatically, so no need to set parent
- if(profile.settings->value("otr", isOffTheRecord).toBool()) {
- // name parent
- profile.ptr = new WebProfile(profile.settings->value("name", id).toString(), nullptr);
- } else {
- // storageName name parent
- profile.ptr = new WebProfile(id, profile.settings->value("name", id).toString(), nullptr);
+template <>
+void WebProfileManager<false>::walk(std::function<void(const QString &id, WebProfile *profile, const QSettings *settings)> f) const
+{
+ for(auto iter = profiles.begin(); iter != profiles.end(); ++iter) {
+ f(iter.key(), iter.value().ptr, iter.value().settings);
}
-
- profile.settings->setParent(profile.ptr);
-
- connect(profile.ptr, &WebProfile::nameChanged, profile.settings, [profile](const QString &name) {
- profile.settings->setValue("name", name);
- });
-
- profile.ptr->setSearch(profile.settings->value("search", conf.value<QString>("profile.search").value()).toString());
- connect(profile.ptr, &WebProfile::searchChanged, profile.settings, [profile](const QString &url) {
- profile.settings->setValue("search", url);
- });
-
- profile.ptr->setHomepage(profile.settings->value("homepage", conf.value<QString>("profile.homepage").value()).toUrl());
- connect(profile.ptr, &WebProfile::homepageChanged, profile.settings, [profile](const QUrl &url) {
- profile.settings->setValue("homepage", url);
- });
-
- profile.ptr->setNewtab(profile.settings->value("newtab", conf.value<QString>("profile.newtab").value()).toUrl());
- connect(profile.ptr, &WebProfile::newtabChanged, profile.settings, [profile](const QUrl &url) {
- profile.settings->setValue("newtab", url);
- });
-
- if(profile.settings != nullptr) {
- profile.settings->beginGroup("properties");
- {
- const auto keys = profile.settings->childKeys();
- for(const QString &key : keys) {
- profile.ptr->setProperty(qUtf8Printable(key), profile.settings->value(key));
- }
- }
- profile.settings->endGroup(); // properties
- connect(profile.ptr, &WebProfile::propertyChanged, [profile](const QString &property, const QVariant &value) {
- profile.settings->setValue("properties/" + property, value);
- });
-
- profile.settings->beginGroup("attributes");
- {
- const auto keys = profile.settings->childKeys();
- auto *settings = profile.ptr->settings();
- for(const QString &key : keys) {
- auto attribute = static_cast<QWebEngineSettings::WebAttribute>(key.toInt());
- settings->setAttribute(attribute, profile.settings->value(key).toBool());
- }
- }
- profile.settings->endGroup();
- connect(profile.ptr, &WebProfile::attributeChanged, [profile](const QWebEngineSettings::WebAttribute attr, const bool value) {
- profile.settings->setValue("attributes/" + QString::number(attr), value);
- });
-
- // headers
- profile.settings->beginGroup("headers");
- for(const QString &key : profile.settings->childKeys()) {
- profile.ptr->setHttpHeader(key.toLatin1(), profile.settings->value(key).toString().toLatin1());
- }
- profile.settings->endGroup();
- connect(profile.ptr, &WebProfile::headerChanged, [profile](const QString &name, const QString &value) {
- profile.settings->setValue("headers/" + name, value);
- });
- connect(profile.ptr, &WebProfile::headerRemoved, [profile](const QString &name) {
- profile.settings->remove("headers/" + name);
- });
-
- } // profile.settings != nullptr
-
- profiles[id] = profile;
- return profile.ptr;
}
-void WebProfileManager::deleteProfile(const QString &id)
+template <>
+void WebProfileManager<true>::walk(std::function<void(const QString &id, WebProfile *profile, const QSettings *settings)> f) const
{
- if(profiles.contains(id)) {
- profiles[id].selfDestruct = true;
- }
+ s_instance->walk(f);
}
void profileMenu(QMenu *menu, const std::function<void(WebProfile *)> &callback, WebProfile *current, bool checkable)
@@ -160,13 +74,10 @@ void profileMenu(QMenu *menu, const std::function<void(WebProfile *)> &callback,
auto *group = new QActionGroup(menu);
QObject::connect(menu, &QMenu::aboutToHide, group, &QActionGroup::deleteLater);
- for(const auto &profile : qAsConst(s_instance->profiles)) {
- auto *action = menu->addAction(profile.ptr->name(), profile.ptr, [profile, callback]() {
- callback(profile.ptr);
- });
+ s_instance->walk([=](const QString &, WebProfile *profile, const QSettings *) {
+ auto *action = menu->addAction(profile->name(), profile, [=]() { callback(profile); });
action->setCheckable(checkable);
- if(profile.ptr == current)
- action->setChecked(true);
+ action->setChecked(profile == current);
group->addAction(action);
- }
+ });
}
diff --git a/src/webengine/webprofilemanager.h b/src/webengine/webprofilemanager.h
index 0e18d5f..22fb31c 100644
--- a/src/webengine/webprofilemanager.h
+++ b/src/webengine/webprofilemanager.h
@@ -9,63 +9,97 @@
#ifndef SMOLBOTE_WEBPROFILEMANAGER_H
#define SMOLBOTE_WEBPROFILEMANAGER_H
+#include "singleton.hpp"
#include "webprofile.h"
#include <QDir>
#include <QFile>
+#include <QFileInfo>
#include <QMap>
#include <QMenu>
-#include <QObject>
-#include <QSettings>
#include <functional>
void profileMenu(QMenu *menu, const std::function<void(WebProfile *)> &callback, WebProfile *current = nullptr, bool checkable = false);
-class Browser;
-class WebProfileManager : public QObject
+template <bool use_global = true>
+class consumable(unconsumed) WebProfileManager
{
- Q_OBJECT
-
- friend class Browser;
- friend void profileMenu(QMenu *, const std::function<void(WebProfile *)> &, WebProfile *current, bool);
-
public:
- explicit WebProfileManager(QObject *parent);
- ~WebProfileManager() override;
+ 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;
+ }
- static auto instance() -> const WebProfileManager *;
- static void setInstance(WebProfileManager *ptr);
+ if(!profiles.contains(default_id)) {
+ Profile profile;
+ profile.settings = WebProfile::load(QString(), search, homepage, newtab);
+ profile.ptr = WebProfile::load(default_id, profile.settings, true);
+ profiles[default_id] = profile;
+ }
+ WebProfile::setDefaultProfile(profiles[default_id].ptr);
+ }
+ ~WebProfileManager();
- /** Create a profile with specified id
- * param id The profile ID
- * return WebProfile* The profile, or nullptr if one could not be created
- */
- WebProfile *profile(const QString &id) const;
+ WebProfileManager(const WebProfileManager &) = delete;
+ WebProfileManager &operator=(const WebProfileManager &) = delete;
- /** Set a profile for deletion
- * param id The profile ID
- * return void
- */
- [[deprecated]] void deleteProfile(const QString &id);
+ return_typestate(unconsumed) WebProfileManager(WebProfileManager<false> && other param_typestate(unconsumed))
+ {
+ static_assert(!use_global);
+ profiles = std::move(other.profiles);
+ other.consume();
+ }
+ WebProfileManager &operator=(WebProfileManager &&) = delete;
- const QStringList idList() const
+ callable_when(unconsumed) [[nodiscard]] WebProfile *profile(const QString &id) const
{
- return profiles.keys();
+ 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{ profile, settings, false };
+ }
}
- QString id(WebProfile *profile) const
+
+ callable_when(unconsumed) void deleteProfile(const QString &id)
{
- QMapIterator<QString, Profile> i(profiles);
- while(i.hasNext()) {
- i.next();
- if(i.value().ptr == profile)
- return i.key();
+ if constexpr(use_global) {
+ return;
+ }
+
+ if(profiles.contains(id)) {
+ profiles[id].selfDestruct = true;
}
- return QString();
}
-protected:
- WebProfile *add(const QString &id, const QString &path = QString(), bool isOffTheRecord = true);
+ callable_when(unconsumed) [[nodiscard]] QStringList idList() const
+ {
+ return profiles.keys();
+ }
+
+ callable_when(unconsumed) void walk(std::function<void(const QString &id, WebProfile *profile, const QSettings *settings)>) const;
+
+ callable_when(unconsumed) void make_global();
private:
+ set_typestate(consumed) void consume() {}
+
struct Profile {
WebProfile *ptr = nullptr;
QSettings *settings = nullptr;
diff --git a/src/webengine/webview.cpp b/src/webengine/webview.cpp
index c64333e..135f25c 100644
--- a/src/webengine/webview.cpp
+++ b/src/webengine/webview.cpp
@@ -7,20 +7,16 @@
*/
#include "webview.h"
-#include "subwindow/subwindow.h"
#include "webpage.h"
#include "webprofile.h"
#include "webprofilemanager.h"
#include "webviewcontextmenu.h"
#include <QContextMenuEvent>
-#include <QJsonObject>
#include <QWebEngineHistoryItem>
WebView::WebView(QWidget *parent)
: QWebEngineView(parent)
{
- m_parentWindow = qobject_cast<SubWindow *>(parent);
-
// load status and progress
connect(this, &QWebEngineView::loadStarted, this, [this]() {
m_loaded = false;
@@ -37,21 +33,22 @@ WebView::WebView(QWidget *parent)
});
}
-WebView::WebView(WebProfile *profile, QWidget *parent)
+WebView::WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent)
: WebView(parent)
{
+ cb_createWindow = cb;
Q_CHECK_PTR(profile);
m_profile = profile;
setPage(new WebPage(profile, this));
}
-WebView::WebView(const Session::WebView &data, QWidget *parent)
+WebView::WebView(const Session::WebView &data, cb_createWindow_t cb, QWidget *parent)
: WebView(parent)
{
- const auto *profileManager = WebProfileManager::instance();
- Q_CHECK_PTR(profileManager);
+ cb_createWindow = cb;
+ WebProfileManager profileManager;
- auto *profile = profileManager->profile(data.profile);
+ auto *profile = profileManager.profile(data.profile);
if(profile != nullptr) {
setProfile(profile);
}
@@ -75,19 +72,11 @@ void WebView::setProfile(WebProfile *profile)
Session::WebView WebView::serialize() const
{
- const auto *profileManager = WebProfileManager::instance();
- Q_CHECK_PTR(profileManager);
-
QByteArray historyData;
QDataStream historyStream(&historyData, QIODevice::WriteOnly);
historyStream << *history();
- return { profileManager->id(profile()), QString(), historyData };
-}
-
-bool WebView::isLoaded() const
-{
- return m_loaded;
+ return { profile()->getId(), QString(), historyData };
}
void WebView::search(const QString &term)
@@ -96,35 +85,6 @@ void WebView::search(const QString &term)
load(searchUrl);
}
-WebView *WebView::createWindow(QWebEnginePage::WebWindowType type)
-{
- Q_CHECK_PTR(m_parentWindow);
-
- // parent Window has been found
- auto index = m_parentWindow->addTab();
- WebView *view = m_parentWindow->view(index);
- switch(type) {
- case QWebEnginePage::WebBrowserWindow:
- // a complete web browser window
- break;
-
- case QWebEnginePage::WebBrowserTab:
- // a web browser tab
- m_parentWindow->setCurrentTab(index);
- break;
-
- case QWebEnginePage::WebDialog:
- // a window without decorations
- break;
-
- case QWebEnginePage::WebBrowserBackgroundTab:
- // a web browser tab, but don't swap to it
- break;
- }
-
- return view;
-}
-
void WebView::contextMenuEvent(QContextMenuEvent *event)
{
auto *menu = new WebViewContextMenu(this);
diff --git a/src/webengine/webview.h b/src/webengine/webview.h
index 5748691..a9c6866 100644
--- a/src/webengine/webview.h
+++ b/src/webengine/webview.h
@@ -11,10 +11,10 @@
#include "webpage.h"
#include <QWebEngineView>
+#include <functional>
#include <session.hpp>
class WebProfile;
-class SubWindow;
class WebViewContextMenu;
class WebView final : public QWebEngineView
{
@@ -25,8 +25,10 @@ class WebView final : public QWebEngineView
explicit WebView(QWidget *parent = nullptr);
public:
- explicit WebView(WebProfile *profile = nullptr, QWidget *parent = nullptr);
- explicit WebView(const Session::WebView &data, QWidget *parent = nullptr);
+ typedef std::function<WebView *(QWebEnginePage::WebWindowType)> cb_createWindow_t;
+
+ WebView(WebProfile *profile, cb_createWindow_t cb, QWidget *parent = nullptr);
+ WebView(const Session::WebView &data, cb_createWindow_t cb, QWidget *parent = nullptr);
~WebView() = default;
[[nodiscard]] WebProfile *profile() const
@@ -37,7 +39,10 @@ public:
[[nodiscard]] Session::WebView serialize() const;
- bool isLoaded() const;
+ bool isLoaded() const
+ {
+ return m_loaded;
+ }
public slots:
void search(const QString &term);
@@ -46,11 +51,14 @@ signals:
void newBookmark(const QString &title, const QUrl &url);
protected:
- WebView *createWindow(QWebEnginePage::WebWindowType type) override;
+ WebView *createWindow(QWebEnginePage::WebWindowType type) override
+ {
+ return cb_createWindow ? cb_createWindow(type) : nullptr;
+ }
void contextMenuEvent(QContextMenuEvent *event) override;
private:
- SubWindow *m_parentWindow = nullptr;
+ cb_createWindow_t cb_createWindow;
WebProfile *m_profile = nullptr;
bool m_loaded = false;
diff --git a/src/webengine/webviewcontextmenu.cpp b/src/webengine/webviewcontextmenu.cpp
index 856ff9c..1f6b337 100644
--- a/src/webengine/webviewcontextmenu.cpp
+++ b/src/webengine/webviewcontextmenu.cpp
@@ -7,7 +7,6 @@
*/
#include "webviewcontextmenu.h"
-#include "wallet/wallet.h"
#include "webprofilemanager.h"
#include "webview.h"
#include <QContextMenuEvent>
@@ -216,6 +215,7 @@ WebViewContextMenu::WebViewContextMenu(WebView *view)
}
#ifndef NDEBUG
+ /*
{
this->addSeparator();
auto *autofillAction = this->addAction(tr("Autofill form"));
@@ -223,5 +223,6 @@ WebViewContextMenu::WebViewContextMenu(WebView *view)
Wallet::autocompleteForm(view);
});
};
+ */
#endif
}