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 "databases/DomainsDatabase.h"
27 #include "dialogs/SaveDialog.h"
28 #include "filters/MouseEventFilter.h"
29 #include "helpers/SearchEngineHelper.h"
30 #include "interceptors/UrlRequestInterceptor.h"
31 #include "windows/BrowserWindow.h"
33 // KDE Framework headers.
34 #include <KIO/FileCopyJob>
35 #include <KIO/JobUiDelegate>
36 #include <KNotification>
38 // Qt toolkit headers.
40 #include <QFileDialog>
41 #include <QGraphicsScene>
42 #include <QGraphicsView>
43 #include <QPrintDialog>
44 #include <QPrintPreviewDialog>
47 // Initialize the public static variables.
48 QString TabWidget::webEngineDefaultUserAgent = QLatin1String("");
50 // Construct the class.
51 TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
53 // Instantiate the user agent helper.
54 userAgentHelperPointer = new UserAgentHelper();
56 // Instantiate the UIs.
57 Ui::TabWidget tabWidgetUi;
58 Ui::AddTabWidget addTabWidgetUi;
61 tabWidgetUi.setupUi(this);
63 // Get a handle for the tab widget.
64 tabWidgetPointer = tabWidgetUi.tabWidget;
66 // Setup the add tab UI.
67 addTabWidgetUi.setupUi(tabWidgetPointer);
69 // Get handles for the add tab widgets.
70 QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
71 QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
73 // Display the add tab widget.
74 tabWidgetPointer->setCornerWidget(addTabWidgetPointer);
79 // Process tab events.
80 connect(tabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
81 connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
82 connect(tabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
84 // Store a copy of the WebEngine default user agent.
85 webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
87 // Instantiate the mouse event filter pointer.
88 MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
90 // Install the mouse event filter.
91 qApp->installEventFilter(mouseEventFilterPointer);
93 // Process mouse forward and back commands.
94 connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
95 connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
98 TabWidget::~TabWidget()
100 // Manually delete each WebEngine page.
101 for (int i = 0; i < tabWidgetPointer->count(); ++i)
103 // Get the privacy WebEngine view.
104 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(i));
106 // Deletion the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
107 delete privacyWebEngineViewPointer->page();
111 // The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
112 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
117 // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be. <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
118 if (!cookie.domain().startsWith(QLatin1String(".")))
121 url.setHost(cookie.domain());
122 url.setScheme(QLatin1String("https"));
124 // Clear the domain from the cookie.
125 cookie.setDomain(QLatin1String(""));
128 // Add the cookie to the store.
129 if (webEngineCookieStorePointer == nullptr)
130 currentWebEngineCookieStorePointer->setCookie(cookie, url);
132 webEngineCookieStorePointer->setCookie(cookie, url);
135 void TabWidget::addFirstTab()
137 // Create the first tab.
140 // Update the UI with the tab settings.
141 updateUiWithTabSettings();
143 // Set the focus on the current tab widget. This prevents the tab bar from showing a blue bar under the label of the first tab.
144 tabWidgetPointer->currentWidget()->setFocus();
147 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
149 // Create a privacy WebEngine view.
150 PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
153 int newTabIndex = tabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
155 // Set the default tab icon.
156 tabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
158 // Create an off-the-record profile (the default when no profile name is specified).
159 QWebEngineProfile *webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));
161 // Create a WebEngine page.
162 QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
164 // Set the WebEngine page.
165 privacyWebEngineViewPointer->setPage(webEnginePagePointer);
167 // Get handles for the web engine elements.
168 QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
169 QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
171 // Update the URL line edit when the URL changes.
172 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
174 // Only update the UI if this is the current tab.
175 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
177 // Update the URL line edit.
178 emit updateUrlLineEdit(newUrl);
180 // Update the status of the forward and back buttons.
181 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
182 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
185 // Reapply the zoom factor. This is a bug in QWebEngineView that resets the zoom with every load. It can be removed once <https://redmine.stoutner.com/issues/799> is fixed.
186 privacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
189 // Update the progress bar when a load is started.
190 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
192 // Store the load progress.
193 privacyWebEngineViewPointer->loadProgressInt = 0;
195 // Show the progress bar if this is the current tab.
196 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
197 emit showProgressBar(0);
200 // Update the progress bar when a load progresses.
201 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
203 // Store the load progress.
204 privacyWebEngineViewPointer->loadProgressInt = progress;
206 // Update the progress bar if this is the current tab.
207 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
208 emit showProgressBar(progress);
211 // Update the progress bar when a load finishes.
212 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
214 // Store the load progress.
215 privacyWebEngineViewPointer->loadProgressInt = -1;
217 // Hide the progress bar if this is the current tab.
218 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
219 emit hideProgressBar();
222 // Update the zoom factor when changed by CTRL-Scrolling. This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
223 connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
225 // Only update the zoom factor action text if this is the current tab.
226 if (webEnginePagePointer == currentWebEnginePagePointer)
227 emit updateZoomFactorAction(webEnginePagePointer->zoomFactor());
230 // Display find text results.
231 connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
233 // Handle full screen requests.
234 connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
236 // Listen for hovered link URLs.
237 connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
239 // Handle file downloads.
240 connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
242 // Instantiate the URL request interceptor.
243 UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
245 // Set the URL request interceptor.
246 webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
248 // Reapply the domain settings when the host changes.
249 connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
251 // Set the local storage filter.
252 webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
254 // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
255 if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
257 //qDebug().noquote().nospace() << "Third-party request blocked: " << filterRequest.origin;
263 // Allow the request if local storage is enabled.
264 if (privacyWebEngineViewPointer->localStorageEnabled)
266 //qDebug().noquote().nospace() << "Request allowed by local storage: " << filterRequest.origin;
272 //qDebug().noquote().nospace() << "Request blocked by default: " << filterRequest.origin;
274 // Block any remaining local storage requests.
278 // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
279 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
281 // Don't allow JavaScript to open windows.
282 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
284 // Allow keyboard navigation.
285 webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
287 // Enable full screen support.
288 webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
290 // Require user interaction to play media.
291 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
293 // Limit WebRTC to public IP addresses.
294 webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
296 // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
297 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
299 // Plugins must be enabled for the PDF viewer to work. <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
300 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
302 // Update the cookies action.
303 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
305 // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
306 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
307 emit updateCookiesAction(numberOfCookies);
310 // Process cookie changes.
311 connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
312 connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
314 // Get a list of durable cookies.
315 QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
317 // Add the durable cookies to the store.
318 for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
319 addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
321 // Update the title when it changes.
322 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
324 // Get the index for this tab.
325 int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
327 // Update the title for this tab.
328 tabWidgetPointer->setTabText(tabIndex, title);
330 // Update the window title if this is the current tab.
331 if (tabIndex == tabWidgetPointer->currentIndex())
332 emit updateWindowTitle(title);
335 // Update the icon when it changes.
336 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
338 // Get the index for this tab.
339 int tabIndex = tabWidgetPointer->indexOf(privacyWebEngineViewPointer);
341 // Update the icon for this tab.
343 tabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
345 tabWidgetPointer->setTabIcon(tabIndex, icon);
348 // Enable spell checking.
349 webEngineProfilePointer->setSpellCheckEnabled(true);
351 // Set the spell check language.
352 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
354 // Populate the zoom factor. This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
355 privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
357 // Move to the new tab if it is not a background tab.
359 tabWidgetPointer->setCurrentIndex(newTabIndex);
361 // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
362 if (removeUrlLineEditFocus)
363 emit clearUrlLineEditFocus();
365 // Return the privacy WebEngine view pointer.
366 return privacyWebEngineViewPointer;
369 void TabWidget::applyApplicationSettings()
371 // Set the tab position.
372 if (Settings::tabsOnTop())
373 tabWidgetPointer->setTabPosition(QTabWidget::North);
375 tabWidgetPointer->setTabPosition(QTabWidget::South);
377 // Set the search engine URL.
378 searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
380 // Emit the update search engine actions signal.
381 emit updateSearchEngineActions(Settings::searchEngine(), true);
384 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
385 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
386 void TabWidget::applyDomainSettingsAndReload()
388 // Apply the domain settings. `true` reloads the website.
389 applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
392 // This exists as a separate function from `applyDomainSettings()` so it can be listed as a slot and function without the need for a boolean argument.
393 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
394 void TabWidget::applyDomainSettingsWithoutReloading(const QString &hostname)
396 // Apply the domain settings `false` does not reload the website.
397 applyDomainSettings(hostname, false);
400 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
401 void TabWidget::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
403 // Get the record for the hostname.
404 QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
406 // Check if the hostname has domain settings.
407 if (domainQuery.isValid()) // The hostname has domain settings.
409 // Get the domain record.
410 QSqlRecord domainRecord = domainQuery.record();
412 // Store the domain settings name.
413 currentPrivacyWebEngineViewPointer->domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();
415 // Set the JavaScript status.
416 switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
418 // Set the default JavaScript status.
419 case (DomainsDatabase::SYSTEM_DEFAULT):
421 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
426 // Disable JavaScript.
427 case (DomainsDatabase::DISABLED):
429 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
434 // Enable JavaScript.
435 case (DomainsDatabase::ENABLED):
437 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
443 // Set the local storage status.
444 switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
446 // Set the default local storage status.
447 case (DomainsDatabase::SYSTEM_DEFAULT):
449 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
454 // Disable local storage.
455 case (DomainsDatabase::DISABLED):
457 currentPrivacyWebEngineViewPointer->localStorageEnabled = false;
462 // Enable local storage.
463 case (DomainsDatabase::ENABLED):
465 currentPrivacyWebEngineViewPointer->localStorageEnabled = true;
471 // Set the DOM storage status.
472 switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
474 // Set the default DOM storage status. QWebEngineSettings confusingly calls this local storage.
475 case (DomainsDatabase::SYSTEM_DEFAULT):
477 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
482 // Disable DOM storage. QWebEngineSettings confusingly calls this local storage.
483 case (DomainsDatabase::DISABLED):
485 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
490 // Enable DOM storage. QWebEngineSettings confusingly calls this local storage.
491 case (DomainsDatabase::ENABLED):
493 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
499 // Set the user agent.
500 currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
502 // Check if a custom zoom factor is set.
503 if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
505 // Store the current zoom factor.
506 currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
510 // Reset the current zoom factor.
511 currentZoomFactor = Settings::zoomFactor();
514 // Set the zoom factor. The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
515 currentPrivacyWebEngineViewPointer->setZoomFactor(currentZoomFactor);
517 else // The hostname does not have domain settings.
519 // Reset the domain settings name.
520 currentPrivacyWebEngineViewPointer->domainSettingsName = QLatin1String("");
522 // Set the JavaScript status.
523 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
525 // Set the local storage status.
526 currentPrivacyWebEngineViewPointer->localStorageEnabled = Settings::localStorageEnabled();
528 // Set DOM storage. In QWebEngineSettings it is called Local Storage.
529 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
531 // Set the user agent.
532 currentWebEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
534 // Store the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
535 currentZoomFactor = Settings::zoomFactor();
537 // Set the zoom factor.
538 currentPrivacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
542 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
543 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
544 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
545 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
546 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
547 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
549 // Reload the website if requested.
551 currentPrivacyWebEngineViewPointer->reload();
554 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
556 // Store the search engine name.
557 QString searchEngineName = searchEngineActionPointer->text();
559 // Strip out any `&` characters.
560 searchEngineName.remove('&');
562 // Store the search engine string.
563 searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
565 // Update the search engine actions.
566 emit updateSearchEngineActions(searchEngineName, false);
569 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
571 // Get the user agent name.
572 QString userAgentName = userAgentActionPointer->text();
574 // Strip out any `&` characters.
575 userAgentName.remove('&');
577 // Apply the user agent.
578 currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
580 // Update the user agent actions.
581 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
583 // Reload the website.
584 currentPrivacyWebEngineViewPointer->reload();
587 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
588 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor)
590 // Update the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
591 currentZoomFactor = zoomFactor;
593 // Set the zoom factor.
594 currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
597 void TabWidget::applySpellCheckLanguages() const
599 // Get the number of tab.
600 int numberOfTabs = tabWidgetPointer->count();
602 // Set the spell check languages for each tab.
603 for (int i = 0; i < numberOfTabs; ++i)
605 // Get the WebEngine view pointer.
606 PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
608 // Get the WebEngine page pointer.
609 QWebEnginePage *webEnginePagePointer = webEngineViewPointer->page();
611 // Get the WebEngine profile pointer.
612 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
614 // Set the spell check languages.
615 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
619 void TabWidget::back() const
622 currentPrivacyWebEngineViewPointer->back();
625 void TabWidget::deleteAllCookies() const
627 // Delete all the cookies.
628 currentWebEngineCookieStorePointer->deleteAllCookies();
631 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
633 // Delete the cookie.
634 currentWebEngineCookieStorePointer->deleteCookie(cookie);
637 void TabWidget::deleteTab(const int tabIndex)
639 // Get the privacy WebEngine view.
640 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->widget(tabIndex));
642 // Proccess the tab delete according to the number of tabs.
643 if (tabWidgetPointer->count() > 1) // There is more than one tab.
646 tabWidgetPointer->removeTab(tabIndex);
648 // Delete the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
649 delete privacyWebEngineViewPointer->page();
651 // Delete the privacy WebEngine view.
652 delete privacyWebEngineViewPointer;
654 else // There is only one tab.
656 // Close Privacy Browser.
661 void TabWidget::findPrevious(const QString &text) const
663 // Store the current text.
664 currentPrivacyWebEngineViewPointer->findString = text;
666 // Find the previous text in the current privacy WebEngine.
667 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
668 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
670 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
673 void TabWidget::findText(const QString &text) const
675 // Store the current text.
676 currentPrivacyWebEngineViewPointer->findString = text;
678 // Find the text in the current privacy WebEngine.
679 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
680 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
682 currentPrivacyWebEngineViewPointer->findText(text);
684 // Clear the currently selected text in the WebEngine page if the find text is empty.
686 currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
689 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
691 // Update the find text UI if it wasn't simply wiping the current find text selection. Otherwise the UI temporarially flashes `0/0`.
692 if (wipingCurrentFindTextSelection) // The current selection is being wiped.
695 wipingCurrentFindTextSelection = false;
697 else // A new search has been performed.
700 currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
703 emit updateFindTextResults(findTextResult);
707 void TabWidget::forward() const
710 currentPrivacyWebEngineViewPointer->forward();
713 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
716 emit fullScreenRequested(fullScreenRequest.toggleOn());
718 // Accept the request.
719 fullScreenRequest.accept();
722 std::list<QNetworkCookie>* TabWidget::getCookieList() const
724 // Return the current cookie list.
725 return currentPrivacyWebEngineViewPointer->cookieListPointer;
728 QString& TabWidget::getDomainSettingsName() const
730 // Return the domain settings name.
731 return currentPrivacyWebEngineViewPointer->domainSettingsName;
734 void TabWidget::home() const
736 // Load the homepage.
737 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
740 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
742 // Apply the application settings.
743 applyApplicationSettings();
745 // Return the current privacy WebEngine view pointer.
746 return currentPrivacyWebEngineViewPointer;
749 void TabWidget::loadInitialWebsite()
751 // Apply the application settings.
752 applyApplicationSettings();
754 // Get the arguments.
755 QStringList argumentsStringList = qApp->arguments();
757 // Check to see if the arguments lists contains a URL.
758 if (argumentsStringList.size() > 1)
760 // Load the URL from the arguments list.
761 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
765 // Load the homepage.
770 void TabWidget::loadUrlFromLineEdit(QString url) const
772 // Decide if the text is more likely to be a URL or a search.
773 if (url.startsWith("file://")) // The text is likely a file URL.
776 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
778 else if (url.contains(".")) // The text is likely a URL.
780 // Check if the URL does not start with a valid protocol.
781 if (!url.startsWith("http"))
783 // Add `https://` to the beginning of the URL.
784 url = "https://" + url;
788 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
790 else // The text is likely a search.
793 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
797 void TabWidget::mouseBack() const
799 // Go back if possible.
800 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
802 // Clear the URL line edit focus.
803 emit clearUrlLineEditFocus();
806 currentPrivacyWebEngineViewPointer->back();
810 void TabWidget::mouseForward() const
812 // Go forward if possible.
813 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
815 // Clear the URL line edit focus.
816 emit clearUrlLineEditFocus();
819 currentPrivacyWebEngineViewPointer->forward();
823 void TabWidget::pageLinkHovered(const QString &linkUrl) const
825 // Emit a signal so that the browser window can update the status bar.
826 emit linkHovered(linkUrl);
829 void TabWidget::print() const
834 // Set the resolution to be 300 dpi.
835 printer.setResolution(300);
837 // Create a printer dialog.
838 QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
840 // Display the dialog and print the page if instructed.
841 if (printDialog.exec() == QDialog::Accepted)
842 printWebpage(&printer);
845 void TabWidget::printPreview() const
850 // Set the resolution to be 300 dpi.
851 printer.setResolution(300);
853 // Create a print preview dialog.
854 QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
856 // Generate the print preview.
857 connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
859 // Display the dialog.
860 printPreviewDialog.exec();
863 void TabWidget::printWebpage(QPrinter *printerPointer) const
865 // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
866 QEventLoop eventLoop;
868 // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
869 // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
870 currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
872 // Instruct the compiler to ignore the unused parameter.
883 void TabWidget::refresh() const
885 // Reload the website.
886 currentPrivacyWebEngineViewPointer->reload();
889 void TabWidget::setTabBarVisible(const bool visible) const
891 // Set the tab bar visibility.
892 tabWidgetPointer->tabBar()->setVisible(visible);
895 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
897 // Get the download attributes.
898 QUrl downloadUrl = webEngineDownloadItemPointer->url();
899 QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
900 QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
901 int totalBytes = webEngineDownloadItemPointer->totalBytes();
903 // Check to see if local storage (cookies) is enabled.
904 if (currentPrivacyWebEngineViewPointer->localStorageEnabled) // Local storage (cookies) is enabled. Use WebEngine's downloader.
906 // Instantiate the save dialog.
907 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
909 // Display the save dialog.
910 int saveDialogResult = saveDialogPointer->exec();
912 // Process the save dialog results.
913 if (saveDialogResult == QDialog::Accepted) // Save was selected.
915 // Get the download directory.
916 QString downloadDirectory = Settings::downloadLocation();
918 // Resolve the system download directory if specified.
919 if (downloadDirectory == QLatin1String("System Download Directory"))
920 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
922 // Display a save file dialog.
923 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
925 // Process the save file path.
926 if (!saveFilePath.isEmpty()) // The file save path is populated.
928 // Create a save file path file info.
929 QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
931 // Get the canonical save path and file name.
932 QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
933 QString saveFileName = saveFilePathFileInfo.fileName();
935 // Set the download directory and file name.
936 webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
937 webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
939 // Create a file download notification.
940 KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
942 // Set the notification title.
943 fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
945 // Set the notification text.
946 fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
948 // Set the notification icon.
949 fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
951 // Set the action list cancel button.
952 fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
954 // Set the notification to display indefinitely.
955 fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
957 // Prevent the notification from being autodeleted if it is closed. Otherwise, the updates to the notification below cause a crash.
958 fileDownloadNotificationPointer->setAutoDelete(false);
960 // Display the notification.
961 fileDownloadNotificationPointer->sendEvent();
963 // Handle clicks on the cancel button.
964 connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
966 // Cancel the download.
967 webEngineDownloadItemPointer->cancel();
969 // Create a file download notification.
970 KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
972 // Set the notification title.
973 canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
976 canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
978 // Set the notification icon.
979 canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
981 // Display the notification.
982 canceledDownloadNotificationPointer->sendEvent();
985 // Update the notification when the download progresses.
986 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
988 // Calculate the download percentage.
989 int downloadPercentage = 100 * bytesReceived / totalBytes;
991 // Set the new text. Total bytes will be 0 if the download size is unknown.
993 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
994 bytesReceived, totalBytes));
996 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1: %2 bytes downloaded", saveFileName, bytesReceived));
998 // Display the updated notification.
999 fileDownloadNotificationPointer->update();
1002 // Update the notification when the download finishes. The save file name must be copied into the lambda or a crash occurs.
1003 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
1005 // Set the new text.
1006 fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
1008 // Set the URL so the file options will be displayed.
1009 fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
1011 // Remove the actions from the notification.
1012 fileDownloadNotificationPointer->setActions(QStringList());
1014 // Set the notification to disappear after a timeout.
1015 fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
1017 // Display the updated notification.
1018 fileDownloadNotificationPointer->update();
1021 // Start the download.
1022 webEngineDownloadItemPointer->accept();
1024 else // The file save path is not populated.
1026 // Cancel the download.
1027 webEngineDownloadItemPointer->cancel();
1030 else // Cancel was selected.
1032 // Cancel the download.
1033 webEngineDownloadItemPointer->cancel();
1036 else // Local storage (cookies) is disabled. Use KDE's native downloader.
1037 // 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.
1039 // Instantiate the save dialog. `true` instructs it to use the native downloader
1040 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
1042 // Connect the save button.
1043 connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &)));
1046 saveDialogPointer->show();
1050 void TabWidget::toggleDomStorage() const
1052 // Toggle DOM storage.
1053 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1055 // Update the DOM storage action.
1056 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1058 // Reload the website.
1059 currentPrivacyWebEngineViewPointer->reload();
1062 void TabWidget::toggleFindCaseSensitive(const QString &text)
1064 // Toggle find case sensitive.
1065 currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
1067 // Set the wiping current find text selection flag.
1068 wipingCurrentFindTextSelection = true;
1070 // Wipe the previous search. Otherwise currently highlighted words will remain highlighted.
1071 findText(QLatin1String(""));
1073 // Update the find text.
1077 void TabWidget::toggleJavaScript() const
1079 // Toggle JavaScript.
1080 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1082 // Update the JavaScript action.
1083 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1085 // Reload the website.
1086 currentPrivacyWebEngineViewPointer->reload();
1089 void TabWidget::toggleLocalStorage()
1091 // Toggle local storage.
1092 currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
1094 // Update the local storage action.
1095 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1097 // Reload the website.
1098 currentPrivacyWebEngineViewPointer->reload();
1101 void TabWidget::updateUiWithTabSettings()
1103 // Update the current WebEngine pointers.
1104 currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(tabWidgetPointer->currentWidget());
1105 currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1106 currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1107 currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1108 currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1109 currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1111 // Clear the URL line edit focus.
1112 emit clearUrlLineEditFocus();
1114 // Update the actions.
1115 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1116 emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1117 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1118 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1119 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1120 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1121 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1122 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
1125 emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1126 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1127 emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
1129 // Update the find text.
1130 emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1131 emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1133 // Update the progress bar.
1134 if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1135 emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1137 emit hideProgressBar();
1140 void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1142 // Get the download directory.
1143 QString downloadDirectory = Settings::downloadLocation();
1145 // Resolve the system download directory if specified.
1146 if (downloadDirectory == QLatin1String("System Download Directory"))
1147 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1149 // Create a save file dialog.
1150 QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1152 // Tell the dialog to use a save button.
1153 saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1155 // Populate the file name from the download item pointer.
1156 saveFileDialogPointer->selectFile(suggestedFileName);
1158 // Prevent interaction with the parent window while the dialog is open.
1159 saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1161 // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1162 auto saveFile = [saveFileDialogPointer, downloadUrl] ()
1164 // Get the save location. The dialog box should only allow the selecting of one file location.
1165 QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1167 // Create a file copy job. `-1` creates the file with default permissions.
1168 KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1170 // Set the download job to display any error messages.
1171 fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1173 // Start the download.
1174 fileCopyJobPointer->start();
1177 // Handle clicks on the save button.
1178 connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1181 saveFileDialogPointer->show();