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