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