ba12f042d4076bc1cc0bb7e679e47f79f7c15e25
[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()
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, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
170
171     // Update the progress bar.
172     connect(privacyWebEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
173     connect(privacyWebEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
174     connect(privacyWebEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
175
176     // Handle full screen requests.
177     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
178
179     // Listen for hovered link URLs.
180     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
181
182     // Handle file downloads.
183     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
184
185     // Instantiate the URL request interceptor.
186     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
187
188     // Set the URL request interceptor.
189     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
190
191     // Reapply the domain settings when the host changes.
192     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
193
194     // Set the local storage filter.
195     webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
196     {
197         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
198         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
199         {
200             //qDebug().noquote().nospace() << "Third-party request blocked:  " << filterRequest.origin;
201
202             // Return false.
203             return false;
204         }
205
206         // Allow the request if local storage is enabled.
207         if (privacyWebEngineViewPointer->localStorageEnabled)
208         {
209             //qDebug().noquote().nospace() << "Request allowed by local storage:  " << filterRequest.origin;
210
211             // Return true.
212             return true;
213         }
214
215         //qDebug().noquote().nospace() << "Request blocked by default:  " << filterRequest.origin;
216
217         // Block any remaining local storage requests.
218         return false;
219     });
220
221     // Disable JavaScript by default (this prevetns JavaScript from being enabled on a new tab before domain settings are loaded).
222     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
223
224     // Don't allow JavaScript to open windows.
225     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
226
227     // Allow keyboard navigation.
228     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
229
230     // Enable full screen support.
231     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
232
233     // Require user interaction to play media.
234     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
235
236     // Limit WebRTC to public IP addresses.
237     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
238
239     // Define an update cookie count lambda.
240     auto updateCookieCount = [privacyWebEngineViewPointer, this] (const int numberOfCookies)
241     {
242         // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
243         if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
244             emit updateCookiesAction(numberOfCookies);
245     };
246
247     // Update the cookies action.
248     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, this, updateCookieCount);
249
250     // Process cookie changes.
251     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
252     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
253
254     // Get a list of durable cookies.
255     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
256
257     // Add the durable cookies to the store.
258     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
259         addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
260
261     // Define an update tab title lambda.
262     auto updateTabTitle = [privacyWebEngineViewPointer, this] (const QString &title)
263     {
264         // Get the index for this tab.
265         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
266
267         // Update the title for this tab.
268         tabWidgetPointer->setTabText(tabIndex, title);
269     };
270
271     // Update the title when it changes.
272     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, this, updateTabTitle);
273
274     // Define an update tab icon lambda.
275     auto updateTabIcon = [privacyWebEngineViewPointer, this] (const QIcon &icon)
276     {
277         // Get the index for this tab.
278         int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
279
280         // Update the icon for this tab.
281         if (icon.isNull())
282             tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
283         else
284             tabWidgetPointer->setTabIcon(tabIndex, icon);
285     };
286
287     // Update the icon when it changes.
288     connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, this, updateTabIcon);
289
290     // Move to the new tab.
291     tabWidgetPointer->setCurrentIndex(newTabIndex);
292
293     // Return the privacy WebEngine view pointer.
294     return privacyWebEngineViewPointer;
295 }
296
297 void TabWidget::applyApplicationSettings()
298 {
299     // Set the tab position.
300     if (Settings::tabsOnTop())
301         tabWidgetPointer->setTabPosition(QTabWidget::North);
302     else
303         tabWidgetPointer->setTabPosition(QTabWidget::South);
304
305     // Set the search engine URL.
306     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
307
308     // Emit the update search engine actions signal.
309     emit updateSearchEngineActions(Settings::searchEngine(), true);
310 }
311
312 // 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.
313 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
314 void TabWidget::applyDomainSettingsAndReload()
315 {
316     // Apply the domain settings.  `true` reloads the website.
317     applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
318 }
319
320 // 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.
321 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
322 void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
323 {
324     // Apply the domain settings  `false` does not reload the website.
325     applyDomainSettings(hostname, false);
326 }
327
328 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
329 void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
330 {
331     // Get the record for the hostname.
332     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
333
334     // Check if the hostname has domain settings.
335     if (domainQuery.isValid())  // The hostname has domain settings.
336     {
337         // Get the domain record.
338         QSqlRecord domainRecord = domainQuery.record();
339
340         // Store the domain settings name.
341         currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
342
343         // Set the JavaScript status.
344         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
345         {
346             // Set the default JavaScript status.
347             case (DomainsDatabase::SYSTEM_DEFAULT):
348             {
349                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
350
351                 break;
352             }
353
354             // Disable JavaScript.
355             case (DomainsDatabase::DISABLED):
356             {
357                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
358
359                 break;
360             }
361
362             // Enable JavaScript.
363             case (DomainsDatabase::ENABLED):
364             {
365                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
366
367                 break;
368             }
369         }
370
371         // Set the local storage status.
372         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
373         {
374             // Set the default local storage status.
375             case (DomainsDatabase::SYSTEM_DEFAULT):
376             {
377                 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
378
379                 break;
380             }
381
382             // Disable local storage.
383             case (DomainsDatabase::DISABLED):
384             {
385                 currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
386
387                 break;
388             }
389
390             // Enable local storage.
391             case (DomainsDatabase::ENABLED):
392             {
393                 currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
394
395                 break;
396             }
397         }
398
399         // Set the DOM storage status.
400         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
401         {
402             // Set the default DOM storage status.
403             case (DomainsDatabase::SYSTEM_DEFAULT):
404             {
405                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
406
407                 break;
408             }
409
410             // Disable DOM storage.
411             case (DomainsDatabase::DISABLED):
412             {
413                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
414
415                 break;
416             }
417
418             // Enable DOM storage.
419             case (DomainsDatabase::ENABLED):
420             {
421                 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
422
423                 break;
424             }
425         }
426
427         // Set the user agent.
428         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
429
430         // Check if a custom zoom factor is set.
431         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
432         {
433             // Store the current zoom factor.
434             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
435         }
436         else
437         {
438             // Reset the current zoom factor.
439             currentZoomFactor = Settings::zoomFactor();
440         }
441
442         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
443         currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
444     }
445     else  // The hostname does not have domain settings.
446     {
447         // Reset the domain settings name.
448         currentPrivacyWebEngineViewPointer->domainSettingsName = QStringLiteral("");
449
450         // Set the JavaScript status.
451         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
452
453         // Set the local storage status.
454         currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
455
456         // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
457         currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
458
459         // Set the user agent.
460         currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
461
462         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
463         currentZoomFactor = Settings::zoomFactor();
464
465         // Set the zoom factor.
466         currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
467     }
468
469     // Update the UI.
470     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
471     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
472     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
473     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
474     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
475     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
476
477     // Reload the website if requested.
478     if (reloadWebsite)
479         currentPrivacyWebEngineViewPointer->reload();
480 }
481
482 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
483 {
484     // Store the search engine name.
485     QString searchEngineName = searchEngineActionPointer->text();
486
487     // Strip out any `&` characters.
488     searchEngineName.remove('&');
489
490     // Store the search engine string.
491     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
492
493     // Update the search engine actionas.
494     emit updateSearchEngineActions(searchEngineName, false);
495 }
496
497 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
498 {
499     // Get the user agent name.
500     QString userAgentName = userAgentActionPointer->text();
501
502     // Strip out any `&` characters.
503     userAgentName.remove('&');
504
505     // Apply the user agent.
506     currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
507
508     // Update the user agent actions.
509     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
510
511     // Reload the website.
512     currentPrivacyWebEngineViewPointer->reload();
513 }
514
515 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
516 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
517 {
518     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
519     currentZoomFactor = zoomFactor;
520
521     // Set the zoom factor.
522     currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
523 }
524
525 void TabWidget::back() const
526 {
527     // Go back.
528     currentPrivacyWebEngineViewPointer->back();
529 }
530
531 void TabWidget::deleteAllCookies() const
532 {
533     // Delete all the cookies.
534     currentWebEngineCookieStorePointer->deleteAllCookies();
535 }
536
537 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
538 {
539     // Delete the cookie.
540     currentWebEngineCookieStorePointer->deleteCookie(cookie);
541 }
542
543 void TabWidget::deleteTab(const int tabIndex)
544 {
545     // Get the privacy WebEngine view.
546     PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
547
548     // Proccess the tab delete according to the number of tabs.
549     if (tabWidgetPointer->count() > 1)  // There is more than one tab.
550     {
551         // Delete the tab.
552         tabWidgetPointer->removeTab(tabIndex);
553
554         // Delete the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
555         delete privacyWebEngineViewPointer->page();
556
557         // Delete the privacy WebEngine view.
558         delete privacyWebEngineViewPointer;
559     }
560     else  // There is only one tab.
561     {
562         // Close Privacy Browser.
563         window()->close();
564     }
565 }
566
567 void TabWidget::forward() const
568 {
569     // Go forward.
570     currentPrivacyWebEngineViewPointer->forward();
571 }
572
573 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
574 {
575     // Make it so.
576     emit fullScreenRequested(fullScreenRequest.toggleOn());
577
578     // Accept the request.
579     fullScreenRequest.accept();
580 }
581
582 std::list<QNetworkCookie>* TabWidget::getCookieList() const
583 {
584     // Return the current cookie list.
585     return currentPrivacyWebEngineViewPointer->cookieListPointer;
586 }
587
588 QString& TabWidget::getDomainSettingsName() const
589 {
590     // Return the domain settings name.
591     return currentPrivacyWebEngineViewPointer->domainSettingsName;
592 }
593
594 void TabWidget::home() const
595 {
596     // Load the homepage.
597     currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
598 }
599
600 void TabWidget::loadFinished() const
601 {
602     // Hide the progress bar.
603     emit hideProgressBar();
604 }
605
606 void TabWidget::loadInitialWebsite()
607 {
608     // Apply the application settings.
609     applyApplicationSettings();
610
611     // Get the arguments.
612     QStringList argumentsStringList = qApp->arguments();
613
614     // Check to see if the arguments lists contains a URL.
615     if (argumentsStringList.size() > 1)
616     {
617         // Load the URL from the arguments list.
618         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
619     }
620     else
621     {
622         // Load the homepage.
623         home();
624     }
625 }
626
627 void TabWidget::loadProgress(const int &progress) const
628 {
629     // Show the progress bar.
630     emit showProgressBar(progress);
631 }
632
633 void TabWidget::loadStarted() const
634 {
635     // Show the progress bar.
636     emit showProgressBar(0);
637 }
638
639 void TabWidget::loadUrlFromLineEdit(QString url) const
640 {
641     // Decide if the text is more likely to be a URL or a search.
642     if (url.startsWith("file://"))  // The text is likely a file URL.
643     {
644         // Load the URL.
645         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
646     }
647     else if (url.contains("."))  // The text is likely a URL.
648     {
649         // Check if the URL does not start with a valid protocol.
650         if (!url.startsWith("http"))
651         {
652             // Add `https://` to the beginning of the URL.
653             url = "https://" + url;
654         }
655
656         // Load the URL.
657         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
658     }
659     else  // The text is likely a search.
660     {
661         // Load the search.
662         currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
663     }
664 }
665
666 void TabWidget::mouseBack() const
667 {
668     // Go back if possible.
669     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
670     {
671         // Clear the URL line edit focus.
672         emit clearUrlLineEditFocus();
673
674         // Go back.
675         currentPrivacyWebEngineViewPointer->back();
676     }
677 }
678
679 void TabWidget::mouseForward() const
680 {
681     // Go forward if possible.
682     if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
683     {
684         // Clear the URL line edit focus.
685         emit clearUrlLineEditFocus();
686
687         // Go forward.
688         currentPrivacyWebEngineViewPointer->forward();
689     }
690 }
691
692 void TabWidget::pageLinkHovered(const QString &linkUrl) const
693 {
694     // Emit a signal so that the browser window can update the status bar.
695     emit linkHovered(linkUrl);
696 }
697
698 void TabWidget::print() const
699 {
700     // Create a printer.
701     QPrinter printer;
702
703     // Set the resolution to be 300 dpi.
704     printer.setResolution(300);
705
706     // Create a printer dialog.
707     QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
708
709     // Display the dialog and print the page if instructed.
710     if (printDialog.exec() == QDialog::Accepted)
711         printWebpage(&printer);
712 }
713
714 void TabWidget::printPreview() const
715 {
716     // Create a printer.
717     QPrinter printer;
718
719     // Set the resolution to be 300 dpi.
720     printer.setResolution(300);
721
722     // Create a print preview dialog.
723     QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
724
725     // Generate the print preview.
726     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
727
728     // Display the dialog.
729     printPreviewDialog.exec();
730 }
731
732 void TabWidget::printWebpage(QPrinter *printerPointer) const
733 {
734     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
735     QEventLoop eventLoop;
736
737     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
738     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
739     currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
740     {
741         // Instruct the compiler to ignore the unused parameter.
742         (void) printSuccess;
743
744         // Quit the loop.
745         eventLoop.quit();
746     });
747
748     // Execute the loop.
749     eventLoop.exec();
750 }
751
752 void TabWidget::refresh() const
753 {
754     // Reload the website.
755     currentPrivacyWebEngineViewPointer->reload();
756 }
757
758 void TabWidget::setTabBarVisible(const bool visible) const
759 {
760     // Set the tab bar visibility.
761     tabWidgetPointer->tabBar()->setVisible(visible);
762 }
763
764 void TabWidget::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
765 {
766     // Instantiate the save dialog.
767     SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
768
769     // Connect the save button.
770     connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
771
772     // Show the dialog.
773     saveDialogPointer->show();
774 }
775
776 void TabWidget::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
777 {
778     // Get the download location.
779     QString downloadDirectory = Settings::downloadLocation();
780
781     // Resolve the system download directory if specified.
782     if (downloadDirectory == QStringLiteral("System Download Directory"))
783         downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
784
785     // Create a save file dialog.
786     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
787
788     // Tell the dialog to use a save button.
789     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
790
791     // Populate the file name from the download item pointer.
792     saveFileDialogPointer->selectFile(suggestedFileName);
793
794     // Prevent interaction with the parent window while the dialog is open.
795     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
796
797     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
798     auto saveFile = [saveFileDialogPointer, &downloadUrl] () {
799         // Get the save location.  The dialog box should only allow the selecting of one file location.
800         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
801
802         // Create a file copy job.  `-1` creates the file with default permissions.
803         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
804
805         // Set the download job to display any error messages.
806         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
807
808         // Start the download.
809         fileCopyJobPointer->start();
810     };
811
812     // Handle clicks on the save button.
813     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
814
815     // Show the dialog.
816     saveFileDialogPointer->show();
817 }
818
819 void TabWidget::toggleDomStorage() const
820 {
821     // Toggle DOM storage.
822     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
823
824     // Update the DOM storage action.
825     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
826
827     // Reload the website.
828     currentPrivacyWebEngineViewPointer->reload();
829 }
830
831 void TabWidget::toggleJavaScript() const
832 {
833     // Toggle JavaScript.
834     currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
835
836     // Update the JavaScript action.
837     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
838
839     // Reload the website.
840     currentPrivacyWebEngineViewPointer->reload();
841 }
842
843 void TabWidget::toggleLocalStorage()
844 {
845     // Toggle local storeage.
846     currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
847
848     // Update the local storage action.
849     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
850
851     // Reload the website.
852     currentPrivacyWebEngineViewPointer->reload();
853 }
854
855 void TabWidget::updateUiWithTabSettings()
856 {
857     // Update the current WebEngine pointers.
858     currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
859     currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
860     currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
861     currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
862     currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
863     currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
864
865     // Clear the URL line edit focus.
866     emit clearUrlLineEditFocus();
867
868     // Update the UI.
869     emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QStringLiteral(""));
870     emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
871     emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
872     emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
873     emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
874     emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
875     emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
876     emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
877 }
878
879 void TabWidget::updateUrl(const QUrl &url) const
880 {
881     // Update the URL line edit.
882     emit updateUrlLineEdit(url);
883
884     // Update the status of the forward and back buttons.
885     emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
886     emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
887
888     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
889     currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
890 }