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 *parent) : QWidget(parent)
52 // Instantiate the user agent helper.
53 userAgentHelperPointer = new UserAgentHelper();
55 // Instantiate the UIs.
56 Ui::TabWidget tabWidgetUi;
57 Ui::AddTabWidget addTabWidgetUi;
60 tabWidgetUi.setupUi(this);
62 // Get a handle for the tab widget.
63 qTabWidgetPointer = tabWidgetUi.tabWidget;
65 // Setup the add tab UI.
66 addTabWidgetUi.setupUi(qTabWidgetPointer);
68 // Get handles for the add tab widgets.
69 QWidget *addTabWidgetPointer = addTabWidgetUi.addTabQWidget;
70 QPushButton *addTabButtonPointer = addTabWidgetUi.addTabButton;
72 // Display the add tab widget.
73 qTabWidgetPointer->setCornerWidget(addTabWidgetPointer);
75 // Create the loading favorite icon movie.
76 loadingFavoriteIconMoviePointer = new QMovie();
78 // Set the loading favorite icon movie file name.
79 loadingFavoriteIconMoviePointer->setFileName(QStringLiteral(":/icons/loading.gif"));
84 // Process tab events.
85 connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
86 connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
87 connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
89 // Store a copy of the WebEngine default user agent.
90 webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
92 // Instantiate the mouse event filter pointer.
93 MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
95 // Install the mouse event filter.
96 qApp->installEventFilter(mouseEventFilterPointer);
98 // Process mouse forward and back commands.
99 connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
100 connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
103 TabWidget::~TabWidget()
105 // Manually delete each WebEngine page.
106 for (int i = 0; i < qTabWidgetPointer->count(); ++i)
108 // Get the privacy WebEngine view.
109 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
111 // Deletion the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
112 delete privacyWebEngineViewPointer->page();
116 // The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
117 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
122 // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be. <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
123 if (!cookie.domain().startsWith(QLatin1String(".")))
126 url.setHost(cookie.domain());
127 url.setScheme(QLatin1String("https"));
129 // Clear the domain from the cookie.
130 cookie.setDomain(QLatin1String(""));
133 // Add the cookie to the store.
134 if (webEngineCookieStorePointer == nullptr)
135 currentWebEngineCookieStorePointer->setCookie(cookie, url);
137 webEngineCookieStorePointer->setCookie(cookie, url);
140 void TabWidget::addFirstTab()
142 // Create the first tab.
145 // Update the UI with the tab settings.
146 updateUiWithTabSettings();
148 // Set the focus on the current tab widget. This prevents the tab bar from showing a blue bar under the label of the first tab.
149 qTabWidgetPointer->currentWidget()->setFocus();
152 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
154 // Create a privacy WebEngine view.
155 PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
158 int newTabIndex = qTabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
160 // Set the default tab icon.
161 qTabWidgetPointer->setTabIcon(newTabIndex, defaultFavoriteIcon);
163 // Get handles for the WebEngine page and profile.
164 QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
165 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
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());
186 // Update the title when it changes.
187 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
189 // Get the index for this tab.
190 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
192 // Update the title for this tab.
193 qTabWidgetPointer->setTabText(tabIndex, title);
195 // Update the window title if this is the current tab.
196 if (tabIndex == qTabWidgetPointer->currentIndex())
197 emit updateWindowTitle(title);
200 // Connect the loading favorite icon movie to the tab icon.
201 connect(loadingFavoriteIconMoviePointer, &QMovie::frameChanged, [privacyWebEngineViewPointer, this]
203 // Get the index for this tab.
204 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
206 // Display the loading favorite icon if this tab is loading.
207 if (privacyWebEngineViewPointer->isLoading)
208 qTabWidgetPointer->setTabIcon(tabIndex, loadingFavoriteIconMoviePointer->currentPixmap());
211 // Update the icon when it changes.
212 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &newFavoriteIcon)
214 // Store the favorite icon in the privacy web engine view.
215 if (newFavoriteIcon.isNull())
216 privacyWebEngineViewPointer->favoriteIcon = defaultFavoriteIcon;
218 privacyWebEngineViewPointer->favoriteIcon = newFavoriteIcon;
220 // Get the index for this tab.
221 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
223 // Update the icon for this tab.
224 if (newFavoriteIcon.isNull())
225 qTabWidgetPointer->setTabIcon(tabIndex, defaultFavoriteIcon);
227 qTabWidgetPointer->setTabIcon(tabIndex, newFavoriteIcon);
230 // Update the progress bar and the favorite icon when a load is started.
231 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
233 // Set the privacy web engine view to be loading.
234 privacyWebEngineViewPointer->isLoading = true;
236 // Store the load progress.
237 privacyWebEngineViewPointer->loadProgressInt = 0;
239 // Show the progress bar if this is the current tab.
240 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
241 emit showProgressBar(0);
243 // Start the loading favorite icon movie.
244 loadingFavoriteIconMoviePointer->start();
247 // Update the progress bar when a load progresses.
248 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
250 // Store the load progress.
251 privacyWebEngineViewPointer->loadProgressInt = progress;
253 // Update the progress bar if this is the current tab.
254 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
255 emit showProgressBar(progress);
258 // Update the progress bar when a load finishes.
259 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
261 // Set the privacy web engine view to be not loading.
262 privacyWebEngineViewPointer->isLoading = false;
264 // Store the load progress.
265 privacyWebEngineViewPointer->loadProgressInt = -1;
267 // Hide the progress bar if this is the current tab.
268 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
269 emit hideProgressBar();
271 // Get the index for this tab.
272 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
274 // Display the current favorite icon
275 qTabWidgetPointer->setTabIcon(tabIndex, privacyWebEngineViewPointer->favoriteIcon);
277 // Create a no tabs loading variable.
278 bool noTabsLoading = true;
280 // Check to see if any other tabs are loading.
281 for (int i = 0; i < qTabWidgetPointer->count(); i++)
283 // Get the privacy WebEngine view for the tab.
284 PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView*>(qTabWidgetPointer->widget(i));
286 // Check to see if it is currently loading.
287 if (webEngineViewPointer->isLoading)
288 noTabsLoading = false;
291 // Stop the loading favorite icon movie if there are no loading tabs.
293 loadingFavoriteIconMoviePointer->stop();
296 // Display HTTP Ping blocked dialogs.
297 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [privacyWebEngineViewPointer, this] (const QString &httpPingUrl)
299 // Only display the HTTP Ping blocked dialog if this is the current tab.
300 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
302 // Instantiate an HTTP ping blocked message box.
303 QMessageBox httpPingBlockedMessageBox;
306 httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
308 // Set the window title.
309 httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
312 httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));
314 // Set the standard button.
315 httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
317 // Display the message box.
318 httpPingBlockedMessageBox.exec();
322 // Update the zoom factor when changed by CTRL-Scrolling. This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
323 connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
325 // Only update the zoom factor action text if this is the current tab.
326 if (webEnginePagePointer == currentWebEnginePagePointer)
327 emit updateZoomFactorAction(webEnginePagePointer->zoomFactor());
330 // Display find text results.
331 connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
333 // Handle full screen requests.
334 connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
336 // Listen for hovered link URLs.
337 connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
339 // Handle file downloads.
340 connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
342 // Set the local storage filter.
343 webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
345 // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
346 if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
348 //qDebug().noquote().nospace() << "Third-party request blocked: " << filterRequest.origin;
354 // Allow the request if local storage is enabled.
355 if (privacyWebEngineViewPointer->localStorageEnabled)
357 //qDebug().noquote().nospace() << "Request allowed by local storage: " << filterRequest.origin;
363 //qDebug().noquote().nospace() << "Request blocked by default: " << filterRequest.origin;
365 // Block any remaining local storage requests.
369 // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
370 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
372 // Don't allow JavaScript to open windows.
373 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
375 // Allow keyboard navigation.
376 webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
378 // Enable full screen support.
379 webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
381 // Require user interaction to play media.
382 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
384 // Limit WebRTC to public IP addresses.
385 webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
387 // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
388 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
390 // Plugins must be enabled for the PDF viewer to work. <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
391 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
393 // Update the cookies action.
394 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
396 // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
397 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
398 emit updateCookiesAction(numberOfCookies);
401 // Process cookie changes.
402 connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
403 connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
405 // Get a list of durable cookies.
406 QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
408 // Add the durable cookies to the store.
409 for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
410 addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
412 // Enable spell checking.
413 webEngineProfilePointer->setSpellCheckEnabled(true);
415 // Set the spell check language.
416 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
418 // Populate the zoom factor. This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
419 privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
421 // Update the UI when domain settings are applied.
422 connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
424 // Move to the new tab if it is not a background tab.
426 qTabWidgetPointer->setCurrentIndex(newTabIndex);
428 // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
429 if (removeUrlLineEditFocus)
430 emit clearUrlLineEditFocus();
432 // Return the privacy WebEngine view pointer.
433 return privacyWebEngineViewPointer;
436 void TabWidget::applyApplicationSettings()
438 // Set the tab position.
439 if (Settings::tabsOnTop())
440 qTabWidgetPointer->setTabPosition(QTabWidget::North);
442 qTabWidgetPointer->setTabPosition(QTabWidget::South);
444 // Set the search engine URL.
445 searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
447 // Emit the update search engine actions signal.
448 emit updateSearchEngineActions(Settings::searchEngine(), true);
451 void TabWidget::applyDomainSettingsAndReload()
453 // Apply the domain settings. `true` reloads the website.
454 currentPrivacyWebEngineViewPointer->applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
457 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
459 // Store the search engine name.
460 QString searchEngineName = searchEngineActionPointer->text();
462 // Strip out any `&` characters.
463 searchEngineName.remove('&');
465 // Store the search engine string.
466 searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
468 // Update the search engine actions.
469 emit updateSearchEngineActions(searchEngineName, false);
472 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
474 // Get the user agent name.
475 QString userAgentName = userAgentActionPointer->text();
477 // Strip out any `&` characters.
478 userAgentName.remove('&');
480 // Apply the user agent.
481 currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
483 // Update the user agent actions.
484 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
486 // Reload the website.
487 currentPrivacyWebEngineViewPointer->reload();
490 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor) const
492 // Set the zoom factor.
493 currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
496 void TabWidget::applySpellCheckLanguages() const
498 // Get the number of tab.
499 int numberOfTabs = qTabWidgetPointer->count();
501 // Set the spell check languages for each tab.
502 for (int i = 0; i < numberOfTabs; ++i)
504 // Get the WebEngine view pointer.
505 PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
507 // Get the WebEngine page pointer.
508 QWebEnginePage *webEnginePagePointer = webEngineViewPointer->page();
510 // Get the WebEngine profile pointer.
511 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
513 // Set the spell check languages.
514 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
518 void TabWidget::back() const
521 currentPrivacyWebEngineViewPointer->back();
524 void TabWidget::deleteAllCookies() const
526 // Delete all the cookies.
527 currentWebEngineCookieStorePointer->deleteAllCookies();
530 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
532 // Delete the cookie.
533 currentWebEngineCookieStorePointer->deleteCookie(cookie);
536 void TabWidget::deleteTab(const int tabIndex)
538 // Get the privacy WebEngine view.
539 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(tabIndex));
541 // Process the tab delete according to the number of tabs.
542 if (qTabWidgetPointer->count() > 1) // There is more than one tab.
545 qTabWidgetPointer->removeTab(tabIndex);
547 // Delete the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
548 delete privacyWebEngineViewPointer->page();
550 // Delete the privacy WebEngine view.
551 delete privacyWebEngineViewPointer;
553 else // There is only one tab.
555 // Close Privacy Browser.
560 void TabWidget::findPrevious(const QString &text) const
562 // Store the current text.
563 currentPrivacyWebEngineViewPointer->findString = text;
565 // Find the previous text in the current privacy WebEngine.
566 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
567 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
569 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
572 void TabWidget::findText(const QString &text) const
574 // Store the current text.
575 currentPrivacyWebEngineViewPointer->findString = text;
577 // Find the text in the current privacy WebEngine.
578 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
579 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
581 currentPrivacyWebEngineViewPointer->findText(text);
583 // Clear the currently selected text in the WebEngine page if the find text is empty.
585 currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
588 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
590 // Update the find text UI if it wasn't simply wiping the current find text selection. Otherwise the UI temporarially flashes `0/0`.
591 if (wipingCurrentFindTextSelection) // The current selection is being wiped.
594 wipingCurrentFindTextSelection = false;
596 else // A new search has been performed.
599 currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
602 emit updateFindTextResults(findTextResult);
606 void TabWidget::forward() const
609 currentPrivacyWebEngineViewPointer->forward();
612 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
615 emit fullScreenRequested(fullScreenRequest.toggleOn());
617 // Accept the request.
618 fullScreenRequest.accept();
621 std::list<QNetworkCookie>* TabWidget::getCookieList() const
623 // Return the current cookie list.
624 return currentPrivacyWebEngineViewPointer->cookieListPointer;
627 QString& TabWidget::getDomainSettingsName() const
629 // Return the domain settings name.
630 return currentPrivacyWebEngineViewPointer->domainSettingsName;
633 void TabWidget::home() const
635 // Load the homepage.
636 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
639 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
641 // Apply the application settings.
642 applyApplicationSettings();
644 // Return the current privacy WebEngine view pointer.
645 return currentPrivacyWebEngineViewPointer;
648 void TabWidget::loadInitialWebsite()
650 // Apply the application settings.
651 applyApplicationSettings();
653 // Get the arguments.
654 QStringList argumentsStringList = qApp->arguments();
656 // Check to see if the arguments lists contains a URL.
657 if (argumentsStringList.size() > 1)
659 // Load the URL from the arguments list.
660 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
664 // Load the homepage.
669 void TabWidget::loadUrlFromLineEdit(QString url) const
671 // Decide if the text is more likely to be a URL or a search.
672 if (url.startsWith("file://")) // The text is likely a file URL.
675 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
677 else if (url.contains(".")) // The text is likely a URL.
679 // Check if the URL does not start with a valid protocol.
680 if (!url.startsWith("http"))
682 // Add `https://` to the beginning of the URL.
683 url = "https://" + url;
687 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
689 else // The text is likely a search.
692 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
696 void TabWidget::mouseBack() const
698 // Go back if possible.
699 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
701 // Clear the URL line edit focus.
702 emit clearUrlLineEditFocus();
705 currentPrivacyWebEngineViewPointer->back();
709 void TabWidget::mouseForward() const
711 // Go forward if possible.
712 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
714 // Clear the URL line edit focus.
715 emit clearUrlLineEditFocus();
718 currentPrivacyWebEngineViewPointer->forward();
722 void TabWidget::pageLinkHovered(const QString &linkUrl) const
724 // Emit a signal so that the browser window can update the status bar.
725 emit linkHovered(linkUrl);
728 void TabWidget::print() const
733 // Set the resolution to be 300 dpi.
734 printer.setResolution(300);
736 // Create a printer dialog.
737 QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
739 // Display the dialog and print the page if instructed.
740 if (printDialog.exec() == QDialog::Accepted)
741 printWebpage(&printer);
744 void TabWidget::printPreview() const
749 // Set the resolution to be 300 dpi.
750 printer.setResolution(300);
752 // Create a print preview dialog.
753 QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
755 // Generate the print preview.
756 connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
758 // Display the dialog.
759 printPreviewDialog.exec();
762 void TabWidget::printWebpage(QPrinter *printerPointer) const
764 // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
765 QEventLoop eventLoop;
767 // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
768 // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
769 currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
771 // Instruct the compiler to ignore the unused parameter.
782 void TabWidget::refresh() const
784 // Reload the website.
785 currentPrivacyWebEngineViewPointer->reload();
788 void TabWidget::setTabBarVisible(const bool visible) const
790 // Set the tab bar visibility.
791 qTabWidgetPointer->tabBar()->setVisible(visible);
794 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
796 // Get the download attributes.
797 QUrl downloadUrl = webEngineDownloadItemPointer->url();
798 QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
799 QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
800 int totalBytes = webEngineDownloadItemPointer->totalBytes();
802 // Check to see if local storage (cookies) is enabled.
803 if (currentPrivacyWebEngineViewPointer->localStorageEnabled) // Local storage (cookies) is enabled. Use WebEngine's downloader.
805 // Instantiate the save dialog.
806 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
808 // Display the save dialog.
809 int saveDialogResult = saveDialogPointer->exec();
811 // Process the save dialog results.
812 if (saveDialogResult == QDialog::Accepted) // Save was selected.
814 // Get the download directory.
815 QString downloadDirectory = Settings::downloadLocation();
817 // Resolve the system download directory if specified.
818 if (downloadDirectory == QLatin1String("System Download Directory"))
819 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
821 // Display a save file dialog.
822 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
824 // Process the save file path.
825 if (!saveFilePath.isEmpty()) // The file save path is populated.
827 // Create a save file path file info.
828 QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
830 // Get the canonical save path and file name.
831 QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
832 QString saveFileName = saveFilePathFileInfo.fileName();
834 // Set the download directory and file name.
835 webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
836 webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
838 // Create a file download notification.
839 KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
841 // Set the notification title.
842 fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
844 // Set the notification text.
845 fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
847 // Set the notification icon.
848 fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
850 // Set the action list cancel button.
851 fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
853 // Set the notification to display indefinitely.
854 fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
856 // Prevent the notification from being autodeleted if it is closed. Otherwise, the updates to the notification below cause a crash.
857 fileDownloadNotificationPointer->setAutoDelete(false);
859 // Display the notification.
860 fileDownloadNotificationPointer->sendEvent();
862 // Handle clicks on the cancel button.
863 connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
865 // Cancel the download.
866 webEngineDownloadItemPointer->cancel();
868 // Create a file download notification.
869 KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
871 // Set the notification title.
872 canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
875 canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
877 // Set the notification icon.
878 canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
880 // Display the notification.
881 canceledDownloadNotificationPointer->sendEvent();
884 // Update the notification when the download progresses.
885 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
887 // Set the new text. Total bytes will be 0 if the download size is unknown.
890 // Calculate the download percentage.
891 int downloadPercentage = 100 * bytesReceived / totalBytes;
893 // Set the file download notification text.
894 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
895 bytesReceived, totalBytes));
899 // Set the file download notification text.
900 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1: %2 bytes downloaded", saveFileName, bytesReceived));
903 // Display the updated notification.
904 fileDownloadNotificationPointer->update();
907 // Update the notification when the download finishes. The save file name must be copied into the lambda or a crash occurs.
908 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
911 fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
913 // Set the URL so the file options will be displayed.
914 fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
916 // Remove the actions from the notification.
917 fileDownloadNotificationPointer->setActions(QStringList());
919 // Set the notification to disappear after a timeout.
920 fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
922 // Display the updated notification.
923 fileDownloadNotificationPointer->update();
926 // Start the download.
927 webEngineDownloadItemPointer->accept();
929 else // The file save path is not populated.
931 // Cancel the download.
932 webEngineDownloadItemPointer->cancel();
935 else // Cancel was selected.
937 // Cancel the download.
938 webEngineDownloadItemPointer->cancel();
941 else // Local storage (cookies) is disabled. Use KDE's native downloader.
942 // This must use the show command to launch a separate dialog which cancels WebEngine's automatic background download of the file to a temporary location.
944 // Instantiate the save dialog. `true` instructs it to use the native downloader
945 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
947 // Connect the save button.
948 connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &)));
951 saveDialogPointer->show();
955 void TabWidget::toggleDomStorage() const
957 // Toggle DOM storage.
958 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
960 // Update the DOM storage action.
961 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
963 // Reload the website.
964 currentPrivacyWebEngineViewPointer->reload();
967 void TabWidget::toggleFindCaseSensitive(const QString &text)
969 // Toggle find case sensitive.
970 currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
972 // Set the wiping current find text selection flag.
973 wipingCurrentFindTextSelection = true;
975 // Wipe the previous search. Otherwise currently highlighted words will remain highlighted.
976 findText(QLatin1String(""));
978 // Update the find text.
982 void TabWidget::toggleJavaScript() const
984 // Toggle JavaScript.
985 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
987 // Update the JavaScript action.
988 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
990 // Reload the website.
991 currentPrivacyWebEngineViewPointer->reload();
994 void TabWidget::toggleLocalStorage()
996 // Toggle local storage.
997 currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
999 // Update the local storage action.
1000 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1002 // Reload the website.
1003 currentPrivacyWebEngineViewPointer->reload();
1006 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
1008 // Only update the UI if the signal was emitted from the current privacy WebEngine.
1009 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
1012 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1013 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1014 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1015 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1016 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1017 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
1021 void TabWidget::updateUiWithTabSettings()
1023 // Update the current WebEngine pointers.
1024 currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
1025 currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
1026 currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
1027 currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
1028 currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
1029 currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
1031 // Clear the URL line edit focus.
1032 emit clearUrlLineEditFocus();
1034 // Update the actions.
1035 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
1036 emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
1037 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
1038 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
1039 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
1040 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
1041 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
1042 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
1045 emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
1046 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
1047 emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
1049 // Update the find text.
1050 emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
1051 emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
1053 // Update the progress bar.
1054 if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
1055 emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1057 emit hideProgressBar();
1060 void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1062 // Get the download directory.
1063 QString downloadDirectory = Settings::downloadLocation();
1065 // Resolve the system download directory if specified.
1066 if (downloadDirectory == QLatin1String("System Download Directory"))
1067 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1069 // Create a save file dialog.
1070 QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1072 // Tell the dialog to use a save button.
1073 saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1075 // Populate the file name from the download item pointer.
1076 saveFileDialogPointer->selectFile(suggestedFileName);
1078 // Prevent interaction with the parent window while the dialog is open.
1079 saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1081 // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1082 auto saveFile = [saveFileDialogPointer, downloadUrl] ()
1084 // Get the save location. The dialog box should only allow the selecting of one file location.
1085 QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1087 // Create a file copy job. `-1` creates the file with default permissions.
1088 KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1090 // Set the download job to display any error messages.
1091 fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1093 // Start the download.
1094 fileCopyJobPointer->start();
1097 // Handle clicks on the save button.
1098 connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1101 saveFileDialogPointer->show();