aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rwxr-xr-xdoc/man/genroff.sh4
-rw-r--r--doc/man/smolbote-profile.5.scd80
-rw-r--r--doc/man/smolbote.1.scd47
-rw-r--r--doc/man/smolbote.5.scd106
-rw-r--r--doc/man/smolboterc.5.scd21
-rw-r--r--doc/meson.build41
-rw-r--r--include/meson.build1
-rw-r--r--include/singleton.hpp27
-rw-r--r--lib/configuration/configuration.h2
-rw-r--r--lib/configuration/test/main.cpp2
-rw-r--r--lib/pluginloader/test/pluginloader-sigmatch.cpp2
-rw-r--r--lib/session_formats/test/json.cpp3
-rw-r--r--meson.build18
-rw-r--r--meson_options.txt2
-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
51 files changed, 1186 insertions, 993 deletions
diff --git a/.gitignore b/.gitignore
index 43ad28c..1347c82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ tools/src/crashhandler/defaults.go
.config
.config.old
compile_commands.json
+*.roff
lib/configuration/test/corpus
diff --git a/doc/man/genroff.sh b/doc/man/genroff.sh
new file mode 100755
index 0000000..1d513f4
--- /dev/null
+++ b/doc/man/genroff.sh
@@ -0,0 +1,4 @@
+#/usr/bin/env sh
+
+scdoc < $1
+
diff --git a/doc/man/smolbote-profile.5.scd b/doc/man/smolbote-profile.5.scd
new file mode 100644
index 0000000..115e6ed
--- /dev/null
+++ b/doc/man/smolbote-profile.5.scd
@@ -0,0 +1,80 @@
+smolbote(5) "smolbote profile configuration"
+
+# NAME
+
+smolbote profile format
+
+# DESCRIPTION
+
+A profile is a collection of settings, policies, scripts, cookies, cache and
+history. They store their data separately, and can be used to isolate pages from
+each other.
+
+Off-the-record profiles only use in-memory cache and don't keep cookies or
+history between sessions. They are useful as a private browsing mode.
+
+Each subwindow has a default profile it uses when opening new tabs. This can be
+changed from the _Subwindow_ menu. Additionally, tabs can have their profiles
+individually changed from the _Page_ menu.
+
+Profiles can be either temporary or permanent. Temporary profiles expire when
+the application is closed, whereas permanent profiles are kept between runs.
+
+The browser will save changes made to profiles automatically. If you want to
+prevent modifications, set the corresponding .profile to read-only.
+
+# SECTIONS
+
+Profiles are defined in an INI format file with a `.profile` extension. The file
+name is used as the profile's ID.
+If no value is specified, the default value is used instead.
+
+## General
+- `name`: Name (Default: same as the ID)
+- `otr`: Off-the-record toggle, true or false (Default: true)
+- `search`: The search engine URL, with the following format:
+ https://engine.url/q=%1, where %1 will be substituted by the search term.
+- `homepage`: The homepage URL. (Default: about:blank)
+- `newtab`: The URL that should be loaded by default when opening a new tab with
+ this profile. (Default: about:blank)
+
+## properties
+- `cachePath`
+- `persistentStoragePath`
+- `persistentCookiesPolicy`
+- `httpAcceptLanguage`
+- `httpCacheMaximumSize`
+- `httpCacheType`
+- `httpUserAgent`
+- `spellCheckEnabled`
+
+See the QtWebEngine documentation[0] for more information.
+
+## attributes
+QWebEngineSettings::WebAttribute, see the QtWebEngine documentation[1] for more
+information.
+
+## headers
+HTTP headers that should be enforced by the profile.
+
+# EXAMPLES
+To check where profiles are read from, you can use:
+ $ poi configuration --dump | grep profile.path
+
+Creating a `otr.profile` file in that location will create a permanent
+off-the-record profile with an ID of `otr`. You can customize it further:
+
+```
+[General]
+name=off-the-record
+
+[headers]
+Dnt=1
+```
+
+This will name the profile `off-the-record` in menus, and cause it to send out a
+Dnt (Do not track) header.
+
+# SEE ALSO
+0: https://doc.qt.io/qt-5/qwebengineprofile.html++
+1: https://doc.qt.io/qt-5/qwebenginesettings.html
diff --git a/doc/man/smolbote.1.scd b/doc/man/smolbote.1.scd
index 4ca55b7..758fda3 100644
--- a/doc/man/smolbote.1.scd
+++ b/doc/man/smolbote.1.scd
@@ -10,66 +10,29 @@ smolbote - yet another no-frills web browser
# DESCRIPTION
-smolbote is a cross-platform keep-it-simple free software web browser that
-uses Qt and QtWebEngine.
+smolbote is a cross-platform keep-it-simple free software web browser that uses Qt and QtWebEngine.
# USAGE
## Command-line options
- `-h`, `--help`: Display command-line options list.
- `-v`, `--version`: Display version information.
-- `--build`: Display build commit.
+- `-b`, `--build`: Display build commit.
- `-c`, `--config`: Set configuration file.
- `--no-remote`: Don't check for other instances when starting.
- `-s, --session`: Open the selected session.
- `--pick-session`: Open all available sessions and select which one to open.
-## Profiles
-A Profile is a collection of settings, policies, scripts, cookies, cache and
-history. Profiles can be used to isolate pages from each other.
-
-Off-the-record profiles only use in-memory cache and save no files to disk.
-
-Profiles can be either temporary or permanent. Temporary profiles expire when
-the application is closed, whereas permanent profiles are kept between runs.
-
-Each window has a default profile it uses when opening new tabs. This can be
-changed from the window's menu. Additionally, tabs can have their profiles
-individually changed from their context menu.
-
-Because profiles store all their data separately, you can log in into the same
-site with a different account from each profile. However, links opened into new
-tabs will still use the subwindow's default profile. For example, you can set a
-profile to hold login information for a site, but all new tabs opened from that
-site would still be using the default off-the-record profile.
-
-## Plugins
-Plugins a way of extending smolbote's functionality using the Qt plugin system.
-They are not to be confused with NPAPI/PPAPI or WebExtension plugins.
-
-To enable a plugin either copy it or symlink it in the plugins.path location,
-or set its absolute path as the plugins.path.
-
-## Filters
-smolbote has a singular URL request filter that is installed onto all profiles.
-Any setting applied to it will be applied to all profiles.
-
-filter.header: A list of header-value pairs, separated by a colon (':').
-
-You can specify multiple headers by using --filter.header multiple times:
-```
-poi --filter.header "Dnt:1" --filter.header "Accept:text/html"
-```
-
# SEE ALSO
-*smolbote*(5) - configuration file and options
+*smolboterc*(5) - configuration file and options++
+*smolbote-profile*(5) - profiles and how to use them
# AUTHORS
Maintained by <aqua@iserlohn-fortress.net>.
-Up-to-date sources can be found at https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote
+Up-to-date sources can be found at https://neueland.iserlohn-fortress.net/cgit/smolbote
Bug reports and patches can be submitted by email to <aqua@iserlohn-fortress.net>.
diff --git a/doc/man/smolbote.5.scd b/doc/man/smolbote.5.scd
deleted file mode 100644
index 063a050..0000000
--- a/doc/man/smolbote.5.scd
+++ /dev/null
@@ -1,106 +0,0 @@
-smolbote(5) "smolbote configuration"
-
-# NAME
-
-smolbote - configuration file and options description
-
-# DESCRIPTION
-
-The smolbote configuration is loaded at startup, and is not reloaded when
-changed. By default (without any plugins), the browser does not change its
-configuration or overwrite this file. Thus, it can be made read-only.
-
-The settings in this file change your preferences and keybindings.
-
-Lines starting with *#* are considered comments and ignored.
-
-Options taking multiple values (list options) can have multiple values set by
-using the same option name multiple times.
-
-# SECTIONS
-
-## Browser Options
-
-*browser.stylesheet* (arg):: TODO
-
-*browser.iconTheme* (arg)::
-Set arg as icon theme.
-
-*browser.locale* (arg)::
-Set Qt localization. This will translate Qt strings, such as the dialog buttons.
-For possible values see `/usr/share/qt/translations`.
-
-*browser.translation* (arg)::
-Set browser localization. This will translate the rest of the browser. For
-possible values, see `install-root/share/smolbote/lang`.
-
-*browser.session.path* (arg=~/.config/smolbote/session.d)::
-Location where browser sessions should be saved and loaded from by default.
-
-*plugins.path* (arg=~/.config/smolbote/plugins.d)::
-Location where plugins should be loaded from.
-
-*bookmarks.path* (arg=~/.config/smolbote/bookmarks.xbel)::
-Default bookmarks location.
-*bookmarks.shortcut* (arg=Ctrl+B)::
-Show/Hide bookmakrs widget shortcut.
-
-*downloads.path* (arg=~/Downloads)::
-Default downloads location.
-*downloads.shortcut* (arg=Ctrl+D)::
-Show/Hide downloads widget shortcut.
-
-## UI Options
-
-*mainwindow.height* (arg=720)
-*mainwindow.width* (arg=1280)
-*mainwindow.maximized* (arg=1):: Default window size and maximize toggle.
-
-*mainwindow.title* (arg=smolbote):: Default window title.
-
-*mainwindow.shortcuts.saveSession* (arg=Ctrl+S,S):: Save Session shortcut
-*mainwindow.shortcuts.openSession* (arg=Ctrl+S,O):: Open Session shortcut
-*mainwindow.shortcuts.newGroup* (arg=Ctrl+G):: New Group shortcut (subwindow)
-*mainwindow.shortcuts.newWindow* (arg=Ctrl+N):: New Window shortcut
-*mainwindow.shortcuts.about* (arg=F1):: About dialog shortcut
-*mainwindow.shortcuts.quit* (arg=Ctrl+Q):: Quit shortcut
-*mainwindow.shortcuts.search* (arg=F3):: Search in page shortcut
-*mainwindow.shortcuts.tileWindows* (arg=F9):: Tile subwindows shortcut
-*mainwindow.shortcuts.cascadeWindows* (arg=F10):: Cascade subwindows shortcut
-
-*navigation.movable* (arg=0):: Make navigation bar movable
-*navigation.shortcuts.back* (arg=Ctrl+Left):: Back shortcut
-*navigation.shortcuts.backMenu* (arg=Ctrl+Down):: Back menu shortcut
-*navigation.shortcuts.forward* (arg=Ctrl+Right):: Forward shortcut
-*navigation.shortcuts.forwardMenu* (arg=Ctrl+Up):: Forward menu shortcut
-*navigation.shortcuts.refresh* (arg=F5):: Refresh shortcut
-*navigation.shortcuts.reload* (arg=Ctrl+F5):: Reload shortcut
-*navigation.shortcuts.home* (arg=Ctrl+Home):: Home shortcut
-
-*addressbar.shortcuts.focus* (arg=F4):: Focus on the address
-*addressbar.shortcuts.menu* (arg=F2):: Show addressbar menu
-
-*subwindow.shortcuts.menu* (arg=Ctrl+M):: Show subwindow menu
-*subwindow.shortcuts.new* (arg=Ctrl+T):: Create new tab shortcut
-*subwindow.shortcuts.close* (arg=Ctrl+X):: Close tab shortcut
-*subwindow.shortcuts.restoreTab* (arg=Ctrl+Shift+T):: Restore last closed tab
-*subwindow.shortcuts.left* (arg=Ctrl+O):: Move to tab on the left
-*subwindow.shortcuts.moveLeft* (arg=Ctrl+Shift+O):: Move tab to the left
-*subwindow.shortcuts.right* (arg=Ctrl+P):: Move to tab on the right
-*subwindow.shortcuts.moveRight* (arg=Ctrl+Shift+P):: Move tab to the right
-*subwindow.shortcuts.fullscreen* (arg=F11):: Show page fullscreen
-
-## Security Options
-
-*filter.hosts* (arg=~/.config/smolbote/hosts.d):: Hostlist
-*filter.adblock* arg:: TODO
-*filter.header* (list)::
-A list of HTTP headers to set. Each header should be given as a colon-separated
-name:value pair.
-
-*profile.default* (arg):: Default browser profile
-*profile.path* (arg=~/.config/smolbote/profiles.d):: Profile location
-*profile.search* (arg=`https://duckduckgo.com/?q=%1&ia=web`)::
-Default search engine. %1 is replaced by the search term.
-*profile.homepage* (arg=`about:blank`):: Default homepage
-*profile.newtab* (arg=`about:blank`):: Default new tab page
diff --git a/doc/man/smolboterc.5.scd b/doc/man/smolboterc.5.scd
new file mode 100644
index 0000000..f340c01
--- /dev/null
+++ b/doc/man/smolboterc.5.scd
@@ -0,0 +1,21 @@
+smolbote(5) "smolbote configuration"
+
+# NAME
+
+smolbote - configuration file and options description
+
+# DESCRIPTION
+
+The smolbote configuration is loaded at startup, and is not reloaded when
+changed. By default (without any plugins), the browser does not change its
+configuration or overwrite this file. Thus, it can be made read-only.
+
+The settings in this file change your preferences and keybindings.
+
+Lines starting with *#* are considered comments and ignored.
+
+Options taking multiple values (list options) can have multiple values set by
+using the same option name multiple times.
+
+# SECTIONS
+
diff --git a/doc/meson.build b/doc/meson.build
index 26e5d6c..6490969 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -1,33 +1,16 @@
-scdoc = find_program('scdoc', required: get_option('manpage'), disabler: true)
-if scdoc.found()
+if not get_option('manpage')
+ subdir_done()
+endif
sh = find_program('sh', required: true, native: true, disabler: true)
-man_files = ['man/smolbote.1.scd', 'man/smolbote.5.scd']
-
-foreach input : man_files
- topic = input.split('/')[-1].split('.')[-3]
- section = input.split('.')[-2]
- output = '@0@.@1@'.format(topic, section)
-
- message('creating manpage target ' + output)
- custom_target(output,
- build_by_default: true,
-
- input: input,
- output: output,
-
- # scdoc takes input from stdin, and prints its output to stdout
- # meson uses 'capture' to store stdout to output, but there is no stdin toggle
- #command: [scdoc],
- #capture: true,
-
- # workaround using sh
- command: [sh, '-c', '@0@ < @INPUT0@ > @OUTPUT0@'.format(scdoc.path())],
-
- install: true,
- install_dir: '@0@/man@1@'.format(get_option('mandir'), section)
- )
+foreach f : [ 'smolbote.1', 'smolboterc.5', 'smolbote-profile.5' ]
+manpage = custom_target(f,
+ input: 'man'/f+'.scd',
+ output: '@BASENAME@',
+ capture: true,
+ command: [ sh, meson.current_source_dir()/'man/genroff.sh', '@INPUT@' ],
+ install: true,
+ install_dir: get_option('mandir')
+)
endforeach
-
-endif # manpage
diff --git a/include/meson.build b/include/meson.build
index 7601966..1cd9957 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -1,4 +1,5 @@
plugininterfaces_include = include_directories('.')
+smolbote_include = include_directories('.')
install_headers('smolbote/filterinterface.hpp', 'smolbote/plugininterface.hpp',
subdir: 'smolbote')
diff --git a/include/singleton.hpp b/include/singleton.hpp
new file mode 100644
index 0000000..d13e29e
--- /dev/null
+++ b/include/singleton.hpp
@@ -0,0 +1,27 @@
+#ifndef SMOLBOTE_SINGLETON_HPP
+#define SMOLBOTE_SINGLETON_HPP
+
+/*
+ * Clang consumed semantics
+ * States can be: unconsumed, consumed, unknown
+ * Mark classes with consumable(unconsumed)
+ * Mark constructors with return_typestate(unconsumed)
+ * Mark invalidating functions with set_typestate(consumed)
+ */
+
+#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
+
+#endif // SMOLBOTE_SINGLETON_HPP
diff --git a/lib/configuration/configuration.h b/lib/configuration/configuration.h
index 09c41a0..cd3c244 100644
--- a/lib/configuration/configuration.h
+++ b/lib/configuration/configuration.h
@@ -42,7 +42,7 @@ public:
return_typestate(unconsumed) Configuration();
return_typestate(unconsumed) Configuration(std::initializer_list<std::pair<std::string, conf_value_t>> l) noexcept;
- return_typestate(unconsumed) Configuration(const Configuration &other param_typestate(unconsumed)) = default;
+ Configuration(const Configuration &) = delete;
Configuration &operator=(const Configuration &) = delete;
return_typestate(unconsumed) Configuration(Configuration && other param_typestate(unconsumed)) = default;
diff --git a/lib/configuration/test/main.cpp b/lib/configuration/test/main.cpp
index 21a02cf..fa04f10 100644
--- a/lib/configuration/test/main.cpp
+++ b/lib/configuration/test/main.cpp
@@ -1,5 +1,7 @@
#define CATCH_CONFIG_RUNNER
+// clazy:excludeall=non-pod-global-static
+
#include "configuration.h"
#include <QApplication>
#include <catch2/catch.hpp>
diff --git a/lib/pluginloader/test/pluginloader-sigmatch.cpp b/lib/pluginloader/test/pluginloader-sigmatch.cpp
index 991d9bc..0f4789a 100644
--- a/lib/pluginloader/test/pluginloader-sigmatch.cpp
+++ b/lib/pluginloader/test/pluginloader-sigmatch.cpp
@@ -1,5 +1,7 @@
#define CATCH_CONFIG_MAIN
+// clazy:excludeall=non-pod-global-static
+
#include "pluginloader.h"
#include <catch2/catch.hpp>
diff --git a/lib/session_formats/test/json.cpp b/lib/session_formats/test/json.cpp
index 4c6b683..e8cccb3 100644
--- a/lib/session_formats/test/json.cpp
+++ b/lib/session_formats/test/json.cpp
@@ -1,4 +1,7 @@
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+
+// clazy:excludeall=non-pod-global-static
+
#include <catch2/catch.hpp>
#include <session_json.hpp>
diff --git a/meson.build b/meson.build
index adee6c2..3cf7105 100644
--- a/meson.build
+++ b/meson.build
@@ -31,14 +31,18 @@ add_project_arguments(cxx.get_supported_arguments([
'-ffunction-sections', # Place each function into its own section, better ASLR but larger executables
'-fstack-protector-all', # Emit code to check for buffer overflows on all functions
'-fstack-clash-protection', # Emit code to check for stack clash attacks
- '-fconcepts',
- '-mspeculative-load-hardening', # Spectre v1 mitigation
+ # gcc specific
+ '-fconcepts', # gcc9 c++20 compat
- '-Wconsumed', # (clang) use-after-move warnings
- '-Wdate-time', # Warn when using __TIME__ and __DATE__ macros
- '-Wimplicit-fallthrough',
- '-Wold-style-cast'
+ # clang specific
+ '-mspeculative-load-hardening', # Spectre v1 mitigation
+ '-Wconsumed', # use-after-move warnings
+ '-Xclang -plugin-arg-clazy -Xclang level0,level1', # clazy default warning level
+
+ '-Wdate-time', # __TIME__ and __DATE__ macros
+ '-Wimplicit-fallthrough', # switch implicit fallthrough
+ '-Wold-style-cast' # c-style casts
]), language: 'cpp')
if get_option('buildtype') == 'release'
@@ -49,7 +53,7 @@ endif
mod_qt5 = import('qt5')
dep_qt5 = dependency('qt5',
- modules: [ 'Core', 'Network', 'Widgets', 'Svg', 'WebEngineWidgets', 'Concurrent', 'Test' ],
+ modules: [ 'Core', 'Network', 'Widgets', 'Svg', 'WebEngine', 'WebEngineWidgets', 'Concurrent' ],
include_type: 'system'
)
diff --git a/meson_options.txt b/meson_options.txt
index 1bbda86..eb39c03 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,7 +1,7 @@
option('poi', description: 'Executable name', type: 'string', value: 'poi')
option('firejail', description: 'firejail executable name', type: 'string', value: '/usr/bin/firejail')
-option('manpage', description: 'Generate and install manpage', type: 'feature', value: 'auto')
+option('manpage', description: 'Generate and install manpage', type: 'boolean', value: 'true')
option('translations', description: 'Generate and install translations', type: 'feature', value: 'auto')
option('crashhandler', description: 'Enable breakpad crash reporting', type: 'feature', value: 'auto')
option('updater', description: 'Build updater component', type: 'feature', value: 'auto')
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
}