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);
78 // Process tab events.
79 connect(qTabWidgetPointer, SIGNAL(currentChanged(int)), this, SLOT(updateUiWithTabSettings()));
80 connect(addTabButtonPointer, SIGNAL(clicked()), this, SLOT(addTab()));
81 connect(qTabWidgetPointer, SIGNAL(tabCloseRequested(int)), this, SLOT(deleteTab(int)));
83 // Store a copy of the WebEngine default user agent.
84 webEngineDefaultUserAgent = currentWebEngineProfilePointer->httpUserAgent();
86 // Instantiate the mouse event filter pointer.
87 MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
89 // Install the mouse event filter.
90 qApp->installEventFilter(mouseEventFilterPointer);
92 // Process mouse forward and back commands.
93 connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
94 connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
97 TabWidget::~TabWidget()
99 // Manually delete each WebEngine page.
100 for (int i = 0; i < qTabWidgetPointer->count(); ++i)
102 // Get the privacy WebEngine view.
103 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(i));
105 // Deletion the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
106 delete privacyWebEngineViewPointer->page();
110 // The cookie is copied instead of referenced so that changes made to the cookie do not create a race condition with the display of the cookie in the dialog.
111 void TabWidget::addCookieToStore(QNetworkCookie cookie, QWebEngineCookieStore *webEngineCookieStorePointer) const
116 // Check to see if the domain does not start with a `.` because Qt makes this harder than it should be. <https://doc.qt.io/qt-5/qwebenginecookiestore.html#setCookie>
117 if (!cookie.domain().startsWith(QLatin1String(".")))
120 url.setHost(cookie.domain());
121 url.setScheme(QLatin1String("https"));
123 // Clear the domain from the cookie.
124 cookie.setDomain(QLatin1String(""));
127 // Add the cookie to the store.
128 if (webEngineCookieStorePointer == nullptr)
129 currentWebEngineCookieStorePointer->setCookie(cookie, url);
131 webEngineCookieStorePointer->setCookie(cookie, url);
134 void TabWidget::addFirstTab()
136 // Create the first tab.
139 // Update the UI with the tab settings.
140 updateUiWithTabSettings();
142 // Set the focus on the current tab widget. This prevents the tab bar from showing a blue bar under the label of the first tab.
143 qTabWidgetPointer->currentWidget()->setFocus();
146 PrivacyWebEngineView* TabWidget::addTab(const bool removeUrlLineEditFocus, const bool backgroundTab)
148 // Create a privacy WebEngine view.
149 PrivacyWebEngineView *privacyWebEngineViewPointer = new PrivacyWebEngineView();
152 int newTabIndex = qTabWidgetPointer->addTab(privacyWebEngineViewPointer, i18nc("New tab label.", "New Tab"));
154 // Set the default tab icon.
155 qTabWidgetPointer->setTabIcon(newTabIndex, defaultTabIcon);
157 // Get handles for the WebEngine page and profile.
158 QWebEnginePage *webEnginePagePointer = privacyWebEngineViewPointer->page();
159 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
161 // Get handles for the web engine elements.
162 QWebEngineCookieStore *webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
163 QWebEngineSettings *webEngineSettingsPointer = webEnginePagePointer->settings();
165 // Update the URL line edit when the URL changes.
166 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::urlChanged, [privacyWebEngineViewPointer, this] (const QUrl &newUrl)
168 // Only update the UI if this is the current tab.
169 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
171 // Update the URL line edit.
172 emit updateUrlLineEdit(newUrl);
174 // Update the status of the forward and back buttons.
175 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
176 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
180 // Update the progress bar when a load is started.
181 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadStarted, [privacyWebEngineViewPointer, this] ()
183 // Store the load progress.
184 privacyWebEngineViewPointer->loadProgressInt = 0;
186 // Show the progress bar if this is the current tab.
187 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
188 emit showProgressBar(0);
191 // Update the progress bar when a load progresses.
192 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadProgress, [privacyWebEngineViewPointer, this] (const int progress)
194 // Store the load progress.
195 privacyWebEngineViewPointer->loadProgressInt = progress;
197 // Update the progress bar if this is the current tab.
198 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
199 emit showProgressBar(progress);
202 // Update the progress bar when a load finishes.
203 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::loadFinished, [privacyWebEngineViewPointer, this] ()
205 // Store the load progress.
206 privacyWebEngineViewPointer->loadProgressInt = -1;
208 // Hide the progress bar if this is the current tab.
209 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
210 emit hideProgressBar();
213 // Display HTTP Ping blocked dialogs.
214 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::displayHttpPingBlockedDialog, [privacyWebEngineViewPointer, this] (const QString &httpPingUrl)
216 // Only display the HTTP Ping blocked dialog if this is the current tab.
217 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
219 // Instantiate an HTTP ping blocked message box.
220 QMessageBox httpPingBlockedMessageBox;
223 httpPingBlockedMessageBox.setIcon(QMessageBox::Information);
225 // Set the window title.
226 httpPingBlockedMessageBox.setWindowTitle(i18nc("HTTP Ping blocked dialog title", "HTTP Ping Blocked"));
229 httpPingBlockedMessageBox.setText(i18nc("HTTP Ping blocked dialog text", "This request has been blocked because it sends a naughty HTTP ping to %1.", httpPingUrl));
231 // Set the standard button.
232 httpPingBlockedMessageBox.setStandardButtons(QMessageBox::Ok);
234 // Display the message box.
235 httpPingBlockedMessageBox.exec();
239 // Update the zoom factor when changed by CTRL-Scrolling. This can be modified when <https://redmine.stoutner.com/issues/845> is fixed.
240 connect(webEnginePagePointer, &QWebEnginePage::contentsSizeChanged, [webEnginePagePointer, this] ()
242 // Only update the zoom factor action text if this is the current tab.
243 if (webEnginePagePointer == currentWebEnginePagePointer)
244 emit updateZoomFactorAction(webEnginePagePointer->zoomFactor());
247 // Display find text results.
248 connect(webEnginePagePointer, SIGNAL(findTextFinished(const QWebEngineFindTextResult &)), this, SLOT(findTextFinished(const QWebEngineFindTextResult &)));
250 // Handle full screen requests.
251 connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
253 // Listen for hovered link URLs.
254 connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
256 // Handle file downloads.
257 connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
259 // Set the local storage filter.
260 webEngineCookieStorePointer->setCookieFilter([privacyWebEngineViewPointer](const QWebEngineCookieStore::FilterRequest &filterRequest)
262 // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
263 if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
265 //qDebug().noquote().nospace() << "Third-party request blocked: " << filterRequest.origin;
271 // Allow the request if local storage is enabled.
272 if (privacyWebEngineViewPointer->localStorageEnabled)
274 //qDebug().noquote().nospace() << "Request allowed by local storage: " << filterRequest.origin;
280 //qDebug().noquote().nospace() << "Request blocked by default: " << filterRequest.origin;
282 // Block any remaining local storage requests.
286 // Disable JavaScript by default (this prevents JavaScript from being enabled on a new tab before domain settings are loaded).
287 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
289 // Don't allow JavaScript to open windows.
290 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
292 // Allow keyboard navigation.
293 webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
295 // Enable full screen support.
296 webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
298 // Require user interaction to play media.
299 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
301 // Limit WebRTC to public IP addresses.
302 webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
304 // Enable the PDF viewer (it should be enabled by default, but it is nice to be explicit in case the defaults change).
305 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PdfViewerEnabled, true);
307 // Plugins must be enabled for the PDF viewer to work. <https://doc.qt.io/qt-5/qtwebengine-features.html#pdf-file-viewing>
308 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PluginsEnabled, true);
310 // Update the cookies action.
311 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::updateCookiesAction, [privacyWebEngineViewPointer, this] (const int numberOfCookies)
313 // Update the cookie action if the specified privacy WebEngine view is the current privacy WebEngine view.
314 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
315 emit updateCookiesAction(numberOfCookies);
318 // Process cookie changes.
319 connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(addCookieToList(QNetworkCookie)));
320 connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), privacyWebEngineViewPointer, SLOT(removeCookieFromList(QNetworkCookie)));
322 // Get a list of durable cookies.
323 QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
325 // Add the durable cookies to the store.
326 for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
327 addCookieToStore(*cookiePointer, webEngineCookieStorePointer);
329 // Update the title when it changes.
330 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::titleChanged, [this, privacyWebEngineViewPointer] (const QString &title)
332 // Get the index for this tab.
333 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
335 // Update the title for this tab.
336 qTabWidgetPointer->setTabText(tabIndex, title);
338 // Update the window title if this is the current tab.
339 if (tabIndex == qTabWidgetPointer->currentIndex())
340 emit updateWindowTitle(title);
343 // Update the icon when it changes.
344 connect(privacyWebEngineViewPointer, &PrivacyWebEngineView::iconChanged, [privacyWebEngineViewPointer, this] (const QIcon &icon)
346 // Get the index for this tab.
347 int tabIndex = qTabWidgetPointer->indexOf(privacyWebEngineViewPointer);
349 // Update the icon for this tab.
351 qTabWidgetPointer->setTabIcon(tabIndex, defaultTabIcon);
353 qTabWidgetPointer->setTabIcon(tabIndex, icon);
356 // Enable spell checking.
357 webEngineProfilePointer->setSpellCheckEnabled(true);
359 // Set the spell check language.
360 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
362 // Populate the zoom factor. This is necessary if a URL is being loaded, like a local URL, that does not trigger `applyDomainSettings()`.
363 privacyWebEngineViewPointer->setZoomFactor(Settings::zoomFactor());
365 // Update the UI when domain settings are applied.
366 connect(privacyWebEngineViewPointer, SIGNAL(updateUi(const PrivacyWebEngineView*)), this, SLOT(updateUiFromWebEngineView(const PrivacyWebEngineView*)));
368 // Move to the new tab if it is not a background tab.
370 qTabWidgetPointer->setCurrentIndex(newTabIndex);
372 // Clear the URL line edit focus so that it populates correctly when opening a new tab from the context menu.
373 if (removeUrlLineEditFocus)
374 emit clearUrlLineEditFocus();
376 // Return the privacy WebEngine view pointer.
377 return privacyWebEngineViewPointer;
380 void TabWidget::applyApplicationSettings()
382 // Set the tab position.
383 if (Settings::tabsOnTop())
384 qTabWidgetPointer->setTabPosition(QTabWidget::North);
386 qTabWidgetPointer->setTabPosition(QTabWidget::South);
388 // Set the search engine URL.
389 searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
391 // Emit the update search engine actions signal.
392 emit updateSearchEngineActions(Settings::searchEngine(), true);
395 void TabWidget::applyDomainSettingsAndReload()
397 // Apply the domain settings. `true` reloads the website.
398 currentPrivacyWebEngineViewPointer->applyDomainSettings(currentPrivacyWebEngineViewPointer->url().host(), true);
401 void TabWidget::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
403 // Store the search engine name.
404 QString searchEngineName = searchEngineActionPointer->text();
406 // Strip out any `&` characters.
407 searchEngineName.remove('&');
409 // Store the search engine string.
410 searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
412 // Update the search engine actions.
413 emit updateSearchEngineActions(searchEngineName, false);
416 void TabWidget::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
418 // Get the user agent name.
419 QString userAgentName = userAgentActionPointer->text();
421 // Strip out any `&` characters.
422 userAgentName.remove('&');
424 // Apply the user agent.
425 currentWebEngineProfilePointer->setHttpUserAgent(userAgentHelperPointer->getUserAgentFromTranslatedName(userAgentName));
427 // Update the user agent actions.
428 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), false);
430 // Reload the website.
431 currentPrivacyWebEngineViewPointer->reload();
434 void TabWidget::applyOnTheFlyZoomFactor(const double &zoomFactor) const
436 // Set the zoom factor.
437 currentPrivacyWebEngineViewPointer->setZoomFactor(zoomFactor);
440 void TabWidget::applySpellCheckLanguages() const
442 // Get the number of tab.
443 int numberOfTabs = qTabWidgetPointer->count();
445 // Set the spell check languages for each tab.
446 for (int i = 0; i < numberOfTabs; ++i)
448 // Get the WebEngine view pointer.
449 PrivacyWebEngineView *webEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
451 // Get the WebEngine page pointer.
452 QWebEnginePage *webEnginePagePointer = webEngineViewPointer->page();
454 // Get the WebEngine profile pointer.
455 QWebEngineProfile *webEngineProfilePointer = webEnginePagePointer->profile();
457 // Set the spell check languages.
458 webEngineProfilePointer->setSpellCheckLanguages(Settings::spellCheckLanguages());
462 void TabWidget::back() const
465 currentPrivacyWebEngineViewPointer->back();
468 void TabWidget::deleteAllCookies() const
470 // Delete all the cookies.
471 currentWebEngineCookieStorePointer->deleteAllCookies();
474 void TabWidget::deleteCookieFromStore(const QNetworkCookie &cookie) const
476 // Delete the cookie.
477 currentWebEngineCookieStorePointer->deleteCookie(cookie);
480 void TabWidget::deleteTab(const int tabIndex)
482 // Get the privacy WebEngine view.
483 PrivacyWebEngineView *privacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->widget(tabIndex));
485 // Process the tab delete according to the number of tabs.
486 if (qTabWidgetPointer->count() > 1) // There is more than one tab.
489 qTabWidgetPointer->removeTab(tabIndex);
491 // Delete the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
492 delete privacyWebEngineViewPointer->page();
494 // Delete the privacy WebEngine view.
495 delete privacyWebEngineViewPointer;
497 else // There is only one tab.
499 // Close Privacy Browser.
504 void TabWidget::findPrevious(const QString &text) const
506 // Store the current text.
507 currentPrivacyWebEngineViewPointer->findString = text;
509 // Find the previous text in the current privacy WebEngine.
510 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
511 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively|QWebEnginePage::FindBackward);
513 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindBackward);
516 void TabWidget::findText(const QString &text) const
518 // Store the current text.
519 currentPrivacyWebEngineViewPointer->findString = text;
521 // Find the text in the current privacy WebEngine.
522 if (currentPrivacyWebEngineViewPointer->findCaseSensitive)
523 currentPrivacyWebEngineViewPointer->findText(text, QWebEnginePage::FindCaseSensitively);
525 currentPrivacyWebEngineViewPointer->findText(text);
527 // Clear the currently selected text in the WebEngine page if the find text is empty.
529 currentWebEnginePagePointer->action(QWebEnginePage::Unselect)->activate(QAction::Trigger);
532 void TabWidget::findTextFinished(const QWebEngineFindTextResult &findTextResult)
534 // Update the find text UI if it wasn't simply wiping the current find text selection. Otherwise the UI temporarially flashes `0/0`.
535 if (wipingCurrentFindTextSelection) // The current selection is being wiped.
538 wipingCurrentFindTextSelection = false;
540 else // A new search has been performed.
543 currentPrivacyWebEngineViewPointer->findTextResult = findTextResult;
546 emit updateFindTextResults(findTextResult);
550 void TabWidget::forward() const
553 currentPrivacyWebEngineViewPointer->forward();
556 void TabWidget::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
559 emit fullScreenRequested(fullScreenRequest.toggleOn());
561 // Accept the request.
562 fullScreenRequest.accept();
565 std::list<QNetworkCookie>* TabWidget::getCookieList() const
567 // Return the current cookie list.
568 return currentPrivacyWebEngineViewPointer->cookieListPointer;
571 QString& TabWidget::getDomainSettingsName() const
573 // Return the domain settings name.
574 return currentPrivacyWebEngineViewPointer->domainSettingsName;
577 void TabWidget::home() const
579 // Load the homepage.
580 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
583 PrivacyWebEngineView* TabWidget::loadBlankInitialWebsite()
585 // Apply the application settings.
586 applyApplicationSettings();
588 // Return the current privacy WebEngine view pointer.
589 return currentPrivacyWebEngineViewPointer;
592 void TabWidget::loadInitialWebsite()
594 // Apply the application settings.
595 applyApplicationSettings();
597 // Get the arguments.
598 QStringList argumentsStringList = qApp->arguments();
600 // Check to see if the arguments lists contains a URL.
601 if (argumentsStringList.size() > 1)
603 // Load the URL from the arguments list.
604 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
608 // Load the homepage.
613 void TabWidget::loadUrlFromLineEdit(QString url) const
615 // Decide if the text is more likely to be a URL or a search.
616 if (url.startsWith("file://")) // The text is likely a file URL.
619 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
621 else if (url.contains(".")) // The text is likely a URL.
623 // Check if the URL does not start with a valid protocol.
624 if (!url.startsWith("http"))
626 // Add `https://` to the beginning of the URL.
627 url = "https://" + url;
631 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(url));
633 else // The text is likely a search.
636 currentPrivacyWebEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
640 void TabWidget::mouseBack() const
642 // Go back if possible.
643 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoBack())
645 // Clear the URL line edit focus.
646 emit clearUrlLineEditFocus();
649 currentPrivacyWebEngineViewPointer->back();
653 void TabWidget::mouseForward() const
655 // Go forward if possible.
656 if (currentPrivacyWebEngineViewPointer->isActiveWindow() && currentWebEngineHistoryPointer->canGoForward())
658 // Clear the URL line edit focus.
659 emit clearUrlLineEditFocus();
662 currentPrivacyWebEngineViewPointer->forward();
666 void TabWidget::pageLinkHovered(const QString &linkUrl) const
668 // Emit a signal so that the browser window can update the status bar.
669 emit linkHovered(linkUrl);
672 void TabWidget::print() const
677 // Set the resolution to be 300 dpi.
678 printer.setResolution(300);
680 // Create a printer dialog.
681 QPrintDialog printDialog(&printer, currentPrivacyWebEngineViewPointer);
683 // Display the dialog and print the page if instructed.
684 if (printDialog.exec() == QDialog::Accepted)
685 printWebpage(&printer);
688 void TabWidget::printPreview() const
693 // Set the resolution to be 300 dpi.
694 printer.setResolution(300);
696 // Create a print preview dialog.
697 QPrintPreviewDialog printPreviewDialog(&printer, currentPrivacyWebEngineViewPointer);
699 // Generate the print preview.
700 connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
702 // Display the dialog.
703 printPreviewDialog.exec();
706 void TabWidget::printWebpage(QPrinter *printerPointer) const
708 // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
709 QEventLoop eventLoop;
711 // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
712 // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
713 currentWebEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
715 // Instruct the compiler to ignore the unused parameter.
726 void TabWidget::refresh() const
728 // Reload the website.
729 currentPrivacyWebEngineViewPointer->reload();
732 void TabWidget::setTabBarVisible(const bool visible) const
734 // Set the tab bar visibility.
735 qTabWidgetPointer->tabBar()->setVisible(visible);
738 void TabWidget::showSaveDialog(QWebEngineDownloadItem *webEngineDownloadItemPointer)
740 // Get the download attributes.
741 QUrl downloadUrl = webEngineDownloadItemPointer->url();
742 QString mimeTypeString = webEngineDownloadItemPointer->mimeType();
743 QString suggestedFileName = webEngineDownloadItemPointer->suggestedFileName();
744 int totalBytes = webEngineDownloadItemPointer->totalBytes();
746 // Check to see if local storage (cookies) is enabled.
747 if (currentPrivacyWebEngineViewPointer->localStorageEnabled) // Local storage (cookies) is enabled. Use WebEngine's downloader.
749 // Instantiate the save dialog.
750 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes);
752 // Display the save dialog.
753 int saveDialogResult = saveDialogPointer->exec();
755 // Process the save dialog results.
756 if (saveDialogResult == QDialog::Accepted) // Save was selected.
758 // Get the download directory.
759 QString downloadDirectory = Settings::downloadLocation();
761 // Resolve the system download directory if specified.
762 if (downloadDirectory == QLatin1String("System Download Directory"))
763 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
765 // Display a save file dialog.
766 QString saveFilePath = QFileDialog::getSaveFileName(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory + QLatin1Char('/') + suggestedFileName);
768 // Process the save file path.
769 if (!saveFilePath.isEmpty()) // The file save path is populated.
771 // Create a save file path file info.
772 QFileInfo saveFilePathFileInfo = QFileInfo(saveFilePath);
774 // Get the canonical save path and file name.
775 QString absoluteSavePath = saveFilePathFileInfo.absolutePath();
776 QString saveFileName = saveFilePathFileInfo.fileName();
778 // Set the download directory and file name.
779 webEngineDownloadItemPointer->setDownloadDirectory(absoluteSavePath);
780 webEngineDownloadItemPointer->setDownloadFileName(saveFileName);
782 // Create a file download notification.
783 KNotification *fileDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
785 // Set the notification title.
786 fileDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
788 // Set the notification text.
789 fileDownloadNotificationPointer->setText(i18nc("Downloading notification text", "Downloading %1", saveFileName));
791 // Set the notification icon.
792 fileDownloadNotificationPointer->setIconName(QLatin1String("download"));
794 // Set the action list cancel button.
795 fileDownloadNotificationPointer->setActions(QStringList({i18nc("Download notification action","Cancel")}));
797 // Set the notification to display indefinitely.
798 fileDownloadNotificationPointer->setFlags(KNotification::Persistent);
800 // Prevent the notification from being autodeleted if it is closed. Otherwise, the updates to the notification below cause a crash.
801 fileDownloadNotificationPointer->setAutoDelete(false);
803 // Display the notification.
804 fileDownloadNotificationPointer->sendEvent();
806 // Handle clicks on the cancel button.
807 connect(fileDownloadNotificationPointer, &KNotification::action1Activated, [webEngineDownloadItemPointer, saveFileName] ()
809 // Cancel the download.
810 webEngineDownloadItemPointer->cancel();
812 // Create a file download notification.
813 KNotification *canceledDownloadNotificationPointer = new KNotification(QLatin1String("FileDownload"));
815 // Set the notification title.
816 canceledDownloadNotificationPointer->setTitle(i18nc("Download notification title", "Download"));
819 canceledDownloadNotificationPointer->setText(i18nc("Download canceled notification", "%1 download canceled", saveFileName));
821 // Set the notification icon.
822 canceledDownloadNotificationPointer->setIconName(QLatin1String("download"));
824 // Display the notification.
825 canceledDownloadNotificationPointer->sendEvent();
828 // Update the notification when the download progresses.
829 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::downloadProgress, [fileDownloadNotificationPointer, saveFileName] (qint64 bytesReceived, qint64 totalBytes)
831 // Set the new text. Total bytes will be 0 if the download size is unknown.
834 // Calculate the download percentage.
835 int downloadPercentage = 100 * bytesReceived / totalBytes;
837 // Set the file download notification text.
838 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1\% of %2 downloaded (%3 of %4 bytes)", downloadPercentage, saveFileName,
839 bytesReceived, totalBytes));
843 // Set the file download notification text.
844 fileDownloadNotificationPointer->setText(i18nc("Download progress notification text", "%1: %2 bytes downloaded", saveFileName, bytesReceived));
847 // Display the updated notification.
848 fileDownloadNotificationPointer->update();
851 // Update the notification when the download finishes. The save file name must be copied into the lambda or a crash occurs.
852 connect(webEngineDownloadItemPointer, &QWebEngineDownloadItem::finished, [fileDownloadNotificationPointer, saveFileName, saveFilePath] ()
855 fileDownloadNotificationPointer->setText(i18nc("Download finished notification text", "%1 download finished", saveFileName));
857 // Set the URL so the file options will be displayed.
858 fileDownloadNotificationPointer->setUrls(QList<QUrl> {QUrl(saveFilePath)});
860 // Remove the actions from the notification.
861 fileDownloadNotificationPointer->setActions(QStringList());
863 // Set the notification to disappear after a timeout.
864 fileDownloadNotificationPointer->setFlags(KNotification::CloseOnTimeout);
866 // Display the updated notification.
867 fileDownloadNotificationPointer->update();
870 // Start the download.
871 webEngineDownloadItemPointer->accept();
873 else // The file save path is not populated.
875 // Cancel the download.
876 webEngineDownloadItemPointer->cancel();
879 else // Cancel was selected.
881 // Cancel the download.
882 webEngineDownloadItemPointer->cancel();
885 else // Local storage (cookies) is disabled. Use KDE's native downloader.
886 // This must use the show command to launch a separate dialog which cancels WebEngine's automatic background download of the file to a temporary location.
888 // Instantiate the save dialog. `true` instructs it to use the native downloader
889 SaveDialog *saveDialogPointer = new SaveDialog(downloadUrl, mimeTypeString, totalBytes, suggestedFileName, true);
891 // Connect the save button.
892 connect(saveDialogPointer, SIGNAL(useNativeDownloader(QUrl &, QString &)), this, SLOT(useNativeDownloader(QUrl &, QString &)));
895 saveDialogPointer->show();
899 void TabWidget::toggleDomStorage() const
901 // Toggle DOM storage.
902 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
904 // Update the DOM storage action.
905 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
907 // Reload the website.
908 currentPrivacyWebEngineViewPointer->reload();
911 void TabWidget::toggleFindCaseSensitive(const QString &text)
913 // Toggle find case sensitive.
914 currentPrivacyWebEngineViewPointer->findCaseSensitive = !currentPrivacyWebEngineViewPointer->findCaseSensitive;
916 // Set the wiping current find text selection flag.
917 wipingCurrentFindTextSelection = true;
919 // Wipe the previous search. Otherwise currently highlighted words will remain highlighted.
920 findText(QLatin1String(""));
922 // Update the find text.
926 void TabWidget::toggleJavaScript() const
928 // Toggle JavaScript.
929 currentWebEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
931 // Update the JavaScript action.
932 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
934 // Reload the website.
935 currentPrivacyWebEngineViewPointer->reload();
938 void TabWidget::toggleLocalStorage()
940 // Toggle local storage.
941 currentPrivacyWebEngineViewPointer->localStorageEnabled = !currentPrivacyWebEngineViewPointer->localStorageEnabled;
943 // Update the local storage action.
944 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
946 // Reload the website.
947 currentPrivacyWebEngineViewPointer->reload();
950 void TabWidget::updateUiFromWebEngineView(const PrivacyWebEngineView *privacyWebEngineViewPointer) const
952 // Only update the UI if the signal was emitted from the current privacy WebEngine.
953 if (privacyWebEngineViewPointer == currentPrivacyWebEngineViewPointer)
956 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
957 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
958 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
959 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
960 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
961 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
965 void TabWidget::updateUiWithTabSettings()
967 // Update the current WebEngine pointers.
968 currentPrivacyWebEngineViewPointer = qobject_cast<PrivacyWebEngineView *>(qTabWidgetPointer->currentWidget());
969 currentWebEngineSettingsPointer = currentPrivacyWebEngineViewPointer->settings();
970 currentWebEnginePagePointer = currentPrivacyWebEngineViewPointer->page();
971 currentWebEngineProfilePointer = currentWebEnginePagePointer->profile();
972 currentWebEngineHistoryPointer = currentWebEnginePagePointer->history();
973 currentWebEngineCookieStorePointer = currentWebEngineProfilePointer->cookieStore();
975 // Clear the URL line edit focus.
976 emit clearUrlLineEditFocus();
978 // Update the actions.
979 emit updateBackAction(currentWebEngineHistoryPointer->canGoBack());
980 emit updateCookiesAction(currentPrivacyWebEngineViewPointer->cookieListPointer->size());
981 emit updateDomStorageAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
982 emit updateForwardAction(currentWebEngineHistoryPointer->canGoForward());
983 emit updateJavaScriptAction(currentWebEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
984 emit updateLocalStorageAction(currentPrivacyWebEngineViewPointer->localStorageEnabled);
985 emit updateUserAgentActions(currentWebEngineProfilePointer->httpUserAgent(), true);
986 emit updateZoomFactorAction(currentPrivacyWebEngineViewPointer->zoomFactor());
989 emit updateWindowTitle(currentPrivacyWebEngineViewPointer->title());
990 emit updateDomainSettingsIndicator(currentPrivacyWebEngineViewPointer->domainSettingsName != QLatin1String(""));
991 emit updateUrlLineEdit(currentPrivacyWebEngineViewPointer->url());
993 // Update the find text.
994 emit updateFindText(currentPrivacyWebEngineViewPointer->findString, currentPrivacyWebEngineViewPointer->findCaseSensitive);
995 emit updateFindTextResults(currentPrivacyWebEngineViewPointer->findTextResult);
997 // Update the progress bar.
998 if (currentPrivacyWebEngineViewPointer->loadProgressInt >= 0)
999 emit showProgressBar(currentPrivacyWebEngineViewPointer->loadProgressInt);
1001 emit hideProgressBar();
1004 void TabWidget::useNativeDownloader(QUrl &downloadUrl, QString &suggestedFileName)
1006 // Get the download directory.
1007 QString downloadDirectory = Settings::downloadLocation();
1009 // Resolve the system download directory if specified.
1010 if (downloadDirectory == QLatin1String("System Download Directory"))
1011 downloadDirectory = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
1013 // Create a save file dialog.
1014 QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), downloadDirectory);
1016 // Tell the dialog to use a save button.
1017 saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
1019 // Populate the file name from the download item pointer.
1020 saveFileDialogPointer->selectFile(suggestedFileName);
1022 // Prevent interaction with the parent window while the dialog is open.
1023 saveFileDialogPointer->setWindowModality(Qt::WindowModal);
1025 // Process the saving of the file. The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
1026 auto saveFile = [saveFileDialogPointer, downloadUrl] ()
1028 // Get the save location. The dialog box should only allow the selecting of one file location.
1029 QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
1031 // Create a file copy job. `-1` creates the file with default permissions.
1032 KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
1034 // Set the download job to display any error messages.
1035 fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
1037 // Start the download.
1038 fileCopyJobPointer->start();
1041 // Handle clicks on the save button.
1042 connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
1045 saveFileDialogPointer->show();