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