2 * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
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.
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.
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/>.
20 // Application headers.
21 #include "TabWidget.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"
31 // KDE Framework headers.
32 #include <KIO/FileCopyJob>
33 #include <KIO/JobUiDelegate>
34 #include <KNotification>
36 // Qt toolkit headers.
38 #include <QFileDialog>
39 #include <QGraphicsScene>
40 #include <QGraphicsView>
41 #include <QMessageBox>
42 #include <QPrintDialog>
43 #include <QPrintPreviewDialog>
46 // Initialize the public static variables.
47 QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
49 // Construct the class.
50 TabWidget::TabWidget(QWidget *windowPointer) : QWidget(windowPointer)
52 // Create a QProcess to check if KDE is running.
53 QProcess *checkIfRunningKdeQProcessPointer = new QProcess();
55 // Create an argument string list that contains `ksmserver` (KDE Session Manager).
56 QStringList argument = QStringList(QLatin1String("ksmserver"));
58 // Run `pidof` to check for the presence of `ksmserver`.
59 checkIfRunningKdeQProcessPointer->start(QLatin1String("pidof"), argument);
61 // Monitor any standard output.
62 connect(checkIfRunningKdeQProcessPointer, &QProcess::readyReadStandardOutput, [this]
64 // If there is any standard output, `ksmserver` is running.
68 // Instantiate the user agent helper.
69 userAgentHelperPointer = new UserAgentHelper();
71 // Instantiate the UIs.
72 Ui::TabWidget tabWidgetUi;
73 Ui::AddTabWidget addTabWidgetUi;
76 tabWidgetUi.setupUi(this);
78 // Get a handle for the tab widget.
79 qTabWidgetPointer = tabWidgetUi.tabWidget;
81 // Setup the add tab UI.
82 addTabWidgetUi.setupUi(qTabWidgetPointer);
84 // Get handles for the add tab widgets.
85 QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
86 QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
88 // Display the add tab widget.
89 qTabWidgetPointer->setCornerWidget(addTabWidgetPointer);
91 // Create the loading favorite icon movie.
92 loadingFavoriteIconMoviePointer = new QMovie();
94 // Set the loading favorite icon movie file name.
95 loadingFavoriteIconMoviePointer->setFileName(QStringLiteral(":/icons/loading.gif"));
97 // Stop the loading favorite icon movie if the window is destroyed. Otherwise, the app will crash if there is more than one window open and a window is closed while at tab is loading.
98 connect(windowPointer, SIGNAL(destroyed()), this, SLOT(stopLoadingFavoriteIconMovie()));
100 // Add the first tab.
103 // Process tab events.
104 connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
105 connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
106 connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
108 // Store a copy of the WebEngine default user agent.
109 webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
111 // Instantiate the mouse event filter pointer.
112 MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
114 // Install the mouse event filter.
115 qApp->installEventFilter(mouseEventFilterPointer);
117 // Process mouse forward and back commands.
118 connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
119 connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
122 TabWidget::~TabWidget()
124 // Get the number of tabs.
125 int numberOfTabs = qTabWidgetPointer->count();
127 // Manually delete each WebEngine page.
128 for (int i = 0; i < numberOfTabs; ++i)
130 // Get the privacy WebEngine view.
131 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
133 // Deletion the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
134 delete privacyWebEngineViewPointer->page();
138 // 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.
139 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
144 // 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>
145 if (!cookie.domain().startsWith(QLatin1String(".")))
148 url.setHost(cookie.domain());
149 url.setScheme(QLatin1String("https"));
151 // Clear the domain from the cookie.
152 cookie.setDomain(QLatin1String(""));
155 // Add the cookie to the store.
156 if (webEngineCookieStorePointer == nullptr)
157 currentWebEngineCookieStorePointer->setCookie(cookie, url);
159 webEngineCookieStorePointer->setCookie(cookie, url);
162 void TabWidget::addFirstTab()
164 // Create the first tab.
167 // Update the UI with the tab settings.
168 updateUiWithTabSettings();
170 // 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.
171 qTabWidgetPointer->currentWidget()->setFocus();
174 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab, const QString urlString)
176 // Create a privacy WebEngine view.
177 PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
180 int newTabIndex = qTabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
182 // Set the default tab icon.
183 qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
185 // Get handles for the WebEngine page and profile.
186 QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
187 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
189 // Get handles for the web engine elements.
190 QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
191 QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
193 // Update the URL line edit when the URL changes.
194 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [this, privacyWebEngineViewPointer] (const QUrl &newUrl)
196 // Only update the UI if this is the current tab.
197 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
199 // Update the URL line edit.
200 emit updateUrlLineEdit(newUrl);
202 // Update the status of the forward and back buttons.
203 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
204 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
208 // Update the title when it changes.
209 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
211 // Get the index for this tab.
212 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
214 // Update the title for this tab.
215 qTabWidgetPointer->setTabText(tabIndex, title);
217 // Update the window title if this is the current tab.
218 if (tabIndex == qTabWidgetPointer->currentIndex())
219 emit updateWindowTitle(title);
222 // Connect the loading favorite icon movie to the tab icon.
223 connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [this, privacyWebEngineViewPointer]
225 // Get the index for this tab.
226 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
228 // Display the loading favorite icon if this tab is loading.
229 if (privacyWebEngineViewPointer->isLoading)
230 qTabWidgetPointer->setTabIcon(tabIndex, loadingFavoriteIconMoviePointer->currentPixmap());
233 // Update the icon when it changes.
234 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [this, privacyWebEngineViewPointer] (const QIcon &newFavoriteIcon)
236 // Store the favorite icon in the privacy web engine view.
237 if (newFavoriteIcon.isNull())
238 privacyWebEngineViewPointer->favoriteIcon = defaultFavoriteIcon;
240 privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;
242 // Get the index for this tab.
243 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
245 // Update the icon for this tab.
246 if (newFavoriteIcon.isNull())
247 qTabWidgetPointer->setTabIcon(tabIndex, defaultFavoriteIcon);
249 qTabWidgetPointer->setTabIcon(tabIndex, newFavoriteIcon);
252 // Update the progress bar and the favorite icon when a load is started.
253 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [this, privacyWebEngineViewPointer] ()
255 // Set the privacy web engine view to be loading.
256 privacyWebEngineViewPointer->isLoading = true;
258 // Store the load progress.
259 privacyWebEngineViewPointer->loadProgressInt = 0;
261 // Show the progress bar if this is the current tab.
262 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
263 emit showProgressBar(0);
265 // Start the loading favorite icon movie.
266 loadingFavoriteIconMoviePointer->start();
269 // Update the progress bar when a load progresses.
270 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [this, privacyWebEngineViewPointer] (const int progress)
272 // Store the load progress.
273 privacyWebEngineViewPointer->loadProgressInt = progress;
275 // Update the progress bar if this is the current tab.
276 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
277 emit showProgressBar(progress);
280 // Update the progress bar when a load finishes.
281 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [this, privacyWebEngineViewPointer] ()
283 // Set the privacy web engine view to be not loading.
284 privacyWebEngineViewPointer->isLoading = false;
286 // Store the load progress.
287 privacyWebEngineViewPointer->loadProgressInt = -1;
289 // Hide the progress bar if this is the current tab.
290 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
291 emit hideProgressBar();
293 // Get the index for this tab.
294 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
296 // Display the current favorite icon
297 qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
299 // Create a no tabs loading variable.
300 bool noTabsLoading = true;
302 // Get the number of tabs.
303 int numberOfTabs = qTabWidgetPointer->count();
305 // Check to see if any other tabs are loading.
306 for (int i = 0; i < numberOfTabs; i++)
308 // Get the privacy WebEngine view for the tab.
309 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView*>(qTabWidgetPointer->widget(i));
311 // Check to see if it is currently loading. If at least one tab is loading, this flag will end up being marked `false` when the for loop has finished.
312 if (privacyWebEngineViewPointer->isLoading)
313 noTabsLoading = false;
316 // Stop the loading favorite icon movie if there are no loading tabs.
318 loadingFavoriteIconMoviePointer->stop();
321 // Display HTTP Ping blocked dialogs.
322 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [this, privacyWebEngineViewPointer] (const QString &httpPingUrl)
324 // Only display the HTTP Ping blocked dialog if this is the current tab.
325 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
327 // Instantiate an HTTP ping blocked message box.
328 QMessageBox httpPingBlockedMessageBox;
331 httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
333 // Set the window title.
334 httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
337 httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));
339 // Set the standard button.
340 httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
342 // Display the message box.
343 httpPingBlockedMessageBox.exec();
347 // Update the zoom actions when changed by CTRL-Scrolling. This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
348 connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
350 // Only update the zoom actions if this is the current tab.
351 if (webEnginePagePointer == currentWebEnginePagePointer)
352 emit updateZoomActions(webEnginePagePointer->zoomFactor());
355 // Display find text results.
356 connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
358 // Handle full screen requests.
359 connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
361 // Listen for hovered link URLs.
362 connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
364 // Handle file downloads.
365 connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
367 // Set the local storage filter.
368 webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
370 // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
371 if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
373 //qDebug().noquote().nospace() << "Third-party request blocked: " << filterRequest.origin;
379 // Allow the request if local storage is enabled.
380 if (privacyWebEngineViewPointer->localStorageEnabled)
382 //qDebug().noquote().nospace() << "Request allowed by local storage: " << filterRequest.origin;
388 //qDebug().noquote().nospace() << "Request blocked by default: " << filterRequest.origin;
390 // Block any remaining local storage requests.
394 // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
395 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
397 // Don't allow JavaScript to open windows.
398 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
400 // Allow keyboard navigation between links and input fields.
401 webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
403 // Enable full screen support.
404 webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
406 // Require user interaction to play media.
407 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
409 // Limit WebRTC to public IP addresses.
410 webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
412 // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
413 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
415 // Plugins must be enabled for the PDF viewer to work. <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
416 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
418 // Update the cookies action.
419 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [this, privacyWebEngineViewPointer] (const int numberOfCookies)
421 // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
422 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
423 emit updateCookiesAction(numberOfCookies);
426 // Process cookie changes.
427 connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
428 connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
430 // Get a list of durable cookies.
431 QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
433 // Add the durable cookies to the store.
434 for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
435 addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
437 // Enable spell checking.
438 webEngineProfilePointer->setSpellCheckEnabled(true);
440 // Set the spell check language.
441 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
443 // Populate the zoom factor. This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
444 privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
446 // Update the UI when domain settings are applied.
447 connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
449 // Move to the new tab if it is not a background tab.
451 qTabWidgetPointer->setCurrentIndex(newTabIndex);
453 // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
454 if (removeUrlLineEditFocus)
455 emit clearUrlLineEditFocus();
457 if (urlString != nullptr)
458 privacyWebEngineViewPointer->load(QUrl::fromUserInput(urlString));
460 // Return the privacy WebEngine view pointer.
461 return privacyWebEngineViewPointer;
464 void TabWidget::applyApplicationSettings()
466 // Set the tab position.
467 if (Settings::tabsOnTop())
468 qTabWidgetPointer->setTabPosition(QTabWidget::North);
470 qTabWidgetPointer->setTabPosition(QTabWidget::South);
472 // Get the number of tabs.
473 int numberOfTabs = qTabWidgetPointer->count();
475 // Apply the spatial navigation settings to each WebEngine.
476 for (int i = 0; i < numberOfTabs; ++i) {
477 // Get the WebEngine view pointer.
478 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
480 // Apply the spatial navigation settings to each page.
481 privacyWebEngineViewPointer->page()->settings()->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, Settings::spatialNavigation());
484 // Set the search engine URL.
485 searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
487 // Emit the update search engine actions signal.
488 emit updateSearchEngineActions(Settings::searchEngine(), true);
491 void TabWidget::applyDomainSettingsAndReload()
493 // Get the number of tabs.
494 int numberOfTabs = qTabWidgetPointer->count();
496 // Apply the domain settings to each WebEngine.
497 for (int i = 0; i < numberOfTabs; ++i) {
498 // Get the WebEngine view pointer.
499 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
501 // Apply the spatial navigation settings to each page.
502 privacyWebEngineViewPointer->applyDomainSettings(privacyWebEngineViewPointer->url().host(), true);
506 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
508 // Store the search engine name.
509 QString searchEngineName = searchEngineActionPointer->text();
511 // Strip out any `&` characters.
512 searchEngineName.remove('&');
514 // Store the search engine string.
515 searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
517 // Update the search engine actions.
518 emit updateSearchEngineActions(searchEngineName, false);
521 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
523 // Get the user agent name.
524 QString userAgentName = userAgentActionPointer->text();
526 // Strip out any `&` characters.
527 userAgentName.remove('&');
529 // Apply the user agent.
530 currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
532 // Update the user agent actions.
533 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
535 // Reload the website.
536 currentPrivacyWebEngineViewPointer->reload();
539 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor) const
541 // Set the zoom factor.
542 currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
545 void TabWidget::applySpellCheckLanguages() const
547 // Get the number of tab.
548 int numberOfTabs = qTabWidgetPointer->count();
550 // Set the spell check languages for each tab.
551 for (int i = 0; i < numberOfTabs; ++i)
553 // Get the WebEngine view pointer.
554 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
556 // Get the WebEngine page pointer.
557 QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
559 // Get the WebEngine profile pointer.
560 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
562 // Set the spell check languages.
563 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
567 void TabWidget::back() const
570 currentPrivacyWebEngineViewPointer->back();
573 void TabWidget::deleteAllCookies() const
575 // Delete all the cookies.
576 currentWebEngineCookieStorePointer->deleteAllCookies();
579 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
581 // Delete the cookie.
582 currentWebEngineCookieStorePointer->deleteCookie(cookie);
585 void TabWidget::deleteTab(const int tabIndex)
587 // Get the privacy WebEngine view.
588 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(tabIndex));
590 // Process the tab delete according to the number of tabs.
591 if (qTabWidgetPointer->count() > 1) // There is more than one tab.
594 qTabWidgetPointer->removeTab(tabIndex);
596 // Delete the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
597 delete privacyWebEngineViewPointer->page();
599 // Delete the privacy WebEngine view.
600 delete privacyWebEngineViewPointer;
602 else // There is only one tab.
604 // Close Privacy Browser.
609 void TabWidget::findPrevious(const QString &text) const
611 // Store the current text.
612 currentPrivacyWebEngineViewPointer->findString = text;
614 // Find the previous text in the current privacy WebEngine.
615 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
616 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
618 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
621 void TabWidget::findText(const QString &text) const
623 // Store the current text.
624 currentPrivacyWebEngineViewPointer->findString = text;
626 // Find the text in the current privacy WebEngine.
627 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
628 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
630 currentPrivacyWebEngineViewPointer->findText(text);
632 // Clear the currently selected text in the WebEngine page if the find text is empty.
634 currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
637 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
639 // Update the find text UI if it wasn't simply wiping the current find text selection. Otherwise the UI temporarily flashes `0/0`.
640 if (wipingCurrentFindTextSelection) // The current selection is being wiped.
643 wipingCurrentFindTextSelection = false;
645 else // A new search has been performed.
648 currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
651 emit updateFindTextResults(findTextResult);
655 void TabWidget::forward() const
658 currentPrivacyWebEngineViewPointer->forward();
661 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
664 emit fullScreenRequested(fullScreenRequest.toggleOn());
666 // Accept the request.
667 fullScreenRequest.accept();
670 std::list<QNetworkCookie>* TabWidget::getCookieList() const
672 // Return the current cookie list.
673 return currentPrivacyWebEngineViewPointer->cookieListPointer;
676 QIcon TabWidget::getCurrentTabFavoritIcon() const
678 // Return the current Privacy WebEngine favorite icon.
679 return currentPrivacyWebEngineViewPointer->favoriteIcon;
682 QString TabWidget::getCurrentTabTitle() const
684 // Return the current Privacy WebEngine title.
685 return currentPrivacyWebEngineViewPointer->title();
688 QString TabWidget::getCurrentTabUrl() const
690 // Return the current Privacy WebEngine URL as a string.
691 return currentPrivacyWebEngineViewPointer->url().toString();
694 QString& TabWidget::getDomainSettingsName() const
696 // Return the domain settings name.
697 return currentPrivacyWebEngineViewPointer->domainSettingsName;
700 void TabWidget::home() const
702 // Load the homepage.
703 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
706 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
708 // Apply the application settings.
709 applyApplicationSettings();
711 // Return the current privacy WebEngine view pointer.
712 return currentPrivacyWebEngineViewPointer;
715 void TabWidget::loadInitialWebsite()
717 // Apply the application settings.
718 applyApplicationSettings();
720 // Get the arguments.
721 QStringList argumentsStringList = qApp->arguments();
723 // Check to see if the arguments lists contains a URL.
724 if (argumentsStringList.size() > 1)
726 // Load the URL from the arguments list.
727 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
731 // Load the homepage.
736 void TabWidget::loadUrlFromLineEdit(QString url) const
738 // Decide if the text is more likely to be a URL or a search.
739 if (url.startsWith("file://") || url.startsWith("view-source:")) // The text is likely a file or view source URL.
742 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
744 else if (url.contains(".")) // The text is likely a URL.
746 // Check if the URL does not start with a valid protocol.
747 if (!url.startsWith("http"))
749 // Add `https://` to the beginning of the URL.
750 url = "https://" + url;
754 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
756 else // The text is likely a search.
759 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
763 void TabWidget::mouseBack() const
765 // Go back if possible.
766 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
768 // Clear the URL line edit focus.
769 emit clearUrlLineEditFocus();
772 currentPrivacyWebEngineViewPointer->back();
776 void TabWidget::mouseForward() const
778 // Go forward if possible.
779 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
781 // Clear the URL line edit focus.
782 emit clearUrlLineEditFocus();
785 currentPrivacyWebEngineViewPointer->forward();
789 void TabWidget::pageLinkHovered(const QString &linkUrl) const
791 // Emit a signal so that the browser window can update the status bar.
792 emit linkHovered(linkUrl);
795 void TabWidget::stopLoadingFavoriteIconMovie() const
797 // Stop the loading favorite icon movie. Otherwise, the browser will crash if a second window is closed while a tab in it is loading. <https://redmine.stoutner.com/issues/1010>
798 loadingFavoriteIconMoviePointer->stop();
801 void TabWidget::print() const
806 // Set the resolution to be 300 dpi.
807 printer.setResolution(300);
809 // Create a printer dialog.
810 QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
812 // Display the dialog and print the page if instructed.
813 if (printDialog.exec() == QDialog::Accepted)
814 printWebpage(&printer);
817 void TabWidget::printPreview() const
822 // Set the resolution to be 300 dpi.
823 printer.setResolution(300);
825 // Create a print preview dialog.
826 QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
828 // Generate the print preview.
829 connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
831 // Display the dialog.
832 printPreviewDialog.exec();
835 void TabWidget::printWebpage(QPrinter *printerPointer) const
837 // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
838 QEventLoop eventLoop;
840 // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
841 // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
842 currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
844 // Instruct the compiler to ignore the unused parameter.
855 void TabWidget::refresh() const
857 // Reload the website.
858 currentPrivacyWebEngineViewPointer->reload();
861 void TabWidget::reloadAndBypassCache() const
863 // Reload the website, bypassing the cache.
864 currentWebEnginePagePointer->triggerAction(QWebEnginePage::ReloadAndBypassCache);
867 void TabWidget::saveArchive()
869 // Get the suggested file name.
870 QString suggestedFileName = currentPrivacyWebEngineViewPointer->url().host() + ".mht";
872 // Get the download directory.
873 QString downloadDirectory = Settings::downloadLocation();
875 // Resolve the system download directory if specified.
876 if (downloadDirectory == QLatin1String("System Download Directory"))
877 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
879 // Get a file path from the file picker.
880 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
882 // Save the webpage as an archive if the file save path is populated.
883 if (!saveFilePath.isEmpty())
885 // Set the saving archive flag. Otherwise, a second download tries to run.
886 savingArchive = true;
889 currentWebEnginePagePointer->save(saveFilePath);
893 void TabWidget::setTabBarVisible(const bool visible) const
895 // Set the tab bar visibility.
896 qTabWidgetPointer->tabBar()->setVisible(visible);
899 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
901 // Only show the save dialog if an archive is not currently being saved. Otherwise, two save dialogs will be shown.
904 // Get the download attributes.
905 QUrl downloadUrl = webEngineDownloadItemPointer->url();
906 QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
907 QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
908 int totalBytes = webEngineDownloadItemPointer->totalBytes();
910 // Check to see if Privacy Browser is not running KDE or if local storage (cookies) is enabled.
911 if (!isRunningKde || currentPrivacyWebEngineViewPointer->localStorageEnabled) // KDE is not running or local storage (cookies) is enabled. Use WebEngine's downloader.
913 // Instantiate the save dialog.
914 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
916 // Display the save dialog.
917 int saveDialogResult = saveDialogPointer->exec();
919 // Process the save dialog results.
920 if (saveDialogResult == QDialog::Accepted) // Save was selected.
922 // Get the download directory.
923 QString downloadDirectory = Settings::downloadLocation();
925 // Resolve the system download directory if specified.
926 if (downloadDirectory == QLatin1String("System Download Directory"))
927 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
929 // Get a file path from the file picker.
930 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
932 // Process the save file path.
933 if (!saveFilePath.isEmpty()) // The file save path is populated.
935 // Create a save file path file info.
936 QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
938 // Get the canonical save path and file name.
939 QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
940 QString saveFileName = saveFilePathFileInfo.fileName();
942 // Set the download directory and file name.
943 webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
944 webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
946 // Create a file download notification.
947 KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
949 // Set the notification title.
950 fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
952 // Set the notification text.
953 fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
955 // Get the download icon from the theme.
956 QIcon downloadIcon = QIcon::fromTheme(QLatin1String("download"), QIcon::fromTheme(QLatin1String("document-save")));
958 // Set the notification icon.
959 fileDownloadNotificationPointer->setIconName(downloadIcon.name());
961 // Set the action list cancel button.
962 fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
964 // Prevent the notification from being autodeleted if it is closed. Otherwise, the updates to the notification below cause a crash.
965 fileDownloadNotificationPointer->setAutoDelete(false);
967 // Handle clicks on the cancel button.
968 connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
970 // Cancel the download.
971 webEngineDownloadItemPointer->cancel();
973 // Create a file download notification.
974 KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
976 // Set the notification title.
977 canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
980 canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
982 // Set the notification icon.
983 canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
985 // Display the notification.
986 canceledDownloadNotificationPointer->sendEvent();
989 // Update the notification when the download progresses.
990 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
992 // Set the new text. Total bytes will be 0 if the download size is unknown.
995 // Calculate the download percentage.
996 int downloadPercentage = 100 * bytesReceived / totalBytes;
998 // Set the file download notification text.
999 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
1000 bytesReceived, totalBytes));
1004 // Set the file download notification text.
1005 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1: %2 bytes downloaded", saveFileName, bytesReceived));
1008 // Display the updated notification.
1009 fileDownloadNotificationPointer->update();
1012 // Update the notification when the download finishes. The save file name must be copied into the lambda or a crash occurs.
1013 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
1015 // Set the new text.
1016 fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
1018 // Set the URL so the file options will be displayed.
1019 fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
1021 // Remove the actions from the notification.
1022 fileDownloadNotificationPointer->setActions(QStringList());
1024 // Set the notification to disappear after a timeout.
1025 fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
1027 // Display the updated notification.
1028 fileDownloadNotificationPointer->update();
1031 // Display the notification.
1032 fileDownloadNotificationPointer->sendEvent();
1034 // Start the download.
1035 webEngineDownloadItemPointer->accept();
1037 else // The file save path is not populated.
1039 // Cancel the download.
1040 webEngineDownloadItemPointer->cancel();
1043 else // Cancel was selected.
1045 // Cancel the download.
1046 webEngineDownloadItemPointer->cancel();
1049 else // KDE is running and local storage (cookies) is disabled. Use KDE's native downloader.
1050 // 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.
1052 // Instantiate the save dialog. `true` instructs it to use the native downloader
1053 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
1055 // Connect the save button.
1056 connect(saveDialogPointer, SIGNAL(useNativeKdeDownloader(QUrl &, QString &)), this, SLOT(useNativeKdeDownloader(QUrl &, QString &)));
1059 saveDialogPointer->show();
1063 // Reset the saving archive flag.
1064 savingArchive = false;
1067 void TabWidget::toggleDomStorage() const
1069 // Toggle DOM storage.
1070 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1072 // Update the DOM storage action.
1073 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1075 // Reload the website.
1076 currentPrivacyWebEngineViewPointer->reload();
1079 void TabWidget::toggleFindCaseSensitive(const QString &text)
1081 // Toggle find case sensitive.
1082 currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
1084 // Set the wiping current find text selection flag.
1085 wipingCurrentFindTextSelection = true;
1087 // Wipe the previous search. Otherwise currently highlighted words will remain highlighted.
1088 findText(QLatin1String(""));
1090 // Update the find text.
1094 void TabWidget::toggleJavaScript() const
1096 // Toggle JavaScript.
1097 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1099 // Update the JavaScript action.
1100 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1102 // Reload the website.
1103 currentPrivacyWebEngineViewPointer->reload();
1106 void TabWidget::toggleLocalStorage()
1108 // Toggle local storage.
1109 currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
1111 // Update the local storage action.
1112 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1114 // Reload the website.
1115 currentPrivacyWebEngineViewPointer->reload();
1118 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
1120 // Only update the UI if the signal was emitted from the current privacy WebEngine.
1121 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
1124 emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1125 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1126 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1127 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1128 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1129 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1130 emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1134 void TabWidget::updateUiWithTabSettings()
1136 // Update the current WebEngine pointers.
1137 currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
1138 currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1139 currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1140 currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1141 currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1142 currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1144 // Clear the URL line edit focus.
1145 emit clearUrlLineEditFocus();
1147 // Update the actions.
1148 emit updateDefaultZoomFactor(currentPrivacyWebEngineViewPointer->defaultZoomFactor);
1149 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1150 emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1151 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1152 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1153 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1154 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1155 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1156 emit updateZoomActions(currentPrivacyWebEngineViewPointer->zoomFactor());
1159 emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1160 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1161 emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
1163 // Update the find text.
1164 emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1165 emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1167 // Update the progress bar.
1168 if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1169 emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1171 emit hideProgressBar();
1174 void TabWidget::useNativeKdeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1176 // Get the download directory.
1177 QString downloadDirectory = Settings::downloadLocation();
1179 // Resolve the system download directory if specified.
1180 if (downloadDirectory == QLatin1String("System Download Directory"))
1181 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1183 // Create a save file dialog.
1184 QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1186 // Tell the dialog to use a save button.
1187 saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1189 // Populate the file name from the download item pointer.
1190 saveFileDialogPointer->selectFile(suggestedFileName);
1192 // Prevent interaction with the parent window while the dialog is open.
1193 saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1195 // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1196 auto saveFile = [saveFileDialogPointer, downloadUrl] ()
1198 // Get the save location. The dialog box should only allow the selecting of one file location.
1199 QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1201 // Create a file copy job. `-1` creates the file with default permissions.
1202 KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1204 // Set the download job to display any warning and error messages.
1205 fileCopyJobPointer->uiDelegate()->setAutoWarningHandlingEnabled(true);
1206 fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1208 // Start the download.
1209 fileCopyJobPointer->start();
1212 // Handle clicks on the save button.
1213 connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1216 saveFileDialogPointer->show();