]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/widgets/TabWidget.cpp
Block all CSP requests. https://redmine.stoutner.com/issues/1193
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
1 /* SPDX-License-Identifier: GPL-3.0-or-later
2  * SPDX-FileCopyrightText: 2022-2025 Soren Stoutner <soren@stoutner.com>
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc/>.
5  *
6  * This program is free software: you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License along with
17  * this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19
20 // Application headers.
21 #include "DevToolsWebEngineView.h"
22 #include "TabWidget.h"
23 #include "Settings.h"
24 #include "ui_AddTabWidget.h"
25 #include "ui_TabWidget.h"
26 #include "databases/CookiesDatabase.h"
27 #include "dialogs/SaveDialog.h"
28 #include "filters/MouseEventFilter.h"
29 #include "helpers/SearchEngineHelper.h"
30 #include "windows/BrowserWindow.h"
31
32 // KDE Framework headers.
33 #include <KIO/FileCopyJob>
34 #include <KIO/JobUiDelegate>
35 #include <KNotification>
36
37 // Qt toolkit headers.
38 #include <QAction>
39 #include <QFileDialog>
40 #include <QGraphicsScene>
41 #include <QGraphicsView>
42 #include <QMessageBox>
43 #include <QPrintDialog>
44 #include <QPrintPreviewDialog>
45 #include <QPrinter>
46
47 // Initialize the public static variables.
48 QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
49
50 // Construct the class.
51 TabWidget::TabWidget(QWidget *windowPointer) : QWidget(windowPointer)
52 {
53     // Create a QProcess to check if KDE is running.
54     QProcess *checkIfRunningKdeQProcessPointer = new QProcess();
55
56     // Create an argument string list that contains `ksmserver` (KDE Session Manager).
57     QStringList argument = QStringList(QLatin1String("ksmserver"));
58
59     // Run `pidof` to check for the presence of `ksmserver`.
60     checkIfRunningKdeQProcessPointer->start(QLatin1String("pidof"), argument);
61
62     // Monitor any standard output.
63     connect(checkIfRunningKdeQProcessPointer, &QProcess::readyReadStandardOutput, [this]
64     {
65         // If there is any standard output, `ksmserver` is running.
66         isRunningKde = true;
67     });
68
69     // Instantiate the user agent helper.
70     userAgentHelperPointer = new UserAgentHelper();
71
72     // Instantiate the UIs.
73     Ui::TabWidget tabWidgetUi;
74     Ui::AddTabWidget addTabWidgetUi;
75
76     // Setup the main UI.
77     tabWidgetUi.setupUi(this);
78
79     // Get a handle for the tab widget.
80     qTabWidgetPointer = tabWidgetUi.tabWidget;
81
82     // Setup the add tab UI.
83     addTabWidgetUi.setupUi(qTabWidgetPointer);
84
85     // Get handles for the add tab widgets.
86     QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
87     QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
88
89     // Display the add tab widget.
90     qTabWidgetPointer->setCornerWidget(addTabWidgetPointer);
91
92     // Create the loading favorite icon movie.
93     loadingFavoriteIconMoviePointer = new QMovie();
94
95     // Set the loading favorite icon movie file name.
96     loadingFavoriteIconMoviePointer->setFileName(QStringLiteral(":/icons/loading.gif"));
97
98     // Stop the loading favorite icon movie if the window is destroyed.  Otherwise, the app will crash if there is more than one window open and a window is closed while at tab is loading.
99     connect(windowPointer, SIGNAL(destroyed()), this, SLOT(stopLoadingFavoriteIconMovie()));
100
101     // Add the first tab.
102     addFirstTab();
103
104     // Process tab events.
105     connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
106     connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
107     connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
108
109     // Store a copy of the WebEngine default user agent.
110     webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
111
112     // Instantiate the mouse event filter pointer.
113     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
114
115     // Install the mouse event filter.
116     qApp->installEventFilter(mouseEventFilterPointer);
117
118     // Process mouse forward and back commands.
119     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
120     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
121 }
122
123 TabWidget::~TabWidget()
124 {
125     // Get the number of tabs.
126     int numberOfTabs = qTabWidgetPointer->count();
127
128     // Manually delete each WebEngine page.
129     for (int i = 0; i < numberOfTabs; ++i)
130     {
131         // Get the tab splitter widget.
132         QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(i);
133
134         // Get the WebEngine views.
135         PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
136         DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
137
138         // Deletion the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
139         delete privacyWebEngineViewPointer->page();
140         delete devToolsWebEngineViewPointer->page();
141     }
142 }
143
144 // 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.
145 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
146 {
147     // Create a URL.
148     QUrl url;
149
150     // 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>
151     if (!cookie.domain().startsWith(QLatin1String(".")))
152     {
153         // Populate the URL.
154         url.setHost(cookie.domain());
155         url.setScheme(QLatin1String("https"));
156
157         // Clear the domain from the cookie.
158         cookie.setDomain(QLatin1String(""));
159     }
160
161     // Add the cookie to the store.
162     if (webEngineCookieStorePointer == nullptr)
163         currentWebEngineCookieStorePointer->setCookie(cookie, url);
164     else
165         webEngineCookieStorePointer->setCookie(cookie, url);
166 }
167
168 void TabWidget::addFirstTab()
169 {
170     // Create the first tab.
171     addTab();
172
173     // Update the UI with the tab settings.
174     updateUiWithTabSettings();
175
176     // 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.
177     qTabWidgetPointer->currentWidget()->setFocus();
178 }
179
180 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool adjacent, const bool backgroundTab, const QString urlString)
181 {
182     // Create a splitter widget.
183     QSplitter *splitterPointer = new QSplitter();
184
185     // Set the splitter to be vertical.
186     splitterPointer->setOrientation(Qt::Vertical);
187
188     // Set the splitter handle size.
189     splitterPointer->setHandleWidth(5);
190
191     // Create the WebEngines.
192     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
193     DevToolsWebEngineView *devToolsWebEngineViewPointer = new DevToolsWebEngineView();
194
195     // Add the WebEngines to the splitter.
196     splitterPointer->addWidget(privacyWebEngineViewPointer);
197     splitterPointer->addWidget(devToolsWebEngineViewPointer);
198
199     // Initialize the new tab index.
200     int newTabIndex = 0;
201
202     // Add a new tab.
203     if (adjacent)  // Add the new tab adjacent to the current tab.
204         newTabIndex = qTabWidgetPointer->insertTab((qTabWidgetPointer->currentIndex() + 1), splitterPointer, i18nc("New tab label.", "New Tab"));
205     else  // Add the new tab at the end of the list.
206         newTabIndex = qTabWidgetPointer->addTab(splitterPointer, i18nc("New tab label.", "New Tab"));
207
208     // Set the default tab icon.
209     qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
210
211     // Get handles for the WebEngine components.
212     QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
213     QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
214     QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
215     QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
216
217     // Set the development tools WebEngine.
218     webEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
219
220     // Initially hide the development tools WebEngine.
221     devToolsWebEngineViewPointer->setVisible(false);
222
223     // Initially disable the development tools WebEngine.
224     webEnginePagePointer->setDevToolsPage(nullptr);
225
226     // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
227     devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
228
229     // Update the URL line edit when the URL changes.
230     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
231     {
232         // Only update the UI if this is the current tab.
233         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
234         {
235             // Update the URL line edit.
236             Q_EMIT updateUrlLineEdit(newUrl);
237
238             // Update the status of the forward and back buttons.
239             Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
240             Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
241         }
242     });
243
244     // Update the title when it changes.
245     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, splitterPointer] (const QString &title)
246     {
247         // Get the index for this tab.
248         int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
249
250         // Update the title for this tab.
251         qTabWidgetPointer->setTabText(tabIndex, title);
252
253         // Update the window title if this is the current tab.
254         if (tabIndex == qTabWidgetPointer->currentIndex())
255             Q_EMIT updateWindowTitle(title);
256     });
257
258     // Connect the loading favorite icon movie to the tab icon.
259     connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, splitterPointer, privacyWebEngineViewPointer]
260     {
261         // Get the index for this tab.
262         int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
263
264         // Display the loading favorite icon if this tab is loading.
265         if (privacyWebEngineViewPointer->isLoading)
266             qTabWidgetPointer->setTabIcon(tabIndex, loadingFavoriteIconMoviePointer->currentPixmap());
267     });
268
269     // Update the icon when it changes.
270     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, splitterPointer, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
271     {
272         // Store the favorite icon in the privacy web engine view.
273         if (newFavoriteIcon.isNull())
274             privacyWebEngineViewPointer->favoriteIcon = defaultFavoriteIcon;
275         else
276             privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;
277
278         // Get the index for this tab.
279         int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
280
281         // Update the icon for this tab.
282         if (newFavoriteIcon.isNull())
283             qTabWidgetPointer->setTabIcon(tabIndex, defaultFavoriteIcon);
284         else
285             qTabWidgetPointer->setTabIcon(tabIndex, newFavoriteIcon);
286     });
287
288     // Update the progress bar and the favorite icon when a load is started.
289     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [this, privacyWebEngineViewPointer] ()
290     {
291         // Set the privacy web engine view to be loading.
292         privacyWebEngineViewPointer->isLoading = true;
293
294         // Store the load progress.
295         privacyWebEngineViewPointer->loadProgressInt = 0;
296
297         // Show the progress bar if this is the current tab.
298         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
299             Q_EMIT showProgressBar(0);
300
301         // Start the loading favorite icon movie.
302         loadingFavoriteIconMoviePointer->start();
303     });
304
305     // Update the progress bar when a load progresses.
306     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [this, privacyWebEngineViewPointer] (const int progress)
307     {
308         // Store the load progress.
309         privacyWebEngineViewPointer->loadProgressInt = progress;
310
311         // Update the progress bar if this is the current tab.
312         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
313             Q_EMIT showProgressBar(progress);
314     });
315
316     // Update the progress bar when a load finishes.
317     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, splitterPointer, privacyWebEngineViewPointer] ()
318     {
319         // Set the privacy web engine view to be not loading.
320         privacyWebEngineViewPointer->isLoading = false;
321
322         // Store the load progress.
323         privacyWebEngineViewPointer->loadProgressInt = -1;
324
325         // Hide the progress bar if this is the current tab.
326         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
327             Q_EMIT hideProgressBar();
328
329         // Get the index for this tab.
330         int tabIndex = qTabWidgetPointer->indexOf(splitterPointer);
331
332         // Display the current favorite icon
333         qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
334
335         // Create a no tabs loading variable.
336         bool noTabsLoading = true;
337
338         // Get the number of tabs.
339         int numberOfTabs = qTabWidgetPointer->count();
340
341         // Check to see if any other tabs are loading.
342         for (int i = 0; i < numberOfTabs; i++)
343         {
344             // Get the privacy WebEngine view for the tab.
345             PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
346
347             // 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.
348             if (privacyWebEngineViewPointer->isLoading)
349                 noTabsLoading = false;
350         }
351
352         // Stop the loading favorite icon movie if there are no loading tabs.
353         if (noTabsLoading)
354             loadingFavoriteIconMoviePointer->stop();
355     });
356
357     // Display HTTP Ping blocked dialogs.
358     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [this, privacyWebEngineViewPointer] (const QString &httpPingUrl)
359     {
360         // Only display the HTTP Ping blocked dialog if this is the current tab.
361         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
362         {
363             // Instantiate an HTTP ping blocked message box.
364             QMessageBox httpPingBlockedMessageBox;
365
366             // Set the icon.
367             httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
368
369             // Set the window title.
370             httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
371
372             // Set the text.
373             httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));
374
375             // Set the standard button.
376             httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
377
378             // Display the message box.
379             httpPingBlockedMessageBox.exec();
380         }
381     });
382
383     // Update the zoom actions when they are changed.
384     connect(webEnginePagePointer, &QWebEnginePage::zoomFactorChanged, [webEnginePagePointer, this] (const qreal newZoomFactor)
385     {
386         // Only update the zoom actions if this is the current tab.
387         if (webEnginePagePointer == currentWebEnginePagePointer)
388             Q_EMIT updateZoomActions(newZoomFactor);
389     });
390
391     // Display find text results.
392     connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
393
394     // Handle full screen requests.
395     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
396
397     // Listen for hovered link URLs.
398     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
399
400     // Handle file downloads.
401     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadRequest *)), this, SLOT(showSaveDialog(QWebEngineDownloadRequest *)));
402
403     // Set the local storage filter.
404     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
405     {
406         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
407         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QUrl(QLatin1String(""))))
408         {
409             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
410
411             // Return false.
412             return false;
413         }
414
415         // Allow the request if local storage is enabled.
416         if (privacyWebEngineViewPointer->localStorageEnabled)
417         {
418             //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;
419
420             // Return true.
421             return true;
422         }
423
424         //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;
425
426         // Block any remaining local storage requests.
427         return false;
428     });
429
430     // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
431     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
432
433     // Don't allow JavaScript to open windows.
434     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
435
436     // Allow keyboard navigation between links and input fields.
437     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
438
439     // Enable full screen support.
440     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
441
442     // Require user interaction to play media.
443     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
444
445     // Limit WebRTC to public IP addresses.
446     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
447
448     // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
449     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
450
451     // Plugins must be enabled for the PDF viewer to work.  <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
452     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
453
454     // Allow JavaScript to paste to (but not copy from) the clipboard, but only with user interaction.
455     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, true);
456
457     // Update the blocked requests action.
458     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::requestBlocked, [this, privacyWebEngineViewPointer] (const QVector<int> blockedRequestsVector)
459     {
460         // Update the blocked requests action if the specified privacy WebEngine view is the current privacy WebEngine view.
461         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
462             Q_EMIT blockedRequestsUpdated(blockedRequestsVector);
463     });
464
465     // Update the cookies action.
466     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::numberOfCookiesChanged, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
467     {
468         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
469         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
470             Q_EMIT cookiesChanged(numberOfCookies);
471     });
472
473     // Process cookie changes.
474     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
475     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
476
477     // Get a list of durable cookies.
478     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
479
480     // Add the durable cookies to the store.
481     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
482         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
483
484     // Enable spell checking.
485     webEngineProfilePointer->setSpellCheckEnabled(true);
486
487     // Set the spell check language.
488     webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
489
490     // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
491     privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
492
493     // Update the UI when domain settings are applied.
494     connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
495
496     // Move to the new tab if it is not a background tab.
497     if (!backgroundTab)
498         qTabWidgetPointer->setCurrentIndex(newTabIndex);
499
500     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
501     if (removeUrlLineEditFocus)
502         Q_EMIT clearUrlLineEditFocus();
503
504     // Load the URL if it isn't blank.
505     if (urlString != QLatin1String(""))
506         privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));
507
508     // Return the privacy WebEngine view pointer.
509     return privacyWebEngineViewPointer;
510 }
511
512 void TabWidget::applyApplicationSettings()
513 {
514     // Set the tab position.
515     if (Settings::tabsOnTop())
516         qTabWidgetPointer->setTabPosition(QTabWidget::North);
517     else
518         qTabWidgetPointer->setTabPosition(QTabWidget::South);
519
520     // Get the number of tabs.
521     int numberOfTabs = qTabWidgetPointer->count();
522
523     // Apply the spatial navigation settings to each WebEngine.
524     for (int i = 0; i < numberOfTabs; ++i) {
525         // Get the WebEngine view pointer.
526         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
527
528         // Apply the spatial navigation settings to each page.
529         privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
530     }
531
532     // Set the search engine URL.
533     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
534
535     // Emit the update search engine actions signal.
536     Q_EMIT updateSearchEngineActions(Settings::searchEngine(), true);
537 }
538
539 void TabWidget::applyDomainSettingsAndReload()
540 {
541     // Get the number of tabs.
542     int numberOfTabs = qTabWidgetPointer->count();
543
544     // Apply the domain settings to each WebEngine.
545     for (int i = 0; i < numberOfTabs; ++i) {
546         // Get the WebEngine view pointer.
547         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
548
549         // Apply the domain settings settings to each page.  `false` indicates that history is not being navigated.
550         privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url(), false);
551     }
552 }
553
554 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
555 {
556     // Store the search engine name.
557     QString searchEngineName = searchEngineActionPointer->text();
558
559     // Strip out any `&` characters.
560     searchEngineName.remove(QLatin1Char('&'));
561
562     // Store the search engine string.
563     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
564
565     // Update the search engine actions.
566     Q_EMIT updateSearchEngineActions(searchEngineName, false);
567 }
568
569 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
570 {
571     // Get the user agent name.
572     QString userAgentName = userAgentActionPointer->text();
573
574     // Strip out any `&` characters.
575     userAgentName.remove(QLatin1Char('&'));
576
577     // Apply the user agent.
578     currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
579
580     // Update the user agent actions.
581     Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
582
583     // Reload the website.
584     currentPrivacyWebEngineViewPointer->reload();
585 }
586
587 void TabWidget::applyOnTheFlyZoomFactor(const double zoomFactorDouble) const
588 {
589     // Set the zoom factor.
590     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactorDouble);
591 }
592
593 void TabWidget::applySpellCheckLanguages() const
594 {
595     // Get the number of tab.
596     int numberOfTabs = qTabWidgetPointer->count();
597
598     // Set the spell check languages for each tab.
599     for (int i = 0; i < numberOfTabs; ++i)
600     {
601         // Get the WebEngine view pointer.
602         PrivacyWebEngineView *privacyWebEngineViewPointer = qTabWidgetPointer->widget(i)->findChild<PrivacyWebEngineView *>();
603
604         // Get the WebEngine page pointer.
605         QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
606
607         // Get the WebEngine profile pointer.
608         QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
609
610         // Set the spell check languages.
611         webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
612     }
613 }
614
615 void TabWidget::back() const
616 {
617     // Go back.
618     currentPrivacyWebEngineViewPointer->back();
619 }
620
621 void TabWidget::deleteAllCookies() const
622 {
623     // Delete all the cookies.
624     currentWebEngineCookieStorePointer->deleteAllCookies();
625 }
626
627 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
628 {
629     // Delete the cookie.
630     currentWebEngineCookieStorePointer->deleteCookie(cookie);
631 }
632
633 void TabWidget::deleteTab(const int tabIndex)
634 {
635     // Get the tab splitter widget.
636     QWidget *tabSplitterWidgetPointer = qTabWidgetPointer->widget(tabIndex);
637
638     // Get the WebEngine views.
639     PrivacyWebEngineView *privacyWebEngineViewPointer = tabSplitterWidgetPointer->findChild<PrivacyWebEngineView *>();
640     DevToolsWebEngineView *devToolsWebEngineViewPointer = tabSplitterWidgetPointer->findChild<DevToolsWebEngineView *>();
641
642     // Process the tab delete according to the number of tabs.
643     if (qTabWidgetPointer->count() > 1)  // There is more than one tab.
644     {
645         // Remove the tab.
646         qTabWidgetPointer->removeTab(tabIndex);
647
648         // Delete the WebEngine pages to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
649         delete privacyWebEngineViewPointer->page();
650         delete devToolsWebEngineViewPointer->page();
651
652         // Delete the WebEngine views.
653         delete privacyWebEngineViewPointer;
654         delete devToolsWebEngineViewPointer;
655
656         // Delete the tab splitter widget.
657         delete tabSplitterWidgetPointer;
658     }
659     else  // There is only one tab.
660     {
661         // Close Privacy Browser.
662         window()->close();
663     }
664 }
665
666 void TabWidget::findPrevious(const QString &text) const
667 {
668     // Store the current text.
669     currentPrivacyWebEngineViewPointer->findString = text;
670
671     // Find the previous text in the current privacy WebEngine.
672     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
673         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
674     else
675         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
676 }
677
678 void TabWidget::findText(const QString &text) const
679 {
680     // Store the current text.
681     currentPrivacyWebEngineViewPointer->findString = text;
682
683     // Find the text in the current privacy WebEngine.
684     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
685         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
686     else
687         currentPrivacyWebEngineViewPointer->findText(text);
688
689     // Clear the currently selected text in the WebEngine page if the find text is empty.
690     if (text.isEmpty())
691         currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
692 }
693
694 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
695 {
696     // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarily flashes `0/0`.
697     if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
698     {
699         // Reset the flag.
700         wipingCurrentFindTextSelection = false;
701     }
702     else  // A new search has been performed.
703     {
704         // Store the result.
705         currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
706
707         // Update the UI.
708         Q_EMIT updateFindTextResults(findTextResult);
709     }
710 }
711
712 void TabWidget::forward() const
713 {
714     // Go forward.
715     currentPrivacyWebEngineViewPointer->forward();
716 }
717
718 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
719 {
720     // Make it so.
721     Q_EMIT fullScreenRequested(fullScreenRequest.toggleOn());
722
723     // Accept the request.
724     fullScreenRequest.accept();
725 }
726
727 std::list<QNetworkCookie>* TabWidget::getCookieList() const
728 {
729     // Return the current cookie list.
730     return currentPrivacyWebEngineViewPointer->cookieListPointer;
731 }
732
733 QIcon TabWidget::getCurrentTabFavoritIcon() const
734 {
735     // Return the current Privacy WebEngine favorite icon.
736     return currentPrivacyWebEngineViewPointer->favoriteIcon;
737 }
738
739 QString TabWidget::getCurrentTabTitle() const
740 {
741     // Return the current Privacy WebEngine title.
742     return currentPrivacyWebEngineViewPointer->title();
743 }
744
745 QString TabWidget::getCurrentTabUrl() const
746 {
747     // Return the current Privacy WebEngine URL as a string.
748     return currentPrivacyWebEngineViewPointer->url().toString();
749 }
750
751 QString TabWidget::getCurrentUserAgent() const
752 {
753     // Return the current WebEngine user agent.
754     return currentWebEngineProfilePointer->httpUserAgent();
755 }
756
757 QString& TabWidget::getDomainSettingsName() const
758 {
759     // Return the domain settings name.
760     return currentPrivacyWebEngineViewPointer->domainSettingsName;
761 }
762
763 void TabWidget::home() const
764 {
765     // Load the homepage.
766     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
767 }
768
769 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
770 {
771     // Apply the application settings.
772     applyApplicationSettings();
773
774     // Return the current privacy WebEngine view pointer.
775     return currentPrivacyWebEngineViewPointer;
776 }
777
778 void TabWidget::loadInitialWebsite()
779 {
780     // Apply the application settings.
781     applyApplicationSettings();
782
783     // Get the arguments.
784     QStringList argumentsStringList = qApp->arguments();
785
786     // Check to see if the arguments lists contains a URL.
787     if (argumentsStringList.size() > 1)
788     {
789         // Load the URL from the arguments list.
790         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
791     }
792     else
793     {
794         // Load the homepage.
795         home();
796     }
797 }
798
799 void TabWidget::loadUrlFromLineEdit(QString url) const
800 {
801     // Decide if the text is more likely to be a URL or a search.
802     if (url.startsWith(QLatin1String("file://")) || url.startsWith(QLatin1String("view-source:")))  // The text is likely a file or view source URL.
803     {
804         // Load the URL.
805         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
806     }
807     else if (url.contains(QLatin1String(".")))  // The text is likely a URL.
808     {
809         // Check if the URL does not start with a valid protocol.
810         if (!url.startsWith(QLatin1String("http")))
811         {
812             // Add `https://` to the beginning of the URL.
813             url = QLatin1String("https://") + url;
814         }
815
816         // Load the URL.
817         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
818     }
819     else  // The text is likely a search.
820     {
821         // Load the search.
822         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
823     }
824 }
825
826 void TabWidget::mouseBack() const
827 {
828     // Go back if possible.
829     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
830     {
831         // Clear the URL line edit focus.
832         Q_EMIT clearUrlLineEditFocus();
833
834         // Go back.
835         currentPrivacyWebEngineViewPointer->back();
836     }
837 }
838
839 void TabWidget::mouseForward() const
840 {
841     // Go forward if possible.
842     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
843     {
844         // Clear the URL line edit focus.
845         Q_EMIT clearUrlLineEditFocus();
846
847         // Go forward.
848         currentPrivacyWebEngineViewPointer->forward();
849     }
850 }
851
852 void TabWidget::pageLinkHovered(const QString &linkUrl) const
853 {
854     // Emit a signal so that the browser window can update the status bar.
855     Q_EMIT linkHovered(linkUrl);
856 }
857
858 void TabWidget::stopLoadingFavoriteIconMovie() const
859 {
860     // Stop the loading favorite icon movie.  Otherwise, the browser will crash if a second window is closed while a tab in it is loading.  <https://redmine.stoutner.com/issues/1010>
861     loadingFavoriteIconMoviePointer->stop();
862 }
863
864 void TabWidget::print() const
865 {
866     // Create a printer.
867     QPrinter *printerPointer = new QPrinter();
868
869     // Set the resolution to be 300 dpi.
870     printerPointer->setResolution(300);
871
872     // Create a printer dialog.
873     QPrintDialog printDialog(printerPointer, currentPrivacyWebEngineViewPointer);
874
875     // Display the dialog and print the page if instructed.
876     if (printDialog.exec() == QDialog::Accepted)
877         currentPrivacyWebEngineViewPointer->print(printerPointer);
878 }
879
880 void TabWidget::printPreview() const
881 {
882     // Create a printer.
883     QPrinter *printerPointer = new QPrinter();
884
885     // Set the resolution to be 300 dpi.
886     printerPointer->setResolution(300);
887
888     // Create a print preview dialog.
889     QPrintPreviewDialog printPreviewDialog(printerPointer, currentPrivacyWebEngineViewPointer);
890
891     // Generate the print preview.
892     auto generatePrintPreview = [this, printerPointer](){
893         // Create an event loop.  The print preview must be generated in a loop or nothing is displayed.
894         QEventLoop eventLoop;
895
896         // Quit the event loop once the print preview has been generated.
897         connect(currentPrivacyWebEngineViewPointer, &QWebEngineView::printFinished, &eventLoop, &QEventLoop::quit);
898
899         // Generate the print preview for the current webpage.
900         currentPrivacyWebEngineViewPointer->print(printerPointer);
901
902         // Execute the loop.
903         eventLoop.exec();
904     };
905
906     // Generate the preview.
907     connect(&printPreviewDialog, &QPrintPreviewDialog::paintRequested, this, generatePrintPreview);
908
909     // Display the dialog.
910     printPreviewDialog.exec();
911 }
912
913 void TabWidget::refresh() const
914 {
915     // Reset the HTTP authentication dialog counter.
916     currentPrivacyWebEngineViewPointer->httpAuthenticationDialogsDisplayed = 0;
917
918     // Reload the website.
919     currentPrivacyWebEngineViewPointer->reload();
920 }
921
922 void TabWidget::reloadAndBypassCache() const
923 {
924     // Reload the website, bypassing the cache.
925     currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
926 }
927
928 void TabWidget::saveArchive()
929 {
930     // Get the suggested file name.
931     QString suggestedFileName = currentPrivacyWebEngineViewPointer->title() + QLatin1String(".mht");
932
933     // Get the download directory.
934     QString downloadDirectory = Settings::downloadDirectory();
935
936     // Resolve the system download directory if specified.
937     if (downloadDirectory == QLatin1String("System Download Directory"))
938         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
939
940     // Get a file path from the file picker.
941     QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
942
943     // Save the webpage as an archive if the file save path is populated.
944     if (!saveFilePath.isEmpty())
945     {
946         // Update the download directory if specified.
947         if (Settings::autoUpateDownloadDirectory())
948             updateDownloadDirectory(saveFilePath);
949
950         // Set the saving archive flag.  Otherwise, a second download tries to run.
951         savingArchive = true;
952
953         // Save the archive.
954         currentWebEnginePagePointer->save(saveFilePath);
955     }
956 }
957
958 void TabWidget::setTabBarVisible(const bool visible) const
959 {
960     // Set the tab bar visibility.
961     qTabWidgetPointer->tabBar()->setVisible(visible);
962 }
963
964 void TabWidget::showSaveDialog(QWebEngineDownloadRequest *webEngineDownloadRequestPointer)
965 {
966     // Only show the save dialog if an archive is not currently being saved.  Otherwise, two save dialogs will be shown.
967     if (!savingArchive)
968     {
969         // Get the download attributes.
970         QUrl downloadUrl = webEngineDownloadRequestPointer->url();
971         QString mimeTypeString = webEngineDownloadRequestPointer->mimeType();
972         QString suggestedFileName = webEngineDownloadRequestPointer->suggestedFileName();
973         int totalBytes = webEngineDownloadRequestPointer->totalBytes();
974
975         // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
976         if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled)  // KDE is not running or local storage (cookies) is enabled.  Use WebEngine's downloader.
977         {
978             // Instantiate the save dialog.
979             SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes);
980
981             // Display the save dialog.
982             int saveDialogResult = saveDialogPointer->exec();
983
984             // Process the save dialog results.
985             if (saveDialogResult == QDialog::Accepted)  // Save was selected.
986             {
987                 // Get the download directory.
988                 QString downloadDirectory = Settings::downloadDirectory();
989
990                 // Resolve the system download directory if specified.
991                 if (downloadDirectory == QLatin1String("System Download Directory"))
992                     downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
993
994                 // Get a file path from the file picker.
995                 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
996
997                 // Process the save file path.
998                 if (!saveFilePath.isEmpty())  // The file save path is populated.
999                 {
1000                     // Update the download directory if specified.
1001                     if (Settings::autoUpateDownloadDirectory())
1002                         updateDownloadDirectory(saveFilePath);
1003
1004                     // Create a save file path file info.
1005                     QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
1006
1007                     // Get the canonical save path and file name.
1008                     QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
1009                     QString saveFileName = saveFilePathFileInfo.fileName();
1010
1011                     // Set the download directory and file name.
1012                     webEngineDownloadRequestPointer->setDownloadDirectory(absoluteSavePath);
1013                     webEngineDownloadRequestPointer->setDownloadFileName(saveFileName);
1014
1015                     // Create a file download notification.
1016                     KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
1017
1018                     // Set the notification title.
1019                     fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
1020
1021                     // Set the notification text.
1022                     fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
1023
1024                     // Get the download icon from the theme.
1025                     QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
1026
1027                     // Set the notification icon.
1028                     fileDownloadNotificationPointer->setIconName(downloadIcon.name());
1029
1030                     // Add the cancel action.
1031                     KNotificationAction *cancelActionPointer = fileDownloadNotificationPointer->addDefaultAction(i18nc("Download notification action","Cancel"));
1032
1033                     // Prevent the notification from being autodeleted if it is closed.  Otherwise, the updates to the notification below cause a crash.
1034                     fileDownloadNotificationPointer->setAutoDelete(false);
1035
1036                     // Handle clicks on the cancel action.
1037                     connect(cancelActionPointer, &KNotificationAction::activated, [webEngineDownloadRequestPointer, saveFileName] ()
1038                     {
1039                         // Cancel the download.
1040                         webEngineDownloadRequestPointer->cancel();
1041
1042                         // Create a file download notification.
1043                         KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
1044
1045                         // Set the notification title.
1046                         canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
1047
1048                         // Set the new text.
1049                         canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
1050
1051                         // Set the notification icon.
1052                         canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
1053
1054                         // Display the notification.
1055                         canceledDownloadNotificationPointer->sendEvent();
1056                     });
1057
1058                     // Update the notification when the download progresses.
1059                     connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::receivedBytesChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName] ()
1060                     {
1061                         // Get the download request information.
1062                         qint64 receivedBytes = webEngineDownloadRequestPointer->receivedBytes();
1063                         qint64 totalBytes = webEngineDownloadRequestPointer->totalBytes();
1064
1065                         // Set the new text.  Total bytes will be 0 if the download size is unknown.
1066                         if (totalBytes > 0)
1067                         {
1068                             // Calculate the download percentage.
1069                             int downloadPercentage = 100 * receivedBytes / totalBytes;
1070
1071                             // Set the file download notification text.
1072                             fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1%% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
1073                                                                            receivedBytes, totalBytes));
1074                         }
1075                         else
1076                         {
1077                             // Set the file download notification text.
1078                             fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1:  %2 bytes downloaded", saveFileName, receivedBytes));
1079                         }
1080
1081                         // Display the updated notification.
1082                         fileDownloadNotificationPointer->sendEvent();
1083                     });
1084
1085                     // Update the notification when the download finishes.  The save file name must be copied into the lambda or a crash occurs.
1086                     connect(webEngineDownloadRequestPointer, &QWebEngineDownloadRequest::isFinishedChanged, [webEngineDownloadRequestPointer, fileDownloadNotificationPointer, saveFileName,
1087                             saveFilePath] ()
1088                     {
1089                         // Update the notification if the download is finished.
1090                         if (webEngineDownloadRequestPointer->isFinished())
1091                         {
1092                             // Set the new text.
1093                             fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
1094
1095                             // Set the URL so the file options will be displayed.
1096                             fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
1097
1098                             // Remove the actions from the notification.
1099                             fileDownloadNotificationPointer->clearActions();
1100
1101                             // Set the notification to disappear after a timeout.
1102                             fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
1103
1104                             // Display the updated notification.
1105                             fileDownloadNotificationPointer->sendEvent();
1106                         }
1107                     });
1108
1109                     // Display the notification.
1110                     fileDownloadNotificationPointer->sendEvent();
1111
1112                     // Start the download.
1113                     webEngineDownloadRequestPointer->accept();
1114                 }
1115                 else  // The file save path is not populated.
1116                 {
1117                     // Cancel the download.
1118                     webEngineDownloadRequestPointer->cancel();
1119                 }
1120             }
1121             else  // Cancel was selected.
1122             {
1123                 // Cancel the download.
1124                 webEngineDownloadRequestPointer->cancel();
1125             }
1126         }
1127         else  // KDE is running and local storage (cookies) is disabled.  Use KDE's native downloader.
1128             // 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.
1129         {
1130             // Instantiate the save dialog.  `true` instructs it to use the native downloader
1131             SaveDialog *saveDialogPointer = new SaveDialog(this, downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
1132
1133             // Connect the save button.
1134             connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
1135
1136             // Show the dialog.
1137             saveDialogPointer->show();
1138         }
1139     }
1140
1141     // Reset the saving archive flag.
1142     savingArchive = false;
1143 }
1144
1145 void TabWidget::stop() const
1146 {
1147     // Stop the loading of the current privacy WebEngine.
1148     currentPrivacyWebEngineViewPointer->stop();
1149 }
1150
1151 void TabWidget::storeCurrentUrlText(const QString &urlText) const
1152 {
1153     // Store the current URL text in the privacy WebEngine view.
1154     currentPrivacyWebEngineViewPointer->currentUrlText = urlText;
1155 }
1156
1157 void TabWidget::toggleDeveloperTools(const bool enabled) const
1158 {
1159     // Get handles for the current tab widgets.
1160     QSplitter *splitterPointer = qobject_cast<QSplitter*>(qTabWidgetPointer->currentWidget());
1161     DevToolsWebEngineView *devToolsWebEngineViewPointer = splitterPointer->findChild<DevToolsWebEngineView *>();
1162
1163     if (enabled)
1164     {
1165         // Set the zoom factor on the development tools WebEngine.
1166         devToolsWebEngineViewPointer->setZoomFactor(currentWebEnginePagePointer->zoomFactor());
1167
1168         // Enable the development tools.
1169         currentWebEnginePagePointer->setDevToolsPage(devToolsWebEngineViewPointer->page());
1170
1171         // Enable JavaScript on the development tools WebEngine.
1172         devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
1173
1174         // Display the developer tools.
1175         devToolsWebEngineViewPointer->setVisible(true);
1176
1177         // Split the visible space equally between the main WebEngine and the developer tools WebEngine.
1178         splitterPointer->setSizes(QList<int>({1, 1}));
1179     }
1180     else
1181     {
1182         // Disable JavaScript on the development tools WebEngine to prevent error messages from being written to the console.
1183         devToolsWebEngineViewPointer->settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
1184
1185         // Disable the development tools.
1186         currentWebEnginePagePointer->setDevToolsPage(nullptr);
1187
1188         // Hide the developer tools.
1189         devToolsWebEngineViewPointer->setVisible(false);
1190     }
1191 }
1192
1193 void TabWidget::toggleDomStorage() const
1194 {
1195     // Toggle DOM storage.
1196     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1197
1198     // Update the DOM storage action.
1199     Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1200
1201     // Reload the website.
1202     currentPrivacyWebEngineViewPointer->reload();
1203 }
1204
1205 void TabWidget::toggleEasyList() const
1206 {
1207     // Toggle EasyList.
1208     currentPrivacyWebEngineViewPointer->easyListEnabled = !currentPrivacyWebEngineViewPointer->easyListEnabled;
1209
1210     // Reload the website.
1211     currentPrivacyWebEngineViewPointer->reload();
1212 }
1213
1214 void TabWidget::toggleEasyPrivacy() const
1215 {
1216     // Toggle EasyPrivacy.
1217     currentPrivacyWebEngineViewPointer->easyPrivacyEnabled = !currentPrivacyWebEngineViewPointer->easyPrivacyEnabled;
1218
1219     // Reload the website.
1220     currentPrivacyWebEngineViewPointer->reload();
1221 }
1222
1223 void TabWidget::toggleFanboysAnnoyanceList() const
1224 {
1225     // Toggle Fanboy's Annoyance List.
1226     currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled = !currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled;
1227
1228     // Reload the website.
1229     currentPrivacyWebEngineViewPointer->reload();
1230 }
1231
1232 void TabWidget::toggleFindCaseSensitive(const QString &text)
1233 {
1234     // Toggle find case sensitive.
1235     currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
1236
1237     // Set the wiping current find text selection flag.
1238     wipingCurrentFindTextSelection = true;
1239
1240     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
1241     findText(QLatin1String(""));
1242
1243     // Update the find text.
1244     findText(text);
1245 }
1246
1247 void TabWidget::toggleJavaScript() const
1248 {
1249     // Toggle JavaScript.
1250     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1251
1252     // Update the JavaScript action.
1253     Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1254
1255     // Reload the website.
1256     currentPrivacyWebEngineViewPointer->reload();
1257 }
1258
1259 void TabWidget::toggleLocalStorage()
1260 {
1261     // Toggle local storage.
1262     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
1263
1264     // Update the local storage action.
1265     Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1266
1267     // Reload the website.
1268     currentPrivacyWebEngineViewPointer->reload();
1269 }
1270
1271 void TabWidget::toggleUltraList() const
1272 {
1273     // Toggle UltraList.
1274     currentPrivacyWebEngineViewPointer->ultraListEnabled = !currentPrivacyWebEngineViewPointer->ultraListEnabled;
1275
1276     // Reload the website.
1277     currentPrivacyWebEngineViewPointer->reload();
1278 }
1279
1280 void TabWidget::toggleUltraPrivacy() const
1281 {
1282     // Toggle UltraPrivacy.
1283     currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled = !currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled;
1284
1285     // Reload the website.
1286     currentPrivacyWebEngineViewPointer->reload();
1287 }
1288
1289 void TabWidget::updateDownloadDirectory(QString newDownloadDirectory) const
1290 {
1291     // Remove the file name from the save file path.
1292     newDownloadDirectory.truncate(newDownloadDirectory.lastIndexOf(QLatin1Char('/')));
1293
1294     // Update the download location.
1295     Settings::setDownloadDirectory(newDownloadDirectory);
1296
1297     // Get a handle for the KConfig skeleton.
1298     KConfigSkeleton *kConfigSkeletonPointer = Settings::self();
1299
1300     // Write the settings to disk.
1301     kConfigSkeletonPointer->save();
1302 }
1303
1304 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
1305 {
1306     // Only update the UI if the signal was emitted from the current privacy WebEngine.
1307     if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
1308     {
1309         // Update the UI.
1310         Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
1311         Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
1312         Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
1313         Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
1314         Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
1315         Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1316         Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1317         Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1318         Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1319         Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1320         Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1321         Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1322     }
1323 }
1324
1325 void TabWidget::updateUiWithTabSettings()
1326 {
1327     // Clear the URL line edit focus.
1328     Q_EMIT clearUrlLineEditFocus();
1329
1330     // Update the current WebEngine pointers.
1331     currentPrivacyWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<PrivacyWebEngineView *>();
1332     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1333     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1334     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1335     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1336     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1337
1338     // Get a handle for the development tools WebEngine view.
1339     DevToolsWebEngineView *devToolsWebEngineViewPointer = qTabWidgetPointer->currentWidget()->findChild<DevToolsWebEngineView *>();
1340
1341     // Update the actions.
1342     Q_EMIT easyListStatusChanged(currentPrivacyWebEngineViewPointer->easyListEnabled);
1343     Q_EMIT easyPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->easyPrivacyEnabled);
1344     Q_EMIT fanboysAnnoyanceListStatusChanged(currentPrivacyWebEngineViewPointer->fanboysAnnoyanceListEnabled);
1345     Q_EMIT ultraListStatusChanged(currentPrivacyWebEngineViewPointer->ultraListEnabled);
1346     Q_EMIT ultraPrivacyStatusChanged(currentPrivacyWebEngineViewPointer->ultraPrivacyEnabled);
1347     Q_EMIT blockedRequestsUpdated(currentPrivacyWebEngineViewPointer->blockedRequestsVector);
1348     Q_EMIT cookiesChanged(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1349     Q_EMIT updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1350     Q_EMIT updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1351     Q_EMIT updateDeveloperToolsAction(devToolsWebEngineViewPointer->isVisible());
1352     Q_EMIT updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1353     Q_EMIT updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1354     Q_EMIT updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1355     Q_EMIT updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1356     Q_EMIT updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1357     Q_EMIT updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1358
1359     // Update the find text.
1360     Q_EMIT updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1361     Q_EMIT updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1362
1363     // Update the progress bar.
1364     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1365         Q_EMIT showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1366     else
1367         Q_EMIT hideProgressBar();
1368
1369     // Update the URL.
1370     Q_EMIT updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1371     Q_EMIT updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1372     Q_EMIT updateUrlLineEdit(QUrl(currentPrivacyWebEngineViewPointer->currentUrlText));
1373 }
1374
1375 void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1376 {
1377     // Get the download directory.
1378     QString downloadDirectory = Settings::downloadDirectory();
1379
1380     // Resolve the system download directory if specified.
1381     if (downloadDirectory == QLatin1String("System Download Directory"))
1382         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1383
1384     // Create a save file dialog.
1385     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1386
1387     // Tell the dialog to use a save button.
1388     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1389
1390     // Populate the file name from the download item pointer.
1391     saveFileDialogPointer->selectFile(suggestedFileName);
1392
1393     // Prevent interaction with the parent window while the dialog is open.
1394     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1395
1396     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1397     auto saveFile = [saveFileDialogPointer, downloadUrl, this] ()
1398     {
1399         // Get the save location.  The dialog box should only allow the selecting of one file location.
1400         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1401
1402         // Update the download directory if specified.
1403         if (Settings::autoUpateDownloadDirectory())
1404             updateDownloadDirectory(saveLocation.toLocalFile());
1405
1406         // Create a file copy job.  `-1` creates the file with default permissions.
1407         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1408
1409         // Set the download job to display any warning and error messages.
1410         fileCopyJobPointer->uiDelegate()->setAutoWarningHandlingEnabled(true);
1411         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1412
1413         // Start the download.
1414         fileCopyJobPointer->start();
1415     };
1416
1417     // Handle clicks on the save button.
1418     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1419
1420     // Show the dialog.
1421     saveFileDialogPointer->show();
1422 }
1423