--- /dev/null
+/*
+ * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
+ *
+ * Privacy Browser PC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser PC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser PC. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "TabWidget.h"
+#include "Settings.h"
+#include "ui_AddTabWidget.h"
+#include "ui_TabWidget.h"
+#include "databases/CookiesDatabase.h"
+#include "databases/DomainsDatabase.h"
+#include "dialogs/SaveDialog.h"
+#include "filters/MouseEventFilter.h"
+#include "helpers/SearchEngineHelper.h"
+#include "helpers/UserAgentHelper.h"
+#include "interceptors/UrlRequestInterceptor.h"
+#include "windows/BrowserWindow.h"
+
+// KDE Framework headers.
+#include <KIO/FileCopyJob>
+#include <KIO/JobUiDelegate>
+
+// Qt toolkit headers.
+#include <QAction>
+#include <QFileDialog>
+#include <QGraphicsScene>
+#include <QGraphicsView>
+#include <QPrintDialog>
+#include <QPrintPreviewDialog>
+#include <QPrinter>
+
+// Initialize the public static variables.
+QString TabWidget::webEngineDefaultUserAgent = QStringLiteral("");
+
+// Construct the class.
+TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
+{
+ // Instantiate the UIs.
+ Ui::TabWidget tabWidgetUi;
+ Ui::AddTabWidget addTabWidgetUi;
+
+ // Setup the main UI.
+ tabWidgetUi.setupUi(this);
+
+ // Get a handle for the tab widget.
+ tabWidgetPointer = tabWidgetUi.tabWidget;
+
+ // Setup the add tab UI.
+ addTabWidgetUi.setupUi(tabWidgetPointer);
+
+ // Get handles for the add tab widgets.
+ QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
+ QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
+
+ // Display the add tab widget.
+ tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
+
+ // Add the first tab.
+ addFirstTab();
+
+ // Process tab events.
+ connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
+ connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
+ connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
+
+ // Store a copy of the WebEngine default user agent.
+ webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
+
+ // Instantiate the mouse event filter pointer.
+ MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
+
+ // Install the mouse event filter.
+ qApp->installEventFilter(mouseEventFilterPointer);
+
+ // Process mouse forward and back commands.
+ connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
+ connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
+}
+
+TabWidget::~TabWidget()
+{
+ // Manually delete each WebEngine page.
+ for (int i = 0; i < tabWidgetPointer->count(); ++i)
+ {
+ // Get the privacy WebEngine view.
+ PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
+
+ // Deletion the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+ delete privacyWebEngineViewPointer->page();
+ }
+}
+
+// The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
+void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
+{
+ // Create a url.
+ QUrl url;
+
+ // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be. <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
+ if (!cookie.domain().startsWith(QStringLiteral(".")))
+ {
+ // Populate the URL.
+ url.setHost(cookie.domain());
+ url.setScheme(QStringLiteral("https"));
+
+ // Clear the domain from the cookie.
+ cookie.setDomain(QStringLiteral(""));
+ }
+
+ // Add the cookie to the store.
+ if (webEngineCookieStorePointer == nullptr)
+ currentWebEngineCookieStorePointer->setCookie(cookie, url);
+ else
+ webEngineCookieStorePointer->setCookie(cookie, url);
+}
+
+void TabWidget::addFirstTab()
+{
+ // Create the first tab.
+ addTab();
+
+ // Update the UI with the tab settings.
+ updateUiWithTabSettings();
+
+ // Set the focus on the current tab widget. This prevents the tab bar from showing a blue bar under the label of the first tab.
+ tabWidgetPointer->currentWidget()->setFocus();
+}
+
+void TabWidget::addTab()
+{
+ // Create a privacy WebEngine view.
+ PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
+
+ // Add a new tab.
+ int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
+
+ // Set the default tab icon.
+ tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
+
+ // Create an off-the-record profile (the default when no profile name is specified).
+ QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
+
+ // Create a WebEngine page.
+ QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
+
+ // Set the WebEngine page.
+ privacyWebEngineViewPointer->setPage(webEnginePagePointer);
+
+ // Get handles for the web engine elements.
+ QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
+ QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
+
+ // Update the URL line edit when the URL changes.
+ connect(privacyWebEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
+
+
+ // Update the progress bar.
+ connect(privacyWebEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
+ connect(privacyWebEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
+ connect(privacyWebEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
+
+ // Handle full screen requests.
+ connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
+
+ // Listen for hovered link URLs.
+ connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
+
+ // Handle file downloads.
+ connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
+
+ // Instantiate the URL request interceptor.
+ UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
+
+ // Set the URL request interceptor.
+ webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
+
+ // Reapply the domain settings when the host changes.
+ connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
+
+ // Set the local storage filter.
+ webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
+ {
+ // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
+ if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
+ {
+ //qDebug().noquote().nospace() << "Third-party request blocked: " << filterRequest.origin;
+
+ // Return false.
+ return false;
+ }
+
+ // Allow the request if local storage is enabled.
+ if (privacyWebEngineViewPointer->localStorageEnabled)
+ {
+ //qDebug().noquote().nospace() << "Request allowed by local storage: " << filterRequest.origin;
+
+ // Return true.
+ return true;
+ }
+
+ //qDebug().noquote().nospace() << "Request blocked by default: " << filterRequest.origin;
+
+ // Block any remaining local storage requests.
+ return false;
+ });
+
+ // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+
+ // Don't allow JavaScript to open windows.
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
+
+ // Allow keyboard navigation.
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
+
+ // Enable full screen support.
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
+
+ // Require user interaction to play media.
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
+
+ // Limit WebRTC to public IP addresses.
+ webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
+
+ // Define an update cookie count lambda.
+ auto updateCookieCount = [privacyWebEngineViewPointer, this] (const int numberOfCookies)
+ {
+ // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
+ if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
+ emit updateCookiesAction(numberOfCookies);
+ };
+
+ // Update the cookies action.
+ connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, this, updateCookieCount);
+
+ // Process cookie changes.
+ connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
+ connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
+
+ // Get a list of durable cookies.
+ QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
+
+ // Add the durable cookies to the store.
+ for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
+ addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
+
+ // Define an update tab title lambda.
+ auto updateTabTitle = [privacyWebEngineViewPointer, this] (const QString &title)
+ {
+ // Get the index for this tab.
+ int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+
+ // Update the title for this tab.
+ tabWidgetPointer->setTabText(tabIndex, title);
+ };
+
+ // Update the title when it changes.
+ connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, this, updateTabTitle);
+
+ // Define an update tab icon lambda.
+ auto updateTabIcon = [privacyWebEngineViewPointer, this] (const QIcon &icon)
+ {
+ // Get the index for this tab.
+ int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+
+ // Update the icon for this tab.
+ if (icon.isNull())
+ tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
+ else
+ tabWidgetPointer->setTabIcon(tabIndex, icon);
+ };
+
+ // Update the icon when it changes.
+ connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, this, updateTabIcon);
+
+ // Move to the new tab.
+ tabWidgetPointer->setCurrentIndex(newTabIndex);
+}
+
+void TabWidget::applyApplicationSettings()
+{
+ // Set the tab position.
+ if (Settings::tabsOnTop())
+ tabWidgetPointer->setTabPosition(QTabWidget::North);
+ else
+ tabWidgetPointer->setTabPosition(QTabWidget::South);
+
+ // Set the search engine URL.
+ searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
+
+ // Emit the update search engine actions signal.
+ emit updateSearchEngineActions(Settings::searchEngine(), true);
+}
+
+// This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
+// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
+void TabWidget::applyDomainSettingsAndReload()
+{
+ // Apply the domain settings. `true` reloads the website.
+ applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
+}
+
+// This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
+// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
+void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
+{
+ // Apply the domain settings `false` does not reload the website.
+ applyDomainSettings(hostname, false);
+}
+
+// Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
+void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
+{
+ // Get the record for the hostname.
+ QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
+
+ // Check if the hostname has domain settings.
+ if (domainQuery.isValid()) // The hostname has domain settings.
+ {
+ // Get the domain record.
+ QSqlRecord domainRecord = domainQuery.record();
+
+ // Store the domain settings name.
+ currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
+
+ // Set the JavaScript status.
+ switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
+ {
+ // Set the default JavaScript status.
+ case (DomainsDatabase::SYSTEM_DEFAULT):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
+
+ break;
+ }
+
+ // Disable JavaScript.
+ case (DomainsDatabase::DISABLED):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+
+ break;
+ }
+
+ // Enable JavaScript.
+ case (DomainsDatabase::ENABLED):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+ break;
+ }
+ }
+
+ // Set the local storage status.
+ switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
+ {
+ // Set the default local storage status.
+ case (DomainsDatabase::SYSTEM_DEFAULT):
+ {
+ currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
+
+ break;
+ }
+
+ // Disable local storage.
+ case (DomainsDatabase::DISABLED):
+ {
+ currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
+
+ break;
+ }
+
+ // Enable local storage.
+ case (DomainsDatabase::ENABLED):
+ {
+ currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
+
+ break;
+ }
+ }
+
+ // Set the DOM storage status.
+ switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
+ {
+ // Set the default DOM storage status.
+ case (DomainsDatabase::SYSTEM_DEFAULT):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
+
+ break;
+ }
+
+ // Disable DOM storage.
+ case (DomainsDatabase::DISABLED):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
+
+ break;
+ }
+
+ // Enable DOM storage.
+ case (DomainsDatabase::ENABLED):
+ {
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
+
+ break;
+ }
+ }
+
+ // Set the user agent.
+ currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
+
+ // Check if a custom zoom factor is set.
+ if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
+ {
+ // Store the current zoom factor.
+ currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
+ }
+ else
+ {
+ // Reset the current zoom factor.
+ currentZoomFactor = Settings::zoomFactor();
+ }
+
+ // Set the zoom factor. The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
+ currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
+ }
+ else // The hostname does not have domain settings.
+ {
+ // Reset the domain settings name.
+ currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
+
+ // Set the JavaScript status.
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
+
+ // Set the local storage status.
+ //currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
+
+ // Set DOM storage.
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
+
+ // Set the user agent.
+ currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
+
+ // Store the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
+ currentZoomFactor = Settings::zoomFactor();
+
+ // Set the zoom factor.
+ currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
+ }
+
+ // Update the UI.
+ emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+ emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+ emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+ emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+ emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+ emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
+
+ // Reload the website if requested.
+ if (reloadWebsite)
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
+{
+ // Store the search engine name.
+ QString searchEngineName = searchEngineActionPointer->text();
+
+ // Strip out any `&` characters.
+ searchEngineName.remove('&');
+
+ // Store the search engine string.
+ searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
+
+ // Update the search engine actionas.
+ emit updateSearchEngineActions(searchEngineName, false);
+}
+
+void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
+{
+ // Get the user agent name.
+ QString userAgentName = userAgentActionPointer->text();
+
+ // Strip out any `&` characters.
+ userAgentName.remove('&');
+
+ // Apply the user agent.
+ currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
+
+ // Update the user agent actions.
+ emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
+
+ // Reload the website.
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+// This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
+void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
+{
+ // Update the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
+ currentZoomFactor = zoomFactor;
+
+ // Set the zoom factor.
+ currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
+}
+
+void TabWidget::back() const
+{
+ // Go back.
+ currentPrivacyWebEngineViewPointer->back();
+}
+
+void TabWidget::deleteAllCookies() const
+{
+ // Delete all the cookies.
+ currentWebEngineCookieStorePointer->deleteAllCookies();
+}
+
+void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
+{
+ // Delete the cookie.
+ currentWebEngineCookieStorePointer->deleteCookie(cookie);
+}
+
+void TabWidget::deleteTab(const int tabIndex)
+{
+ // Get the privacy WebEngine view.
+ PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
+
+ // Proccess the tab delete according to the number of tabs.
+ if (tabWidgetPointer->count() > 1) // There is more than one tab.
+ {
+ // Delete the tab.
+ tabWidgetPointer->removeTab(tabIndex);
+
+ // Delete the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+ delete privacyWebEngineViewPointer->page();
+
+ // Delete the privacy WebEngine view.
+ delete privacyWebEngineViewPointer;
+ }
+ else // There is only one tab.
+ {
+ // Close Privacy Browser.
+ window()->close();
+ }
+}
+
+void TabWidget::forward() const
+{
+ // Go forward.
+ currentPrivacyWebEngineViewPointer->forward();
+}
+
+void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
+{
+ // Make it so.
+ emit fullScreenRequested(fullScreenRequest.toggleOn());
+
+ // Accept the request.
+ fullScreenRequest.accept();
+}
+
+std::list<QNetworkCookie>* TabWidget::getCookieList() const
+{
+ // Return the current cookie list.
+ return currentPrivacyWebEngineViewPointer->cookieListPointer;
+}
+
+QString& TabWidget::getDomainSettingsName() const
+{
+ // Return the domain settings name.
+ return currentPrivacyWebEngineViewPointer->domainSettingsName;
+}
+
+void TabWidget::home() const
+{
+ // Load the homepage.
+ currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
+}
+
+void TabWidget::loadFinished() const
+{
+ // Hide the progress bar.
+ emit hideProgressBar();
+}
+
+void TabWidget::loadInitialWebsite()
+{
+ // Apply the application settings.
+ applyApplicationSettings();
+
+ // Get the arguments.
+ QStringList argumentsStringList = qApp->arguments();
+
+ // Check to see if the arguments lists contains a URL.
+ if (argumentsStringList.size() > 1)
+ {
+ // Load the URL from the arguments list.
+ currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
+ }
+ else
+ {
+ // Load the homepage.
+ home();
+ }
+}
+
+void TabWidget::loadProgress(const int &progress) const
+{
+ // Show the progress bar.
+ emit showProgressBar(progress);
+}
+
+void TabWidget::loadStarted() const
+{
+ // Show the progress bar.
+ emit showProgressBar(0);
+}
+
+void TabWidget::loadUrlFromLineEdit(QString url) const
+{
+ // Decide if the text is more likely to be a URL or a search.
+ if (url.startsWith("file://")) // The text is likely a file URL.
+ {
+ // Load the URL.
+ currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
+ }
+ else if (url.contains(".")) // The text is likely a URL.
+ {
+ // Check if the URL does not start with a valid protocol.
+ if (!url.startsWith("http"))
+ {
+ // Add `https://` to the beginning of the URL.
+ url = "https://" + url;
+ }
+
+ // Load the URL.
+ currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
+ }
+ else // The text is likely a search.
+ {
+ // Load the search.
+ currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
+ }
+}
+
+void TabWidget::mouseBack() const
+{
+ // Go back if possible.
+ if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
+ {
+ // Clear the URL line edit focus.
+ emit clearUrlLineEditFocus();
+
+ // Go back.
+ currentPrivacyWebEngineViewPointer->back();
+ }
+}
+
+void TabWidget::mouseForward() const
+{
+ // Go forward if possible.
+ if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
+ {
+ // Clear the URL line edit focus.
+ emit clearUrlLineEditFocus();
+
+ // Go forward.
+ currentPrivacyWebEngineViewPointer->forward();
+ }
+}
+
+void TabWidget::pageLinkHovered(const QString &linkUrl) const
+{
+ // Emit a signal so that the browser window can update the status bar.
+ emit linkHovered(linkUrl);
+}
+
+void TabWidget::print() const
+{
+ // Create a printer.
+ QPrinter printer;
+
+ // Set the resolution to be 300 dpi.
+ printer.setResolution(300);
+
+ // Create a printer dialog.
+ QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
+
+ // Display the dialog and print the page if instructed.
+ if (printDialog.exec() == QDialog::Accepted)
+ printWebpage(&printer);
+}
+
+void TabWidget::printPreview() const
+{
+ // Create a printer.
+ QPrinter printer;
+
+ // Set the resolution to be 300 dpi.
+ printer.setResolution(300);
+
+ // Create a print preview dialog.
+ QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
+
+ // Generate the print preview.
+ connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
+
+ // Display the dialog.
+ printPreviewDialog.exec();
+}
+
+void TabWidget::printWebpage(QPrinter *printerPointer) const
+{
+ // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
+ QEventLoop eventLoop;
+
+ // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
+ // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
+ currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
+ {
+ // Instruct the compiler to ignore the unused parameter.
+ (void) printSuccess;
+
+ // Quit the loop.
+ eventLoop.quit();
+ });
+
+ // Execute the loop.
+ eventLoop.exec();
+}
+
+void TabWidget::refresh() const
+{
+ // Reload the website.
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::setTabBarVisible(const bool visible) const
+{
+ // Set the tab bar visibility.
+ tabWidgetPointer->tabBar()->setVisible(visible);
+}
+
+void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
+{
+ // Instantiate the save dialog.
+ SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
+
+ // Connect the save button.
+ connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
+
+ // Show the dialog.
+ saveDialogPointer->show();
+}
+
+void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
+{
+ // Get the download location.
+ QString downloadDirectory = Settings::downloadLocation();
+
+ // Resolve the system download directory if specified.
+ if (downloadDirectory == QStringLiteral("System Download Directory"))
+ downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+
+ // Create a save file dialog.
+ QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
+
+ // Tell the dialog to use a save button.
+ saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
+
+ // Populate the file name from the download item pointer.
+ saveFileDialogPointer->selectFile(suggestedFileName);
+
+ // Prevent interaction with the parent window while the dialog is open.
+ saveFileDialogPointer->setWindowModality(Qt::WindowModal);
+
+ // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
+ auto saveFile = [saveFileDialogPointer, &downloadUrl] () {
+ // Get the save location. The dialog box should only allow the selecting of one file location.
+ QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
+
+ // Create a file copy job. `-1` creates the file with default permissions.
+ KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
+
+ // Set the download job to display any error messages.
+ fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
+
+ // Start the download.
+ fileCopyJobPointer->start();
+ };
+
+ // Handle clicks on the save button.
+ connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
+
+ // Show the dialog.
+ saveFileDialogPointer->show();
+}
+
+void TabWidget::toggleDomStorage() const
+{
+ // Toggle DOM storage.
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+
+ // Update the DOM storage action.
+ emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+
+ // Reload the website.
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleJavaScript() const
+{
+ // Toggle JavaScript.
+ currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+
+ // Update the JavaScript action.
+ emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+
+ // Reload the website.
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleLocalStorage()
+{
+ // Toggle local storeage.
+ currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
+
+ // Update the local storage action.
+ emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+
+ // Reload the website.
+ currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::updateUiWithTabSettings()
+{
+ // Update the current WebEngine pointers.
+ currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
+ currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
+ currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
+ currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
+ currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
+ currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
+
+ // Clear the URL line edit focus.
+ emit clearUrlLineEditFocus();
+
+ // Update the UI.
+ emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+ emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+ emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+ emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+ emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+ emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
+ emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
+ emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
+}
+
+void TabWidget::updateUrl(const QUrl &url) const
+{
+ // Update the URL line edit.
+ emit updateUrlLineEdit(url);
+
+ // Update the status of the forward and back buttons.
+ emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
+ emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
+
+ // Reapply the zoom factor. This is a bug in QWebEngineView that resets the zoom with every load. <https://redmine.stoutner.com/issues/799>
+ currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
+}