]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/widgets/TabWidget.cpp
bd62f83beaa521343768bb9355fa3922359cd1f3
[PrivacyBrowserPC.git] / src / widgets / TabWidget.cpp
1 /*
2  * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
6  * Privacy Browser PC is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser PC is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 // Application headers.
21 #include "TabWidget.h"
22 #include "Settings.h"
23 #include "ui_AddTabWidget.h"
24 #include "ui_TabWidget.h"
25 #include "databases/CookiesDatabase.h"
26 #include "databases/DomainsDatabase.h"
27 #include "dialogs/SaveDialog.h"
28 #include "filters/MouseEventFilter.h"
29 #include "helpers/SearchEngineHelper.h"
30 #include "helpers/UserAgentHelper.h"
31 #include "interceptors/UrlRequestInterceptor.h"
32 #include "windows/BrowserWindow.h"
33
34 // KDE Framework headers.
35 #include <KIO/FileCopyJob>
36 #include <KIO/JobUiDelegate>
37
38 // Qt toolkit headers.
39 #include <QAction>
40 #include <QFileDialog>
41 #include <QGraphicsScene>
42 #include <QGraphicsView>
43 #include <QPrintDialog>
44 #include <QPrintPreviewDialog>
45 #include <QPrinter>
46
47 // Initialize the public static variables.
48 QString TabWidget::webEngineDefaultUserAgent = QStringLiteral("");
49
50 // Construct the class.
51 TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
52 {
53     // Instantiate the UIs.
54     Ui::TabWidget tabWidgetUi;
55     Ui::AddTabWidget addTabWidgetUi;
56
57     // Setup the main UI.
58     tabWidgetUi.setupUi(this);
59
60     // Get a handle for the tab widget.
61     tabWidgetPointer = tabWidgetUi.tabWidget;
62
63     // Setup the add tab UI.
64     addTabWidgetUi.setupUi(tabWidgetPointer);
65
66     // Get handles for the add tab widgets.
67     QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
68     QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
69
70     // Display the add tab widget.
71     tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
72
73     // Add the first tab.
74     addFirstTab();
75
76     // Process tab events.
77     connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
78     connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
79     connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
80
81     // Store a copy of the WebEngine default user agent.
82     webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
83
84     // Instantiate the mouse event filter pointer.
85     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
86
87     // Install the mouse event filter.
88     qApp->installEventFilter(mouseEventFilterPointer);
89
90     // Process mouse forward and back commands.
91     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
92     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
93 }
94
95 TabWidget::~TabWidget()
96 {
97     // Manually delete each WebEngine page.
98     for (int i = 0; i < tabWidgetPointer->count(); ++i)
99     {
100         // Get the privacy WebEngine view.
101         PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
102
103         // Deletion the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
104         delete privacyWebEngineViewPointer->page();
105     }
106 }
107
108 // 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.
109 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
110 {
111     // Create a url.
112     QUrl url;
113
114     // 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>
115     if (!cookie.domain().startsWith(QStringLiteral(".")))
116     {
117         // Populate the URL.
118         url.setHost(cookie.domain());
119         url.setScheme(QStringLiteral("https"));
120
121         // Clear the domain from the cookie.
122         cookie.setDomain(QStringLiteral(""));
123     }
124
125     // Add the cookie to the store.
126     if (webEngineCookieStorePointer == nullptr)
127         currentWebEngineCookieStorePointer->setCookie(cookie, url);
128     else
129         webEngineCookieStorePointer->setCookie(cookie, url);
130 }
131
132 void TabWidget::addFirstTab()
133 {
134     // Create the first tab.
135     addTab();
136
137     // Update the UI with the tab settings.
138     updateUiWithTabSettings();
139
140     // 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.
141     tabWidgetPointer->currentWidget()->setFocus();
142 }
143
144 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
145 {
146     // Create a privacy WebEngine view.
147     PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
148
149     // Add a new tab.
150     int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
151
152     // Set the default tab icon.
153     tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
154
155     // Create an off-the-record profile (the default when no profile name is specified).
156     QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
157
158     // Create a WebEngine page.
159     QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
160
161     // Set the WebEngine page.
162     privacyWebEngineViewPointer->setPage(webEnginePagePointer);
163
164     // Get handles for the web engine elements.
165     QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
166     QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
167
168     // Update the URL line edit when the URL changes.
169     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
170     {
171         // Only update the UI if this is the current tab.
172         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
173         {
174             // Update the URL line edit.
175             emit updateUrlLineEdit(newUrl);
176
177             // Update the status of the forward and back buttons.
178             emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
179             emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
180         }
181
182         // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  It can be removed once <https://redmine.stoutner.com/issues/799> is fixed.
183         privacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
184     });
185
186     // Update the progress bar when a load is started.
187     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
188     {
189         // Store the load progress.
190         privacyWebEngineViewPointer->loadProgressInt = 0;
191
192         // Show the progress bar if this is the current tab.
193         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
194             emit showProgressBar(0);
195     });
196
197     // Update the progress bar when a load progresses.
198     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
199     {
200         // Store the load progress.
201         privacyWebEngineViewPointer->loadProgressInt = progress;
202
203         // Update the progress bar if this is the current tab.
204         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
205             emit showProgressBar(progress);
206     });
207
208     // Update the progress bar when a load finishes.
209     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
210     {
211         // Store the load progress.
212         privacyWebEngineViewPointer->loadProgressInt = -1;
213
214         // Hide the progress bar if this is the current tab.
215         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
216             emit hideProgressBar();
217     });
218
219     // Display find text results.
220     connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
221
222     // Handle full screen requests.
223     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
224
225     // Listen for hovered link URLs.
226     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
227
228     // Handle file downloads.
229     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
230
231     // Instantiate the URL request interceptor.
232     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
233
234     // Set the URL request interceptor.
235     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
236
237     // Reapply the domain settings when the host changes.
238     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
239
240     // Set the local storage filter.
241     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
242     {
243         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
244         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
245         {
246             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
247
248             // Return false.
249             return false;
250         }
251
252         // Allow the request if local storage is enabled.
253         if (privacyWebEngineViewPointer->localStorageEnabled)
254         {
255             //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;
256
257             // Return true.
258             return true;
259         }
260
261         //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;
262
263         // Block any remaining local storage requests.
264         return false;
265     });
266
267     // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
268     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
269
270     // Don't allow JavaScript to open windows.
271     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
272
273     // Allow keyboard navigation.
274     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
275
276     // Enable full screen support.
277     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
278
279     // Require user interaction to play media.
280     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
281
282     // Limit WebRTC to public IP addresses.
283     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
284
285     // Update the cookies action.
286     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
287     {
288         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
289         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
290             emit updateCookiesAction(numberOfCookies);
291     });
292
293     // Process cookie changes.
294     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
295     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
296
297     // Get a list of durable cookies.
298     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
299
300     // Add the durable cookies to the store.
301     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
302         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
303
304     // Update the title when it changes.
305     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
306     {
307         // Get the index for this tab.
308         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
309
310         // Update the title for this tab.
311         tabWidgetPointer->setTabText(tabIndex, title);
312
313         // Update the window title if this is the current tab.
314         if (tabIndex == tabWidgetPointer->currentIndex())
315             emit updateWindowTitle(title);
316     });
317
318     // Update the icon when it changes.
319     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
320     {
321         // Get the index for this tab.
322         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
323
324         // Update the icon for this tab.
325         if (icon.isNull())
326             tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
327         else
328             tabWidgetPointer->setTabIcon(tabIndex, icon);
329     });
330
331     // Enable spell checking.
332     webEngineProfilePointer->setSpellCheckEnabled(true);
333
334     // Set the spell check language.
335     webEngineProfilePointer->setSpellCheckLanguages({QStringLiteral("en_US")});
336
337     // Populate the zoom factor.  This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
338     privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
339
340     // Move to the new tab if it is not a background tab.
341     if (!backgroundTab)
342         tabWidgetPointer->setCurrentIndex(newTabIndex);
343
344     // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
345     if (removeUrlLineEditFocus)
346         emit clearUrlLineEditFocus();
347
348     // Return the privacy WebEngine view pointer.
349     return privacyWebEngineViewPointer;
350 }
351
352 void TabWidget::applyApplicationSettings()
353 {
354     // Set the tab position.
355     if (Settings::tabsOnTop())
356         tabWidgetPointer->setTabPosition(QTabWidget::North);
357     else
358         tabWidgetPointer->setTabPosition(QTabWidget::South);
359
360     // Set the search engine URL.
361     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
362
363     // Emit the update search engine actions signal.
364     emit updateSearchEngineActions(Settings::searchEngine(), true);
365 }
366
367 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
368 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
369 void TabWidget::applyDomainSettingsAndReload()
370 {
371     // Apply the domain settings.  `true` reloads the website.
372     applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
373 }
374
375 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
376 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
377 void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
378 {
379     // Apply the domain settings  `false` does not reload the website.
380     applyDomainSettings(hostname, false);
381 }
382
383 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
384 void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
385 {
386     // Get the record for the hostname.
387     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
388
389     // Check if the hostname has domain settings.
390     if (domainQuery.isValid())  // The hostname has domain settings.
391     {
392         // Get the domain record.
393         QSqlRecord domainRecord = domainQuery.record();
394
395         // Store the domain settings name.
396         currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
397
398         // Set the JavaScript status.
399         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
400         {
401             // Set the default JavaScript status.
402             case (DomainsDatabase::SYSTEM_DEFAULT):
403             {
404                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
405
406                 break;
407             }
408
409             // Disable JavaScript.
410             case (DomainsDatabase::DISABLED):
411             {
412                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
413
414                 break;
415             }
416
417             // Enable JavaScript.
418             case (DomainsDatabase::ENABLED):
419             {
420                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
421
422                 break;
423             }
424         }
425
426         // Set the local storage status.
427         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
428         {
429             // Set the default local storage status.
430             case (DomainsDatabase::SYSTEM_DEFAULT):
431             {
432                 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
433
434                 break;
435             }
436
437             // Disable local storage.
438             case (DomainsDatabase::DISABLED):
439             {
440                 currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
441
442                 break;
443             }
444
445             // Enable local storage.
446             case (DomainsDatabase::ENABLED):
447             {
448                 currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
449
450                 break;
451             }
452         }
453
454         // Set the DOM storage status.
455         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
456         {
457             // Set the default DOM storage status.
458             case (DomainsDatabase::SYSTEM_DEFAULT):
459             {
460                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
461
462                 break;
463             }
464
465             // Disable DOM storage.
466             case (DomainsDatabase::DISABLED):
467             {
468                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
469
470                 break;
471             }
472
473             // Enable DOM storage.
474             case (DomainsDatabase::ENABLED):
475             {
476                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
477
478                 break;
479             }
480         }
481
482         // Set the user agent.
483         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
484
485         // Check if a custom zoom factor is set.
486         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
487         {
488             // Store the current zoom factor.
489             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
490         }
491         else
492         {
493             // Reset the current zoom factor.
494             currentZoomFactor = Settings::zoomFactor();
495         }
496
497         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
498         currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
499     }
500     else  // The hostname does not have domain settings.
501     {
502         // Reset the domain settings name.
503         currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
504
505         // Set the JavaScript status.
506         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
507
508         // Set the local storage status.
509         currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
510
511         // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
512         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
513
514         // Set the user agent.
515         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
516
517         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
518         currentZoomFactor = Settings::zoomFactor();
519
520         // Set the zoom factor.
521         currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
522     }
523
524     // Update the UI.
525     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
526     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
527     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
528     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
529     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
530     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
531
532     // Reload the website if requested.
533     if (reloadWebsite)
534         currentPrivacyWebEngineViewPointer->reload();
535 }
536
537 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
538 {
539     // Store the search engine name.
540     QString searchEngineName = searchEngineActionPointer->text();
541
542     // Strip out any `&` characters.
543     searchEngineName.remove('&');
544
545     // Store the search engine string.
546     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
547
548     // Update the search engine actionas.
549     emit updateSearchEngineActions(searchEngineName, false);
550 }
551
552 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
553 {
554     // Get the user agent name.
555     QString userAgentName = userAgentActionPointer->text();
556
557     // Strip out any `&` characters.
558     userAgentName.remove('&');
559
560     // Apply the user agent.
561     currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
562
563     // Update the user agent actions.
564     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
565
566     // Reload the website.
567     currentPrivacyWebEngineViewPointer->reload();
568 }
569
570 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
571 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
572 {
573     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
574     currentZoomFactor = zoomFactor;
575
576     // Set the zoom factor.
577     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
578 }
579
580 void TabWidget::back() const
581 {
582     // Go back.
583     currentPrivacyWebEngineViewPointer->back();
584 }
585
586 void TabWidget::deleteAllCookies() const
587 {
588     // Delete all the cookies.
589     currentWebEngineCookieStorePointer->deleteAllCookies();
590 }
591
592 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
593 {
594     // Delete the cookie.
595     currentWebEngineCookieStorePointer->deleteCookie(cookie);
596 }
597
598 void TabWidget::deleteTab(const int tabIndex)
599 {
600     // Get the privacy WebEngine view.
601     PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
602
603     // Proccess the tab delete according to the number of tabs.
604     if (tabWidgetPointer->count() > 1)  // There is more than one tab.
605     {
606         // Delete the tab.
607         tabWidgetPointer->removeTab(tabIndex);
608
609         // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
610         delete privacyWebEngineViewPointer->page();
611
612         // Delete the privacy WebEngine view.
613         delete privacyWebEngineViewPointer;
614     }
615     else  // There is only one tab.
616     {
617         // Close Privacy Browser.
618         window()->close();
619     }
620 }
621
622 void TabWidget::findPrevious(const QString &text) const
623 {
624     // Store the current text.
625     currentPrivacyWebEngineViewPointer->findString = text;
626
627     // Find the previous text in the current privacy WebEngine.
628     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
629         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
630     else
631         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
632 }
633
634 void TabWidget::findText(const QString &text) const
635 {
636     // Store the current text.
637     currentPrivacyWebEngineViewPointer->findString = text;
638
639     // Find the text in the current privacy WebEngine.
640     if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
641         currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
642     else
643         currentPrivacyWebEngineViewPointer->findText(text);
644
645     // Clear the currently selected text in the WebEngine page if the find text is empty.
646     if (text.isEmpty())
647         currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
648 }
649
650 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
651 {
652     // Update the find text UI if it wasn't simply wiping the current find text selection.  Otherwise the UI temporarially flashes `0/0`.
653     if (wipingCurrentFindTextSelection)  // The current selection is being wiped.
654     {
655         // Reset the flag.
656         wipingCurrentFindTextSelection = false;
657     }
658     else  // A new search has been performed.
659     {
660         // Store the result.
661         currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
662
663         // Update the UI.
664         emit updateFindTextResults(findTextResult);
665     }
666 }
667
668 void TabWidget::forward() const
669 {
670     // Go forward.
671     currentPrivacyWebEngineViewPointer->forward();
672 }
673
674 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
675 {
676     // Make it so.
677     emit fullScreenRequested(fullScreenRequest.toggleOn());
678
679     // Accept the request.
680     fullScreenRequest.accept();
681 }
682
683 std::list<QNetworkCookie>* TabWidget::getCookieList() const
684 {
685     // Return the current cookie list.
686     return currentPrivacyWebEngineViewPointer->cookieListPointer;
687 }
688
689 QString& TabWidget::getDomainSettingsName() const
690 {
691     // Return the domain settings name.
692     return currentPrivacyWebEngineViewPointer->domainSettingsName;
693 }
694
695 void TabWidget::home() const
696 {
697     // Load the homepage.
698     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
699 }
700
701 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
702 {
703     // Apply the application settings.
704     applyApplicationSettings();
705
706     // Return the current privacy WebEngine view pointer.
707     return currentPrivacyWebEngineViewPointer;
708 }
709
710 void TabWidget::loadInitialWebsite()
711 {
712     // Apply the application settings.
713     applyApplicationSettings();
714
715     // Get the arguments.
716     QStringList argumentsStringList = qApp->arguments();
717
718     // Check to see if the arguments lists contains a URL.
719     if (argumentsStringList.size() > 1)
720     {
721         // Load the URL from the arguments list.
722         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
723     }
724     else
725     {
726         // Load the homepage.
727         home();
728     }
729 }
730
731 void TabWidget::loadUrlFromLineEdit(QString url) const
732 {
733     // Decide if the text is more likely to be a URL or a search.
734     if (url.startsWith("file://"))  // The text is likely a file URL.
735     {
736         // Load the URL.
737         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
738     }
739     else if (url.contains("."))  // The text is likely a URL.
740     {
741         // Check if the URL does not start with a valid protocol.
742         if (!url.startsWith("http"))
743         {
744             // Add `https://` to the beginning of the URL.
745             url = "https://" + url;
746         }
747
748         // Load the URL.
749         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
750     }
751     else  // The text is likely a search.
752     {
753         // Load the search.
754         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
755     }
756 }
757
758 void TabWidget::mouseBack() const
759 {
760     // Go back if possible.
761     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
762     {
763         // Clear the URL line edit focus.
764         emit clearUrlLineEditFocus();
765
766         // Go back.
767         currentPrivacyWebEngineViewPointer->back();
768     }
769 }
770
771 void TabWidget::mouseForward() const
772 {
773     // Go forward if possible.
774     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
775     {
776         // Clear the URL line edit focus.
777         emit clearUrlLineEditFocus();
778
779         // Go forward.
780         currentPrivacyWebEngineViewPointer->forward();
781     }
782 }
783
784 void TabWidget::pageLinkHovered(const QString &linkUrl) const
785 {
786     // Emit a signal so that the browser window can update the status bar.
787     emit linkHovered(linkUrl);
788 }
789
790 void TabWidget::print() const
791 {
792     // Create a printer.
793     QPrinter printer;
794
795     // Set the resolution to be 300 dpi.
796     printer.setResolution(300);
797
798     // Create a printer dialog.
799     QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
800
801     // Display the dialog and print the page if instructed.
802     if (printDialog.exec() == QDialog::Accepted)
803         printWebpage(&printer);
804 }
805
806 void TabWidget::printPreview() const
807 {
808     // Create a printer.
809     QPrinter printer;
810
811     // Set the resolution to be 300 dpi.
812     printer.setResolution(300);
813
814     // Create a print preview dialog.
815     QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
816
817     // Generate the print preview.
818     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
819
820     // Display the dialog.
821     printPreviewDialog.exec();
822 }
823
824 void TabWidget::printWebpage(QPrinter *printerPointer) const
825 {
826     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
827     QEventLoop eventLoop;
828
829     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
830     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
831     currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
832     {
833         // Instruct the compiler to ignore the unused parameter.
834         (void) printSuccess;
835
836         // Quit the loop.
837         eventLoop.quit();
838     });
839
840     // Execute the loop.
841     eventLoop.exec();
842 }
843
844 void TabWidget::refresh() const
845 {
846     // Reload the website.
847     currentPrivacyWebEngineViewPointer->reload();
848 }
849
850 void TabWidget::setTabBarVisible(const bool visible) const
851 {
852     // Set the tab bar visibility.
853     tabWidgetPointer->tabBar()->setVisible(visible);
854 }
855
856 void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
857 {
858     // Instantiate the save dialog.
859     SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
860
861     // Connect the save button.
862     connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
863
864     // Show the dialog.
865     saveDialogPointer->show();
866 }
867
868 void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
869 {
870     // Get the download location.
871     QString downloadDirectory = Settings::downloadLocation();
872
873     // Resolve the system download directory if specified.
874     if (downloadDirectory == QStringLiteral("System Download Directory"))
875         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
876
877     // Create a save file dialog.
878     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
879
880     // Tell the dialog to use a save button.
881     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
882
883     // Populate the file name from the download item pointer.
884     saveFileDialogPointer->selectFile(suggestedFileName);
885
886     // Prevent interaction with the parent window while the dialog is open.
887     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
888
889     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
890     auto saveFile = [saveFileDialogPointer, &downloadUrl] () {
891         // Get the save location.  The dialog box should only allow the selecting of one file location.
892         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
893
894         // Create a file copy job.  `-1` creates the file with default permissions.
895         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
896
897         // Set the download job to display any error messages.
898         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
899
900         // Start the download.
901         fileCopyJobPointer->start();
902     };
903
904     // Handle clicks on the save button.
905     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
906
907     // Show the dialog.
908     saveFileDialogPointer->show();
909 }
910
911 void TabWidget::toggleDomStorage() const
912 {
913     // Toggle DOM storage.
914     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
915
916     // Update the DOM storage action.
917     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
918
919     // Reload the website.
920     currentPrivacyWebEngineViewPointer->reload();
921 }
922
923 void TabWidget::toggleFindCaseSensitive(const QString &text)
924 {
925     // Toggle find case sensitive.
926     currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
927
928     // Set the wiping current find text selection flag.
929     wipingCurrentFindTextSelection = true;
930
931     // Wipe the previous search.  Otherwise currently highlighted words will remain highlighted.
932     findText(QStringLiteral(""));
933
934     // Update the find text.
935     findText(text);
936 }
937
938 void TabWidget::toggleJavaScript() const
939 {
940     // Toggle JavaScript.
941     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
942
943     // Update the JavaScript action.
944     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
945
946     // Reload the website.
947     currentPrivacyWebEngineViewPointer->reload();
948 }
949
950 void TabWidget::toggleLocalStorage()
951 {
952     // Toggle local storeage.
953     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
954
955     // Update the local storage action.
956     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
957
958     // Reload the website.
959     currentPrivacyWebEngineViewPointer->reload();
960 }
961
962 void TabWidget::updateUiWithTabSettings()
963 {
964     // Update the current WebEngine pointers.
965     currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
966     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
967     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
968     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
969     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
970     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
971
972     // Clear the URL line edit focus.
973     emit clearUrlLineEditFocus();
974
975     // Update the actions.
976     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
977     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
978     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
979     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
980     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
981     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
982     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
983     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
984
985     // Update the URL.
986     emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
987     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
988     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
989
990     // Update the find text.
991     emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
992     emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
993
994     // Update the progress bar.
995     if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
996         emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
997     else
998         emit hideProgressBar();
999 }