]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blobdiff - src/widgets/TabWidget.cpp
Block all CSP requests. https://redmine.stoutner.com/issues/1193
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
index e19244e59568a5df0d4089d5e7f3a7e9f117aab7..ce8978b6ca46f11016090175d137f1b22af01667 100644 (file)
@@ -1,23 +1,24 @@
-/*
- * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
+/* SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2022-2025 Soren Stoutner <soren@stoutner.com>
  *
- * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
+ * 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.
+ * This program 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.
+ * This program 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/>.
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
 // Application headers.
+#include "DevToolsWebEngineView.h"
 #include "TabWidget.h"
 #include "Settings.h"
 #include "ui_AddTabWidget.h"
@@ -127,11 +128,16 @@ TabWidget::~TabWidget()
     // Manually delete each WebEngine page.
     for (int i = 0; i < numberOfTabs; ++i)
     {
-        // Get the privacy WebEngine view.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
+        // Get the tab splitter widget.
+        QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(i);
 
-        // Deletion the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+        // Get the WebEngine views.
+        PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
+        DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
+
+        // Deletion the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
+        delete devToolsWebEngineViewPointer->page();
     }
 }
 
@@ -171,25 +177,55 @@ void TabWidget::addFirstTab()
     qTabWidgetPointer->currentWidget()->setFocus();
 }
 
-PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
+PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool adjacent, const bool backgroundTab, const QString urlString)
 {
-    // Create a privacy WebEngine view.
+    // Create a splitter widget.
+    QSplitter *splitterPointer = new QSplitter();
+
+    // Set the splitter to be vertical.
+    splitterPointer->setOrientation(Qt::Vertical);
+
+    // Set the splitter handle size.
+    splitterPointer->setHandleWidth(5);
+
+    // Create the WebEngines.
     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = new DevToolsWebEngineView();
+
+    // Add the WebEngines to the splitter.
+    splitterPointer->addWidget(privacyWebEngineViewPointer);
+    splitterPointer->addWidget(devToolsWebEngineViewPointer);
+
+    // Initialize the new tab index.
+    int newTabIndex = 0;
 
     // Add a new tab.
-    int newTabIndex = qTabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
+    if (adjacent)  // Add the new tab adjacent to the current tab.
+        newTabIndex = qTabWidgetPointer->insertTab((qTabWidgetPointer->currentIndex() + 1), splitterPointer, i18nc("New tab label.", "New Tab"));
+    else  // Add the new tab at the end of the list.
+        newTabIndex = qTabWidgetPointer->addTab(splitterPointer, i18nc("New tab label.", "New Tab"));
 
     // Set the default tab icon.
     qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
 
-    // Get handles for the WebEngine page and profile.
+    // Get handles for the WebEngine components.
     QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
     QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
-
-    // Get handles for the web engine elements.
     QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
     QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
 
+    // Set the development tools WebEngine.
+    webEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
+
+    // Initially hide the development tools WebEngine.
+    devToolsWebEngineViewPointer->setVisible(false);
+
+    // Initially disable the development tools WebEngine.
+    webEnginePagePointer->setDevToolsPage(nullptr);
+
+    // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
+    devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
+
     // Update the URL line edit when the URL changes.
     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
     {
@@ -197,33 +233,33 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
         {
             // Update the URL line edit.
-            emit updateUrlLineEdit(newUrl);
+            Q_EMIT updateUrlLineEdit(newUrl);
 
             // Update the status of the forward and back buttons.
-            emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
-            emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
+            Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
+            Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
         }
     });
 
     // Update the title when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, splitterPointer] (const QString &title)
     {
         // Get the index for this tab.
-        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
 
         // Update the title for this tab.
         qTabWidgetPointer->setTabText(tabIndex, title);
 
         // Update the window title if this is the current tab.
         if (tabIndex == qTabWidgetPointer->currentIndex())
-            emit updateWindowTitle(title);
+            Q_EMIT updateWindowTitle(title);
     });
 
     // Connect the loading favorite icon movie to the tab icon.
-    connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, privacyWebEngineViewPointer]
+    connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, splitterPointer, privacyWebEngineViewPointer]
     {
         // Get the index for this tab.
-        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
 
         // Display the loading favorite icon if this tab is loading.
         if (privacyWebEngineViewPointer->isLoading)
@@ -231,7 +267,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
     });
 
     // Update the icon when it changes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, splitterPointer, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
     {
         // Store the favorite icon in the privacy web engine view.
         if (newFavoriteIcon.isNull())
@@ -240,7 +276,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
             privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;
 
         // Get the index for this tab.
-        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
 
         // Update the icon for this tab.
         if (newFavoriteIcon.isNull())
@@ -260,7 +296,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
 
         // Show the progress bar if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
-            emit showProgressBar(0);
+            Q_EMIT showProgressBar(0);
 
         // Start the loading favorite icon movie.
         loadingFavoriteIconMoviePointer->start();
@@ -274,11 +310,11 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
 
         // Update the progress bar if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
-            emit showProgressBar(progress);
+            Q_EMIT showProgressBar(progress);
     });
 
     // Update the progress bar when a load finishes.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, privacyWebEngineViewPointer] ()
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, splitterPointer, privacyWebEngineViewPointer] ()
     {
         // Set the privacy web engine view to be not loading.
         privacyWebEngineViewPointer->isLoading = false;
@@ -288,10 +324,10 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
 
         // Hide the progress bar if this is the current tab.
         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
-            emit hideProgressBar();
+            Q_EMIT hideProgressBar();
 
         // Get the index for this tab.
-        int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
+        int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
 
         // Display the current favorite icon
         qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
@@ -306,7 +342,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
         for (int i = 0; i < numberOfTabs; i++)
         {
             // Get the privacy WebEngine view for the tab.
-            PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView*>(qTabWidgetPointer->widget(i));
+            PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
 
             // Check to see if it is currently loading.  If at least one tab is loading, this flag will end up being marked `false` when the for loop has finished.
             if (privacyWebEngineViewPointer->isLoading)
@@ -344,12 +380,12 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
         }
     });
 
-    // Update the zoom factor when changed by CTRL-Scrolling.  This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
-    connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
+    // Update the zoom actions when they are changed.
+    connect(webEnginePagePointer, &QWebEnginePage::zoomFactorChanged, [webEnginePagePointer, this] (const qreal newZoomFactor)
     {
-        // Only update the zoom factor action text if this is the current tab.
+        // Only update the zoom actions if this is the current tab.
         if (webEnginePagePointer == currentWebEnginePagePointer)
-            emit updateZoomFactorAction(webEnginePagePointer->zoomFactor());
+            Q_EMIT updateZoomActions(newZoomFactor);
     });
 
     // Display find text results.
@@ -362,13 +398,13 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
 
     // Handle file downloads.
-    connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
+    connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadRequest *)), this, SLOT(showSaveDialog(QWebEngineDownloadRequest *)));
 
     // 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("")))
+        if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QUrl(QLatin1String(""))))
         {
             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
 
@@ -415,12 +451,23 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
     // Plugins must be enabled for the PDF viewer to work.  <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
 
+    // Allow JavaScript to paste to (but not copy from) the clipboard, but only with user interaction.
+    webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true);
+
+    // Update the blocked requests action.
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::requestBlocked, [this, privacyWebEngineViewPointer] (const QVector<int> blockedRequestsVector)
+    {
+        // Update the blocked requests action if the specified privacy WebEngine view is the current privacy WebEngine view.
+        if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
+            Q_EMIT blockedRequestsUpdated(blockedRequestsVector);
+    });
+
     // Update the cookies action.
-    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
+    connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::numberOfCookiesChanged, [this, privacyWebEngineViewPointer] (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);
+            Q_EMIT cookiesChanged(numberOfCookies);
     });
 
     // Process cookie changes.
@@ -452,7 +499,11 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
 
     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
     if (removeUrlLineEditFocus)
-        emit clearUrlLineEditFocus();
+        Q_EMIT clearUrlLineEditFocus();
+
+    // Load the URL if it isn't blank.
+    if (urlString != QLatin1String(""))
+        privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));
 
     // Return the privacy WebEngine view pointer.
     return privacyWebEngineViewPointer;
@@ -472,7 +523,7 @@ void TabWidget::applyApplicationSettings()
     // Apply the spatial navigation settings to each WebEngine.
     for (int i = 0; i < numberOfTabs; ++i) {
         // Get the WebEngine view pointer.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
 
         // Apply the spatial navigation settings to each page.
         privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
@@ -482,7 +533,7 @@ void TabWidget::applyApplicationSettings()
     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
 
     // Emit the update search engine actions signal.
-    emit updateSearchEngineActions(Settings::searchEngine(), true);
+    Q_EMIT updateSearchEngineActions(Settings::searchEngine(), true);
 }
 
 void TabWidget::applyDomainSettingsAndReload()
@@ -493,10 +544,10 @@ void TabWidget::applyDomainSettingsAndReload()
     // Apply the domain settings to each WebEngine.
     for (int i = 0; i < numberOfTabs; ++i) {
         // Get the WebEngine view pointer.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
 
-        // Apply the spatial navigation settings to each page.
-        privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url().host(), true);
+        // Apply the domain settings settings to each page.  `false` indicates that history is not being navigated.
+        privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url(), false);
     }
 }
 
@@ -506,13 +557,13 @@ void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
     QString searchEngineName = searchEngineActionPointer->text();
 
     // Strip out any `&` characters.
-    searchEngineName.remove('&');
+    searchEngineName.remove(QLatin1Char('&'));
 
     // Store the search engine string.
     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
 
     // Update the search engine actions.
-    emit updateSearchEngineActions(searchEngineName, false);
+    Q_EMIT updateSearchEngineActions(searchEngineName, false);
 }
 
 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
@@ -521,22 +572,22 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
     QString userAgentName = userAgentActionPointer->text();
 
     // Strip out any `&` characters.
-    userAgentName.remove('&');
+    userAgentName.remove(QLatin1Char('&'));
 
     // Apply the user agent.
     currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
 
     // Update the user agent actions.
-    emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
+    Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
 
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
 }
 
-void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor) const
+void TabWidget::applyOnTheFlyZoomFactor(const double zoomFactorDouble) const
 {
     // Set the zoom factor.
-    currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
+    currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactorDouble);
 }
 
 void TabWidget::applySpellCheckLanguages() const
@@ -548,7 +599,7 @@ void TabWidget::applySpellCheckLanguages() const
     for (int i = 0; i < numberOfTabs; ++i)
     {
         // Get the WebEngine view pointer.
-        PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
+        PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
 
         // Get the WebEngine page pointer.
         QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
@@ -581,20 +632,29 @@ void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
 
 void TabWidget::deleteTab(const int tabIndex)
 {
-    // Get the privacy WebEngine view.
-    PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(tabIndex));
+    // Get the tab splitter widget.
+    QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(tabIndex);
+
+    // Get the WebEngine views.
+    PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
 
     // Process the tab delete according to the number of tabs.
     if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
     {
-        // Delete the tab.
+        // Remove the tab.
         qTabWidgetPointer->removeTab(tabIndex);
 
-        // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
+        // Delete the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
         delete privacyWebEngineViewPointer->page();
+        delete devToolsWebEngineViewPointer->page();
 
-        // Delete the privacy WebEngine view.
+        // Delete the WebEngine views.
         delete privacyWebEngineViewPointer;
+        delete devToolsWebEngineViewPointer;
+
+        // Delete the tab splitter widget.
+        delete tabSplitterWidgetPointer;
     }
     else  // There is only one tab.
     {
@@ -645,7 +705,7 @@ void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
         currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
 
         // Update the UI.
-        emit updateFindTextResults(findTextResult);
+        Q_EMIT updateFindTextResults(findTextResult);
     }
 }
 
@@ -658,7 +718,7 @@ void TabWidget::forward() const
 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
 {
     // Make it so.
-    emit fullScreenRequested(fullScreenRequest.toggleOn());
+    Q_EMIT fullScreenRequested(fullScreenRequest.toggleOn());
 
     // Accept the request.
     fullScreenRequest.accept();
@@ -670,6 +730,30 @@ std::list<QNetworkCookie>* TabWidget::getCookieList() const
     return currentPrivacyWebEngineViewPointer->cookieListPointer;
 }
 
+QIcon TabWidget::getCurrentTabFavoritIcon() const
+{
+    // Return the current Privacy WebEngine favorite icon.
+    return currentPrivacyWebEngineViewPointer->favoriteIcon;
+}
+
+QString TabWidget::getCurrentTabTitle() const
+{
+    // Return the current Privacy WebEngine title.
+    return currentPrivacyWebEngineViewPointer->title();
+}
+
+QString TabWidget::getCurrentTabUrl() const
+{
+    // Return the current Privacy WebEngine URL as a string.
+    return currentPrivacyWebEngineViewPointer->url().toString();
+}
+
+QString TabWidget::getCurrentUserAgent() const
+{
+    // Return the current WebEngine user agent.
+    return currentWebEngineProfilePointer->httpUserAgent();
+}
+
 QString& TabWidget::getDomainSettingsName() const
 {
     // Return the domain settings name.
@@ -715,18 +799,18 @@ void TabWidget::loadInitialWebsite()
 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.
+    if (url.startsWith(QLatin1String("file://")) || url.startsWith(QLatin1String("view-source:")))  // The text is likely a file or view source URL.
     {
         // Load the URL.
         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
     }
-    else if (url.contains("."))  // The text is likely a URL.
+    else if (url.contains(QLatin1String(".")))  // The text is likely a URL.
     {
         // Check if the URL does not start with a valid protocol.
-        if (!url.startsWith("http"))
+        if (!url.startsWith(QLatin1String("http")))
         {
             // Add `https://` to the beginning of the URL.
-            url = "https://" + url;
+            url = QLatin1String("https://") + url;
         }
 
         // Load the URL.
@@ -745,7 +829,7 @@ void TabWidget::mouseBack() const
     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
     {
         // Clear the URL line edit focus.
-        emit clearUrlLineEditFocus();
+        Q_EMIT clearUrlLineEditFocus();
 
         // Go back.
         currentPrivacyWebEngineViewPointer->back();
@@ -758,7 +842,7 @@ void TabWidget::mouseForward() const
     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
     {
         // Clear the URL line edit focus.
-        emit clearUrlLineEditFocus();
+        Q_EMIT clearUrlLineEditFocus();
 
         // Go forward.
         currentPrivacyWebEngineViewPointer->forward();
@@ -768,7 +852,7 @@ void TabWidget::mouseForward() const
 void TabWidget::pageLinkHovered(const QString &linkUrl) const
 {
     // Emit a signal so that the browser window can update the status bar.
-    emit linkHovered(linkUrl);
+    Q_EMIT linkHovered(linkUrl);
 }
 
 void TabWidget::stopLoadingFavoriteIconMovie() const
@@ -780,59 +864,57 @@ void TabWidget::stopLoadingFavoriteIconMovie() const
 void TabWidget::print() const
 {
     // Create a printer.
-    QPrinter printer;
+    QPrinter *printerPointer = new QPrinter();
 
     // Set the resolution to be 300 dpi.
-    printer.setResolution(300);
+    printerPointer->setResolution(300);
 
     // Create a printer dialog.
-    QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
+    QPrintDialog printDialog(printerPointer, currentPrivacyWebEngineViewPointer);
 
     // Display the dialog and print the page if instructed.
     if (printDialog.exec() == QDialog::Accepted)
-        printWebpage(&printer);
+        currentPrivacyWebEngineViewPointer->print(printerPointer);
 }
 
 void TabWidget::printPreview() const
 {
     // Create a printer.
-    QPrinter printer;
+    QPrinter *printerPointer = new QPrinter();
 
     // Set the resolution to be 300 dpi.
-    printer.setResolution(300);
+    printerPointer->setResolution(300);
 
     // Create a print preview dialog.
-    QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
+    QPrintPreviewDialog printPreviewDialog(printerPointer, currentPrivacyWebEngineViewPointer);
 
     // Generate the print preview.
-    connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
+    auto generatePrintPreview = [this, printerPointer](){
+        // Create an event loop.  The print preview must be generated in a loop or nothing is displayed.
+        QEventLoop eventLoop;
 
-    // Display the dialog.
-    printPreviewDialog.exec();
-}
+        // Quit the event loop once the print preview has been generated.
+        connect(currentPrivacyWebEngineViewPointer, &QWebEngineView::printFinished, &eventLoop, &QEventLoop::quit);
 
-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;
+        // Generate the print preview for the current webpage.
+        currentPrivacyWebEngineViewPointer->print(printerPointer);
 
-    // 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;
+        // Execute the loop.
+        eventLoop.exec();
+    };
 
-        // Quit the loop.
-        eventLoop.quit();
-    });
+    // Generate the preview.
+    connect(&printPreviewDialog, &QPrintPreviewDialog::paintRequested, this, generatePrintPreview);
 
-    // Execute the loop.
-    eventLoop.exec();
+    // Display the dialog.
+    printPreviewDialog.exec();
 }
 
 void TabWidget::refresh() const
 {
+    // Reset the HTTP authentication dialog counter.
+    currentPrivacyWebEngineViewPointer->httpAuthenticationDialogsDisplayed = 0;
+
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
 }
@@ -843,6 +925,35 @@ void TabWidget::reloadAndBypassCache() const
     currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
 }
 
+void TabWidget::saveArchive()
+{
+    // Get the suggested file name.
+    QString suggestedFileName = currentPrivacyWebEngineViewPointer->title() + QLatin1String(".mht");
+
+    // Get the download directory.
+    QString downloadDirectory = Settings::downloadDirectory();
+
+    // Resolve the system download directory if specified.
+    if (downloadDirectory == QLatin1String("System Download Directory"))
+        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+
+    // Get a file path from the file picker.
+    QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
+
+    // Save the webpage as an archive if the file save path is populated.
+    if (!saveFilePath.isEmpty())
+    {
+        // Update the download directory if specified.
+        if (Settings::autoUpateDownloadDirectory())
+            updateDownloadDirectory(saveFilePath);
+
+        // Set the saving archive flag.  Otherwise, a second download tries to run.
+        savingArchive = true;
+
+        // Save the archive.
+        currentWebEnginePagePointer->save(saveFilePath);
+    }
+}
 
 void TabWidget::setTabBarVisible(const bool visible) const
 {
@@ -850,164 +961,232 @@ void TabWidget::setTabBarVisible(const bool visible) const
     qTabWidgetPointer->tabBar()->setVisible(visible);
 }
 
-void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
+void TabWidget::showSaveDialog(QWebEngineDownloadRequest *webEngineDownloadRequestPointer)
 {
-    // Get the download attributes.
-    QUrl downloadUrl = webEngineDownloadItemPointer->url();
-    QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
-    QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
-    int totalBytes = webEngineDownloadItemPointer->totalBytes();
-
-    // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
-    if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
+    // Only show the save dialog if an archive is not currently being saved.  Otherwise, two save dialogs will be shown.
+    if (!savingArchive)
     {
-        // Instantiate the save dialog.
-        SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
+        // Get the download attributes.
+        QUrl downloadUrl = webEngineDownloadRequestPointer->url();
+        QString mimeTypeString = webEngineDownloadRequestPointer->mimeType();
+        QString suggestedFileName = webEngineDownloadRequestPointer->suggestedFileName();
+        int totalBytes = webEngineDownloadRequestPointer->totalBytes();
+
+        // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
+        if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
+        {
+            // Instantiate the save dialog.
+            SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes);
 
-        // Display the save dialog.
-        int saveDialogResult = saveDialogPointer->exec();
+            // Display the save dialog.
+            int saveDialogResult = saveDialogPointer->exec();
 
-        // Process the save dialog results.
-        if (saveDialogResult == QDialog::Accepted)  // Save was selected.
-        {
-            // Get the download directory.
-            QString downloadDirectory = Settings::downloadLocation();
+            // Process the save dialog results.
+            if (saveDialogResult == QDialog::Accepted)  // Save was selected.
+            {
+                // Get the download directory.
+                QString downloadDirectory = Settings::downloadDirectory();
 
-            // Resolve the system download directory if specified.
-            if (downloadDirectory == QLatin1String("System Download Directory"))
-                downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+                // Resolve the system download directory if specified.
+                if (downloadDirectory == QLatin1String("System Download Directory"))
+                    downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
 
-            // Get a file path from the file picker.
-            QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
+                // Get a file path from the file picker.
+                QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
 
-            // Process the save file path.
-            if (!saveFilePath.isEmpty())  // The file save path is populated.
-            {
-                // Create a save file path file info.
-                QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
+                // Process the save file path.
+                if (!saveFilePath.isEmpty())  // The file save path is populated.
+                {
+                    // Update the download directory if specified.
+                    if (Settings::autoUpateDownloadDirectory())
+                        updateDownloadDirectory(saveFilePath);
 
-                // Get the canonical save path and file name.
-                QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
-                QString saveFileName = saveFilePathFileInfo.fileName();
+                    // Create a save file path file info.
+                    QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
 
-                // Set the download directory and file name.
-                webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
-                webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
+                    // Get the canonical save path and file name.
+                    QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
+                    QString saveFileName = saveFilePathFileInfo.fileName();
 
-                // Create a file download notification.
-                KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
+                    // Set the download directory and file name.
+                    webEngineDownloadRequestPointer->setDownloadDirectory(absoluteSavePath);
+                    webEngineDownloadRequestPointer->setDownloadFileName(saveFileName);
 
-                // Set the notification title.
-                fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
+                    // Create a file download notification.
+                    KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
 
-                // Set the notification text.
-                fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
+                    // Set the notification title.
+                    fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
 
-                // Get the download icon from the theme.
-                QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
+                    // Set the notification text.
+                    fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
 
-                // Set the notification icon.
-                fileDownloadNotificationPointer->setIconName(downloadIcon.name());
+                    // Get the download icon from the theme.
+                    QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
 
-                // Set the action list cancel button.
-                fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
+                    // Set the notification icon.
+                    fileDownloadNotificationPointer->setIconName(downloadIcon.name());
 
-                // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
-                fileDownloadNotificationPointer->setAutoDelete(false);
+                    // Add the cancel action.
+                    KNotificationAction *cancelActionPointer = fileDownloadNotificationPointer->addDefaultAction(i18nc("Download notification action","Cancel"));
 
-                // Display the notification.
-                fileDownloadNotificationPointer->sendEvent();
+                    // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
+                    fileDownloadNotificationPointer->setAutoDelete(false);
 
-                // Handle clicks on the cancel button.
-                connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
-                {
-                    // Cancel the download.
-                    webEngineDownloadItemPointer->cancel();
+                    // Handle clicks on the cancel action.
+                    connect(cancelActionPointer, &KNotificationAction::activated, [webEngineDownloadRequestPointer, saveFileName] ()
+                    {
+                        // Cancel the download.
+                        webEngineDownloadRequestPointer->cancel();
 
-                    // Create a file download notification.
-                    KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
+                        // Create a file download notification.
+                        KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
 
-                    // Set the notification title.
-                    canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
+                        // Set the notification title.
+                        canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
 
-                    // Set the new text.
-                    canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
+                        // Set the new text.
+                        canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
 
-                    // Set the notification icon.
-                    canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
+                        // Set the notification icon.
+                        canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
 
-                    // Display the notification.
-                    canceledDownloadNotificationPointer->sendEvent();
-                });
+                        // Display the notification.
+                        canceledDownloadNotificationPointer->sendEvent();
+                    });
 
-                // Update the notification when the download progresses.
-                connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
-                {
-                    // Set the new text.  Total bytes will be 0 if the download size is unknown.
-                    if (totalBytes > 0)
+                    // Update the notification when the download progresses.
+                    connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::receivedBytesChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName] ()
                     {
-                        // Calculate the download percentage.
-                        int downloadPercentage = 100 * bytesReceived / totalBytes;
-
-                        // Set the file download notification text.
-                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
-                                                                    bytesReceived, totalBytes));
-                    }
-                    else
+                        // Get the download request information.
+                        qint64 receivedBytes = webEngineDownloadRequestPointer->receivedBytes();
+                        qint64 totalBytes = webEngineDownloadRequestPointer->totalBytes();
+
+                        // Set the new text.  Total bytes will be 0 if the download size is unknown.
+                        if (totalBytes > 0)
+                        {
+                            // Calculate the download percentage.
+                            int downloadPercentage = 100 * receivedBytes / totalBytes;
+
+                            // Set the file download notification text.
+                            fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1%% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
+                                                                           receivedBytes, totalBytes));
+                        }
+                        else
+                        {
+                            // Set the file download notification text.
+                            fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, receivedBytes));
+                        }
+
+                        // Display the updated notification.
+                        fileDownloadNotificationPointer->sendEvent();
+                    });
+
+                    // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
+                    connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::isFinishedChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName,
+                            saveFilePath] ()
                     {
-                        // Set the file download notification text.
-                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, bytesReceived));
-                    }
-
-                    // Display the updated notification.
-                    fileDownloadNotificationPointer->update();
-                });
+                        // Update the notification if the download is finished.
+                        if (webEngineDownloadRequestPointer->isFinished())
+                        {
+                            // Set the new text.
+                            fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
 
-                // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
-                connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
-                {
-                    // Set the new text.
-                    fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
+                            // Set the URL so the file options will be displayed.
+                            fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
 
-                    // Set the URL so the file options will be displayed.
-                    fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
+                            // Remove the actions from the notification.
+                            fileDownloadNotificationPointer->clearActions();
 
-                    // Remove the actions from the notification.
-                    fileDownloadNotificationPointer->setActions(QStringList());
+                            // Set the notification to disappear after a timeout.
+                            fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
 
-                    // Set the notification to disappear after a timeout.
-                    fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
+                            // Display the updated notification.
+                            fileDownloadNotificationPointer->sendEvent();
+                        }
+                    });
 
-                    // Display the updated notification.
-                    fileDownloadNotificationPointer->update();
-                });
+                    // Display the notification.
+                    fileDownloadNotificationPointer->sendEvent();
 
-                // Start the download.
-                webEngineDownloadItemPointer->accept();
+                    // Start the download.
+                    webEngineDownloadRequestPointer->accept();
+                }
+                else  // The file save path is not populated.
+                {
+                    // Cancel the download.
+                    webEngineDownloadRequestPointer->cancel();
+                }
             }
-            else  // The file save path is not populated.
+            else  // Cancel was selected.
             {
                 // Cancel the download.
-                webEngineDownloadItemPointer->cancel();
+                webEngineDownloadRequestPointer->cancel();
             }
         }
-        else  // Cancel was selected.
+        else  // KDE is running and local storage (cookies) is disabled.  Use KDE's native downloader.
+            // This must use the show command to launch a separate dialog which cancels WebEngine's automatic background download of the file to a temporary location.
         {
-            // Cancel the download.
-            webEngineDownloadItemPointer->cancel();
+            // Instantiate the save dialog.  `true` instructs it to use the native downloader
+            SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
+
+            // Connect the save button.
+            connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
+
+            // Show the dialog.
+            saveDialogPointer->show();
         }
     }
-    else  // KDE is running and local storage (cookies) is disabled.  Use KDE's native downloader.
-          // This must use the show command to launch a separate dialog which cancels WebEngine's automatic background download of the file to a temporary location.
+
+    // Reset the saving archive flag.
+    savingArchive = false;
+}
+
+void TabWidget::stop() const
+{
+    // Stop the loading of the current privacy WebEngine.
+    currentPrivacyWebEngineViewPointer->stop();
+}
+
+void TabWidget::storeCurrentUrlText(const QString &urlText) const
+{
+    // Store the current URL text in the privacy WebEngine view.
+    currentPrivacyWebEngineViewPointer->currentUrlText = urlText;
+}
+
+void TabWidget::toggleDeveloperTools(const bool enabled) const
+{
+    // Get handles for the current tab widgets.
+    QSplitter *splitterPointer = qobject_cast<QSplitter*>(qTabWidgetPointer->currentWidget());
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = splitterPointer->findChild<DevToolsWebEngineView *>();
+
+    if (enabled)
+    {
+        // Set the zoom factor on the development tools WebEngine.
+        devToolsWebEngineViewPointer->setZoomFactor(currentWebEnginePagePointer->zoomFactor());
+
+        // Enable the development tools.
+        currentWebEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
+
+        // Enable JavaScript on the development tools WebEngine.
+        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
+
+        // Display the developer tools.
+        devToolsWebEngineViewPointer->setVisible(true);
+
+        // Split the visible space equally between the main WebEngine and the developer tools WebEngine.
+        splitterPointer->setSizes(QList<int>({1, 1}));
+    }
+    else
     {
-        // Instantiate the save dialog.  `true` instructs it to use the native downloader
-        SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
+        // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
+        devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
 
-        // Connect the save button.
-        connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
+        // Disable the development tools.
+        currentWebEnginePagePointer->setDevToolsPage(nullptr);
 
-        // Show the dialog.
-        saveDialogPointer->show();
+        // Hide the developer tools.
+        devToolsWebEngineViewPointer->setVisible(false);
     }
 }
 
@@ -1017,7 +1196,34 @@ void TabWidget::toggleDomStorage() const
     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
 
     // Update the DOM storage action.
-    emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+    Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+
+    // Reload the website.
+    currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleEasyList() const
+{
+    // Toggle EasyList.
+    currentPrivacyWebEngineViewPointer->easyListEnabled = !currentPrivacyWebEngineViewPointer->easyListEnabled;
+
+    // Reload the website.
+    currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleEasyPrivacy() const
+{
+    // Toggle EasyPrivacy.
+    currentPrivacyWebEngineViewPointer->easyPrivacyEnabled = !currentPrivacyWebEngineViewPointer->easyPrivacyEnabled;
+
+    // Reload the website.
+    currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleFanboysAnnoyanceList() const
+{
+    // Toggle Fanboy's Annoyance List.
+    currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled = !currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled;
 
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
@@ -1044,7 +1250,7 @@ void TabWidget::toggleJavaScript() const
     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
 
     // Update the JavaScript action.
-    emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+    Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
 
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
@@ -1056,70 +1262,120 @@ void TabWidget::toggleLocalStorage()
     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
 
     // Update the local storage action.
-    emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+    Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+
+    // Reload the website.
+    currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::toggleUltraList() const
+{
+    // Toggle UltraList.
+    currentPrivacyWebEngineViewPointer->ultraListEnabled = !currentPrivacyWebEngineViewPointer->ultraListEnabled;
 
     // Reload the website.
     currentPrivacyWebEngineViewPointer->reload();
 }
 
+void TabWidget::toggleUltraPrivacy() const
+{
+    // Toggle UltraPrivacy.
+    currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled = !currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled;
+
+    // Reload the website.
+    currentPrivacyWebEngineViewPointer->reload();
+}
+
+void TabWidget::updateDownloadDirectory(QString newDownloadDirectory) const
+{
+    // Remove the file name from the save file path.
+    newDownloadDirectory.truncate(newDownloadDirectory.lastIndexOf(QLatin1Char('/')));
+
+    // Update the download location.
+    Settings::setDownloadDirectory(newDownloadDirectory);
+
+    // Get a handle for the KConfig skeleton.
+    KConfigSkeleton *kConfigSkeletonPointer = Settings::self();
+
+    // Write the settings to disk.
+    kConfigSkeletonPointer->save();
+}
+
 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
 {
     // Only update the UI if the signal was emitted from the current privacy WebEngine.
     if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
     {
         // Update the UI.
-        emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
-        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());
+        Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
+        Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
+        Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
+        Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
+        Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
+        Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
+        Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
+        Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+        Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+        Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+        Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+        Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
     }
 }
 
 void TabWidget::updateUiWithTabSettings()
 {
+    // Clear the URL line edit focus.
+    Q_EMIT clearUrlLineEditFocus();
+
     // Update the current WebEngine pointers.
-    currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
+    currentPrivacyWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<PrivacyWebEngineView *>();
     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
 
-    // Clear the URL line edit focus.
-    emit clearUrlLineEditFocus();
+    // Get a handle for the development tools WebEngine view.
+    DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
 
     // Update the actions.
-    emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
-    emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
-    emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
-    emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
-    emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
-    emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
-    emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
-    emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
-
-    // Update the URL.
-    emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
-    emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
+    Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
+    Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
+    Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
+    Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
+    Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
+    Q_EMIT blockedRequestsUpdated(currentPrivacyWebEngineViewPointer->blockedRequestsVector);
+    Q_EMIT cookiesChanged(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
+    Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
+    Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
+    Q_EMIT updateDeveloperToolsAction(devToolsWebEngineViewPointer->isVisible());
+    Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
+    Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
+    Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
+    Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
+    Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
+    Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
 
     // Update the find text.
-    emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
-    emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
+    Q_EMIT updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
+    Q_EMIT updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
 
     // Update the progress bar.
     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
-        emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
+        Q_EMIT showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
     else
-        emit hideProgressBar();
+        Q_EMIT hideProgressBar();
+
+    // Update the URL.
+    Q_EMIT updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
+    Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
+    Q_EMIT updateUrlLineEdit(QUrl(currentPrivacyWebEngineViewPointer->currentUrlText));
 }
 
 void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
 {
     // Get the download directory.
-    QString downloadDirectory = Settings::downloadLocation();
+    QString downloadDirectory = Settings::downloadDirectory();
 
     // Resolve the system download directory if specified.
     if (downloadDirectory == QLatin1String("System Download Directory"))
@@ -1138,11 +1394,15 @@ void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFile
     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] ()
+    auto saveFile = [saveFileDialogPointer, downloadUrl, this] ()
     {
         // Get the save location.  The dialog box should only allow the selecting of one file location.
         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
 
+        // Update the download directory if specified.
+        if (Settings::autoUpateDownloadDirectory())
+            updateDownloadDirectory(saveLocation.toLocalFile());
+
         // Create a file copy job.  `-1` creates the file with default permissions.
         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
 
@@ -1160,3 +1420,4 @@ void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFile
     // Show the dialog.
     saveFileDialogPointer->show();
 }
+