2 * Copyright © 2022 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 "BrowserView.h"
23 #include "ui_BrowserView.h"
24 #include "databases/CookiesDatabase.h"
25 #include "databases/DomainsDatabase.h"
26 #include "filters/MouseEventFilter.h"
27 #include "helpers/SearchEngineHelper.h"
28 #include "helpers/UserAgentHelper.h"
29 #include "interceptors/UrlRequestInterceptor.h"
30 #include "windows/BrowserWindow.h"
32 // Qt framework headers.
34 #include <QPrintDialog>
35 #include <QPrintPreviewDialog>
38 // Initialize the public static variables.
39 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
41 // Construct the class.
42 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
44 // Initialize the variables.
45 privacyWebEngineListPointer = new QList<PrivacyWebEngine*>;
47 // Instantiate the browser view UI.
48 Ui::BrowserView browserViewUi;
51 browserViewUi.setupUi(this);
53 // Get handles for the views.
54 webEngineViewPointer = browserViewUi.webEngineView;
56 // Create an off-the-record profile (the default when no profile name is specified).
57 webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
59 // Create a WebEngine page.
60 webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
62 // Set the WebEngine page.
63 webEngineViewPointer->setPage(webEnginePagePointer);
65 // Handle full screen requests.
66 connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
68 // Get handles for the aspects of the WebEngine.
69 webEngineHistoryPointer = webEnginePagePointer->history();
70 webEngineSettingsPointer = webEngineViewPointer->settings();
71 webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
73 // Initialize the current privacy web engine pointer.
74 currentPrivacyWebEnginePointer = new PrivacyWebEngine(webEngineViewPointer);
76 // Populate the privacy web engine list.
77 privacyWebEngineListPointer->append(currentPrivacyWebEnginePointer);
79 // Set the local storage filter.
80 webEngineCookieStorePointer->setCookieFilter([this](const QWebEngineCookieStore::FilterRequest &filterRequest)
82 //qDebug().noquote().nospace() << "Page URL: " << filterRequest.firstPartyUrl << ", Local storage URL: " << filterRequest.origin << ", Is third-party: " << filterRequest.thirdParty;
84 // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
85 if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
87 //qDebug() << "Request blocked.";
93 /* TODO. Waiting for a solution to <https://redmine.stoutner.com/issues/857>.
94 // Check each tab to see if this local storage request should be allowed.
95 for (PrivacyWebEngine *privacyWebEnginePointer : *privacyWebEngineListPointer)
97 //qDebug().noquote().nospace() << "Local storage: " << privacyWebEnginePointer->localStorageEnabled << ". WebEngine URL: " << webEngineViewPointer->url().host() << ". Request Host: " << filterRequest.firstPartyUrl.host();
99 // Allow this local storage request if it comes from a tab with local storage enabled.
100 if (privacyWebEnginePointer->localStorageEnabled && (webEngineViewPointer->url().host() == filterRequest.firstPartyUrl.host()))
102 //qDebug() << "Request allowed.";
110 // Allow the request if it is first party and local storage is enabled.
111 if (!filterRequest.thirdParty && currentPrivacyWebEnginePointer->localStorageEnabled)
117 //qDebug() << "Request blocked.";
119 // Block any remaining local storage requests.
123 // Process cookie changes.
124 connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
125 connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), this, SLOT(cookieRemoved(QNetworkCookie)));
127 // Get a list of durable cookies.
128 QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
130 // Add the durable cookies to the store.
131 for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
132 addCookieToStore(*cookiePointer);
134 // Store a copy of the WebEngine default user agent.
135 webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
137 // Update the URL line edit when the URL changes.
138 connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
140 // Update the progress bar.
141 connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
142 connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
143 connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
145 // Instantiate the mouse event filter pointer.
146 MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
148 // Install the mouse event filter.
149 qApp->installEventFilter(mouseEventFilterPointer);
151 // Process mouse forward and back commands.
152 connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
153 connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
155 // Listen for hovered link URLs.
156 connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
158 // Instantiate the URL request interceptor.
159 UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
161 // Set the URL request interceptor.
162 webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
164 // Reapply the domain settings when the host changes.
165 connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
167 // Don't allow JavaScript to open windows.
168 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
170 // Allow keyboard navigation.
171 webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
173 // Enable full screen support.
174 webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
176 // Require user interaction to play media.
177 webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
179 // Limit WebRTC to public IP addresses.
180 webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
182 // Set the focus on the WebEngine view.
183 webEngineViewPointer->setFocus();
186 BrowserView::~BrowserView()
188 // Delay the deletion of the WebEngine page to prevent the following error: `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
189 webEnginePagePointer->deleteLater();
192 // 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.
193 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
198 // 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>
199 if (!cookie.domain().startsWith(QStringLiteral(".")))
202 url.setHost(cookie.domain());
203 url.setScheme(QStringLiteral("https"));
205 // Clear the domain from the cookie.
206 cookie.setDomain(QStringLiteral(""));
209 // Add the cookie to the store.
210 webEngineCookieStorePointer->setCookie(cookie, url);
213 void BrowserView::applyApplicationSettings()
215 // Set the search engine URL.
216 searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
218 // Emit the update search engine actions signal.
219 emit updateSearchEngineActions(Settings::searchEngine(), true);
222 // 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.
223 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
224 void BrowserView::applyDomainSettingsAndReload()
226 // Apply the domain settings. `true` reloads the website.
227 applyDomainSettings(webEngineViewPointer->url().host(), true);
230 // 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.
231 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
232 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
234 // Apply the domain settings `false` does not reload the website.
235 applyDomainSettings(hostname, false);
238 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
239 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
241 // Get the record for the hostname.
242 QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
244 // Check if the hostname has domain settings.
245 if (domainQuery.isValid()) // The hostname has domain settings.
247 // Get the domain record.
248 QSqlRecord domainRecord = domainQuery.record();
250 // Set the JavaScript status.
251 switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
253 // Set the default JavaScript status.
254 case (DomainsDatabase::SYSTEM_DEFAULT):
256 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
261 // Disable JavaScript.
262 case (DomainsDatabase::DISABLED):
264 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
269 // Enable JavaScript.
270 case (DomainsDatabase::ENABLED):
272 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
278 // Set the local storage status.
279 switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
281 // Set the default local storage status.
282 case (DomainsDatabase::SYSTEM_DEFAULT):
284 currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
289 // Disable local storage.
290 case (DomainsDatabase::DISABLED):
292 currentPrivacyWebEnginePointer->localStorageEnabled = false;
297 // Enable local storage.
298 case (DomainsDatabase::ENABLED):
300 currentPrivacyWebEnginePointer->localStorageEnabled = true;
306 // Set the DOM storage status.
307 switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
309 // Set the default DOM storage status.
310 case (DomainsDatabase::SYSTEM_DEFAULT):
312 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
317 // Disable DOM storage.
318 case (DomainsDatabase::DISABLED):
320 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
325 // Enable DOM storage.
326 case (DomainsDatabase::ENABLED):
328 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
334 // Set the user agent.
335 webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
337 // Check if a custom zoom factor is set.
338 if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
340 // Store the current zoom factor.
341 currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
345 // Reset the current zoom factor.
346 currentZoomFactor = Settings::zoomFactor();
349 // Set the zoom factor. The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
350 webEngineViewPointer->setZoomFactor(currentZoomFactor);
352 // Apply the domain settings palette to the URL line edit.
353 emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString());
355 else // The hostname does not have domain settings.
357 // Set the JavaScript status.
358 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
360 // Set the local storage status.
361 currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
364 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
366 // Set the user agent.
367 webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
369 // Store the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
370 currentZoomFactor = Settings::zoomFactor();
372 // Set the zoom factor.
373 webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
375 // Apply the no domain settings palette to the URL line edit.
376 emit updateDomainSettingsIndicator(false, QStringLiteral(""));
379 // Emit the update actions signals.
380 emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
381 emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
382 emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
383 emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), true);
384 emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
386 // Reload the website if requested.
388 webEngineViewPointer->reload();
391 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
393 // Store the search engine name.
394 QString searchEngineName = searchEngineActionPointer->text();
396 // Strip out any `&` characters.
397 searchEngineName.remove('&');
399 // Store the search engine string.
400 searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
402 // Update the search engine actionas.
403 emit updateSearchEngineActions(searchEngineName, false);
406 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
408 // Get the user agent name.
409 QString userAgentName = userAgentActionPointer->text();
411 // Strip out any `&` characters.
412 userAgentName.remove('&');
414 // Apply the user agent.
415 webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
417 // Update the user agent actions.
418 emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), false);
420 // Reload the website.
421 webEngineViewPointer->reload();
424 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
425 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
427 // Update the current zoom factor. This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
428 currentZoomFactor = zoomFactor;
430 // Set the zoom factor.
431 webEngineViewPointer->setZoomFactor(zoomFactor);
434 void BrowserView::back() const
437 webEngineViewPointer->back();
440 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
442 // Add the cookie to the cookie list.
443 emit addCookie(cookie);
446 void BrowserView::cookieRemoved(const QNetworkCookie &cookie) const
448 // Remove the cookie from the cookie list.
449 emit removeCookie(cookie);
452 void BrowserView::deleteAllCookies() const
454 // Delete all the cookies.
455 webEngineCookieStorePointer->deleteAllCookies();
458 void BrowserView::deleteCookieFromStore(const QNetworkCookie &cookie) const
460 // Delete the cookie.
461 webEngineCookieStorePointer->deleteCookie(cookie);
464 void BrowserView::forward() const
467 webEngineViewPointer->forward();
470 void BrowserView::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
473 emit fullScreenRequested(fullScreenRequest.toggleOn());
475 // Accept the request.
476 fullScreenRequest.accept();
479 void BrowserView::home() const
481 // Load the homepage.
482 webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
485 void BrowserView::loadFinished() const
487 // Hide the progress bar.
488 emit hideProgressBar();
491 void BrowserView::loadInitialWebsite()
493 // Apply the application settings.
494 applyApplicationSettings();
496 // Get the arguments.
497 QStringList argumentsStringList = qApp->arguments();
499 // Check to see if the arguments lists contains a URL.
500 if (argumentsStringList.size() > 1)
502 // Load the URL from the arguments list.
503 webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
507 // Load the homepage.
512 void BrowserView::loadProgress(const int &progress) const
514 // Show the progress bar.
515 emit showProgressBar(progress);
518 void BrowserView::loadStarted() const
520 // Show the progress bar.
521 emit showProgressBar(0);
524 void BrowserView::loadUrlFromLineEdit(QString url) const
526 // Decide if the text is more likely to be a URL or a search.
527 if (url.startsWith("file://")) // The text is likely a file URL.
530 webEngineViewPointer->load(QUrl::fromUserInput(url));
532 else if (url.contains(".")) // The text is likely a URL.
534 // Check if the URL does not start with a valid protocol.
535 if (!url.startsWith("http"))
537 // Add `https://` to the beginning of the URL.
538 url = "https://" + url;
542 webEngineViewPointer->load(QUrl::fromUserInput(url));
544 else // The text is likely a search.
547 webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
551 void BrowserView::mouseBack() const
553 // Go back if possible.
554 if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoBack())
556 // Clear the URL line edit focus.
557 emit clearUrlLineEditFocus();
560 webEngineViewPointer->back();
564 void BrowserView::mouseForward() const
566 // Go forward if possible.
567 if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoForward())
569 // Clear the URL line edit focus.
570 emit clearUrlLineEditFocus();
573 webEngineViewPointer->forward();
577 void BrowserView::pageLinkHovered(const QString &linkUrl) const
579 // Emit a signal so that the browser window can update the status bar.
580 emit linkHovered(linkUrl);
583 void BrowserView::print() const
588 // Set the resolution to be 300 dpi.
589 printer.setResolution(300);
591 // Create a printer dialog.
592 QPrintDialog printDialog(&printer, webEngineViewPointer);
594 // Display the dialog and print the page if instructed.
595 if (printDialog.exec() == QDialog::Accepted)
596 printWebpage(&printer);
599 void BrowserView::printPreview() const
604 // Set the resolution to be 300 dpi.
605 printer.setResolution(300);
607 // Create a print preview dialog.
608 QPrintPreviewDialog printPreviewDialog(&printer, webEngineViewPointer);
610 // Generate the print preview.
611 connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
613 // Display the dialog.
614 printPreviewDialog.exec();
617 void BrowserView::printWebpage(QPrinter *printerPointer) const
619 // Create an event loop. For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
620 QEventLoop eventLoop;
622 // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
623 // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
624 webEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
626 // Instruct the compiler to ignore the unused parameter.
637 void BrowserView::refresh() const
639 // Reload the website.
640 webEngineViewPointer->reload();
643 void BrowserView::toggleDomStorage() const
645 // Toggle DOM storage.
646 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
648 // Update the DOM storage action.
649 emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
651 // Reload the website.
652 webEngineViewPointer->reload();
655 void BrowserView::toggleJavaScript() const
657 // Toggle JavaScript.
658 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
660 // Update the JavaScript action.
661 emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
663 // Reload the website.
664 webEngineViewPointer->reload();
667 void BrowserView::toggleLocalStorage()
669 // Toggle local storeage.
670 currentPrivacyWebEnginePointer->localStorageEnabled = !currentPrivacyWebEnginePointer->localStorageEnabled;
672 // Update the local storage action.
673 emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
675 // Reload the website.
676 webEngineViewPointer->reload();
679 void BrowserView::updateUrl(const QUrl &url) const
681 // Update the URL line edit.
682 emit updateUrlLineEdit(url);
684 // Update the status of the forward and back buttons.
685 emit updateBackAction(webEngineHistoryPointer->canGoBack());
686 emit updateForwardAction(webEngineHistoryPointer->canGoForward());
688 // Reapply the zoom factor. This is a bug in QWebEngineView that resets the zoom with every load. <https://redmine.stoutner.com/issues/799>
689 webEngineViewPointer->setZoomFactor(currentZoomFactor);