]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blobdiff - src/widgets/TabWidget.cpp
Enable downloading of files that require login cookies. https://redmine.stoutner...
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
index bd62f83beaa521343768bb9355fa3922359cd1f3..4edb304d0db6c45ed5d757a6d4a5f6cd20ad5596 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2022 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
  *
 #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>
+#include <KNotification>
 
 // Qt toolkit headers.
 #include <QAction>
 #include <QPrinter>
 
 // Initialize the public static variables.
-QString TabWidget::webEngineDefaultUserAgent = QStringLiteral("");
+QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
 
 // Construct the class.
 TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
 {
+    // Instantiate the user agent helper.
+    userAgentHelperPointer = new UserAgentHelper();
+
     // Instantiate the UIs.
     Ui::TabWidget tabWidgetUi;
     Ui::AddTabWidget addTabWidgetUi;
@@ -112,14 +115,14 @@ void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *w
     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(".")))
+    if (!cookie.domain().startsWith(QLatin1String(".")))
     {
         // Populate the URL.
         url.setHost(cookie.domain());
-        url.setScheme(QStringLiteral("https"));
+        url.setScheme(QLatin1String("https"));
 
         // Clear the domain from the cookie.
-        cookie.setDomain(QStringLiteral(""));
+        cookie.setDomain(QLatin1String(""));
     }
 
     // Add the cookie to the store.
@@ -153,7 +156,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
     tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
 
     // Create an off-the-record profile (the default when no profile name is specified).
-    QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
+    QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));
 
     // Create a WebEngine page.
     QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
@@ -332,7 +335,7 @@ PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const
     webEngineProfilePointer->setSpellCheckEnabled(true);
 
     // Set the spell check language.
-    webEngineProfilePointer->setSpellCheckLanguages({QStringLiteral("en_US")});
+    webEngineProfilePointer->setSpellCheckLanguages({QLatin1String("en_US")});
 
     // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
     privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
@@ -454,7 +457,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe
         // Set the DOM storage status.
         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
         {
-            // Set the default DOM storage status.
+            // Set the default DOM storage status.  QWebEngineSettings confusingly calls this local storage.
             case (DomainsDatabase::SYSTEM_DEFAULT):
             {
                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
@@ -462,7 +465,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe
                 break;
             }
 
-            // Disable DOM storage.
+            // Disable DOM storage.  QWebEngineSettings confusingly calls this local storage.
             case (DomainsDatabase::DISABLED):
             {
                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
@@ -470,7 +473,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe
                 break;
             }
 
-            // Enable DOM storage.
+            // Enable DOM storage.  QWebEngineSettings confusingly calls this local storage.
             case (DomainsDatabase::ENABLED):
             {
                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
@@ -500,7 +503,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe
     else  // The hostname does not have domain settings.
     {
         // Reset the domain settings name.
-        currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
+        currentPrivacyWebEngineViewPointer->domainSettingsName = QLatin1String("");
 
         // Set the JavaScript status.
         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
@@ -522,7 +525,7 @@ void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWe
     }
 
     // Update the UI.
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
@@ -558,7 +561,7 @@ void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
     userAgentName.remove('&');
 
     // Apply the user agent.
-    currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
+    currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
 
     // Update the user agent actions.
     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
@@ -853,59 +856,159 @@ void TabWidget::setTabBarVisible(const bool visible) const
     tabWidgetPointer->tabBar()->setVisible(visible);
 }
 
-void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
+void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
 {
-    // Instantiate the save dialog.
-    SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
+    // Get the download attributes.
+    QUrl downloadUrl = webEngineDownloadItemPointer->url();
+    QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
+    QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
+    int totalBytes = webEngineDownloadItemPointer->totalBytes();
+
+    // Check to see if local storage (cookies) is enabled.
+    if (currentPrivacyWebEngineViewPointer->localStorageEnabled)  // Local storage (cookies) is enabled.  Use WebEngine's downloader.
+    {
+        // Instantiate the save dialog.
+        SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
 
-    // Connect the save button.
-    connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
+        // Display the save dialog.
+        int saveDialogResult = saveDialogPointer->exec();
 
-    // Show the dialog.
-    saveDialogPointer->show();
-}
+        // Process the save dialog results.
+        if (saveDialogResult == QDialog::Accepted)  // Save was selected.
+        {
+            // Get the download directory.
+            QString downloadDirectory = Settings::downloadLocation();
 
-void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
-{
-    // Get the download location.
-    QString downloadDirectory = Settings::downloadLocation();
+            // 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 == QStringLiteral("System Download Directory"))
-        downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+            // Display a save file dialog.
+            QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
 
-    // Create a save file dialog.
-    QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
+            // 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);
 
-    // Tell the dialog to use a save button.
-    saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
+                // Get the canonical save path and file name.
+                QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
+                QString saveFileName = saveFilePathFileInfo.fileName();
 
-    // Populate the file name from the download item pointer.
-    saveFileDialogPointer->selectFile(suggestedFileName);
+                // Set the download directory and file name.
+                webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
+                webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
 
-    // Prevent interaction with the parent window while the dialog is open.
-    saveFileDialogPointer->setWindowModality(Qt::WindowModal);
+                // Create a file download notification.
+                KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
 
-    // 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);
+                // Set the notification title.
+                fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
 
-        // 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 notification text.
+                fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
 
-        // Set the download job to display any error messages.
-        fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
+                // Set the notification icon.
+                fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
 
-        // Start the download.
-        fileCopyJobPointer->start();
-    };
+                // Set the action list cancel button.
+                fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
 
-    // Handle clicks on the save button.
-    connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
+                // Set the notification to display indefinitely.
+                fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
 
-    // Show the dialog.
-    saveFileDialogPointer->show();
+                // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
+                fileDownloadNotificationPointer->setAutoDelete(false);
+
+                // Display the notification.
+                fileDownloadNotificationPointer->sendEvent();
+
+                // Handle clicks on the cancel button.
+                connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
+                {
+                    // Cancel the download.
+                    webEngineDownloadItemPointer->cancel();
+
+                    // Create a file download notification.
+                    KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
+
+                    // 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 notification icon.
+                    canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
+
+                    // Display the notification.
+                    canceledDownloadNotificationPointer->sendEvent();
+                });
+
+                // Update the notification when the download progresses.
+                connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
+                {
+                    // Calculate the download percentage.
+                    int downloadPercentage = 100 * bytesReceived / totalBytes;
+
+                    // Set the new text.  Total bytes will be 0 if the download size is unknown.
+                    if (totalBytes > 0)
+                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
+                                                                    bytesReceived, totalBytes));
+                    else
+                        fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, bytesReceived));
+
+                    // Display the updated notification.
+                    fileDownloadNotificationPointer->update();
+                });
+
+                // 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)});
+
+                    // Remove the actions from the notification.
+                    fileDownloadNotificationPointer->setActions(QStringList());
+
+                    // Set the notification to disappear after a timeout.
+                    fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
+
+                    // Display the updated notification.
+                    fileDownloadNotificationPointer->update();
+                });
+
+                // Start the download.
+                webEngineDownloadItemPointer->accept();
+            }
+            else  // The file save path is not populated.
+            {
+                // Cancel the download.
+                webEngineDownloadItemPointer->cancel();
+            }
+        }
+        else  // Cancel was selected.
+        {
+            // Cancel the download.
+            webEngineDownloadItemPointer->cancel();
+        }
+    }
+    else  // 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.
+    {
+        // Instantiate the save dialog.  `true` instructs it to use the native downloader
+        SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
+
+        // Connect the save button.
+        connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &)));
+
+        // Show the dialog.
+        saveDialogPointer->show();
+    }
 }
 
 void TabWidget::toggleDomStorage() const
@@ -929,7 +1032,7 @@ void TabWidget::toggleFindCaseSensitive(const QString &text)
     wipingCurrentFindTextSelection = true;
 
     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
-    findText(QStringLiteral(""));
+    findText(QLatin1String(""));
 
     // Update the find text.
     findText(text);
@@ -984,7 +1087,7 @@ void TabWidget::updateUiWithTabSettings()
 
     // Update the URL.
     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
-    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
+    emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
 
     // Update the find text.
@@ -997,3 +1100,47 @@ void TabWidget::updateUiWithTabSettings()
     else
         emit hideProgressBar();
 }
+
+void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
+{
+    // Get the download directory.
+    QString downloadDirectory = Settings::downloadLocation();
+
+    // Resolve the system download directory if specified.
+    if (downloadDirectory == QLatin1String("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();
+}