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