]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/views/BrowserView.cpp
Allow toggling of custom On-The-Fly entries. https://redmine.stoutner.com/issues/859
[PrivacyBrowserPC.git] / src / views / BrowserView.cpp
1 /*
2  * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 // Application headers.
21 #include "BrowserView.h"
22 #include "Settings.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"
31
32 // Qt framework headers.
33 #include <QAction>
34
35 // Initialize the public static variables.
36 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
37
38 // Construct the class.
39 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
40 {
41     // Initialize the variables.
42     privacyWebEngineListPointer = new QList<PrivacyWebEngine*>;
43
44     // Instantiate the browser view UI.
45     Ui::BrowserView browserViewUi;
46
47     // Setup the UI.
48     browserViewUi.setupUi(this);
49
50     // Get handles for the views.
51     webEngineViewPointer = browserViewUi.webEngineView;
52
53     // Create an off-the-record profile (the default when no profile name is specified).
54     webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
55
56     // Create a WebEngine page.
57     webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
58
59     // Set the WebEngine page.
60     webEngineViewPointer->setPage(webEnginePagePointer);
61
62     // Get handles for the aspects of the WebEngine.
63     webEngineHistoryPointer = webEnginePagePointer->history();
64     webEngineSettingsPointer = webEngineViewPointer->settings();
65     webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
66
67     // Initialize the current privacy web engine pointer.
68     currentPrivacyWebEnginePointer = new PrivacyWebEngine(webEngineViewPointer);
69
70     // Populate the privacy web engine list.
71     privacyWebEngineListPointer->append(currentPrivacyWebEnginePointer);
72
73     // Set the local storage filter.
74     webEngineCookieStorePointer->setCookieFilter([this](const QWebEngineCookieStore::FilterRequest &filterRequest)
75     {
76         //qDebug().noquote().nospace() << "Page URL:  " << filterRequest.firstPartyUrl << ", Local storage URL:  " << filterRequest.origin << ",  Is third-party:  " << filterRequest.thirdParty;
77
78         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
79         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
80         {
81             //qDebug() << "Request blocked.";
82
83             // Return false.
84             return false;
85         }
86
87         /*  TODO.  Waiting for a solution to <https://redmine.stoutner.com/issues/857>.
88         // Check each tab to see if this local storage request should be allowed.
89         for (PrivacyWebEngine *privacyWebEnginePointer : *privacyWebEngineListPointer)
90         {
91             //qDebug().noquote().nospace() << "Local storage:  " << privacyWebEnginePointer->localStorageEnabled << ".  WebEngine URL:  " << webEngineViewPointer->url().host() << ".  Request Host:  " << filterRequest.firstPartyUrl.host();
92
93             // Allow this local storage request if it comes from a tab with local storage enabled.
94             if (privacyWebEnginePointer->localStorageEnabled && (webEngineViewPointer->url().host() == filterRequest.firstPartyUrl.host()))
95             {
96                 //qDebug() << "Request allowed.";
97
98                 // Return true.
99                 return true;
100             }
101         }
102         */
103
104         // Allow the request if it is first party and local storage is enabled.
105         if (!filterRequest.thirdParty && currentPrivacyWebEnginePointer->localStorageEnabled)
106         {
107             // Return true.
108             return true;
109         }
110
111         //qDebug() << "Request blocked.";
112
113         // Block any remaining local storage requests.
114         return false;
115     });
116
117     // Process cookie changes.
118     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
119     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), this, SLOT(cookieRemoved(QNetworkCookie)));
120
121     // Get a list of durable cookies.
122     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
123
124     // Add the durable cookies to the store.
125     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
126         addCookieToStore(*cookiePointer);
127
128     // Store a copy of the WebEngine default user agent.
129     webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
130
131     // Update the URL line edit when the URL changes.
132     connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
133
134     // Update the progress bar.
135     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
136     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
137     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
138
139     // Instantiate the mouse event filter pointer.
140     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
141
142     // Install the mouse event filter.
143     qApp->installEventFilter(mouseEventFilterPointer);
144
145     // Process mouse forward and back commands.
146     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
147     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
148
149     // Listen for hovered link URLs.
150     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
151
152     // Instantiate the URL request interceptor.
153     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
154
155     // Set the URL request interceptor.
156     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
157
158     // Reapply the domain settings when the host changes.
159     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
160
161     // Don't allow JavaScript to open windows.
162     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
163
164     // Allow keyboard navigation.
165     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
166
167     // Enable full screen support.
168     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
169
170     // Require user interaction to play media.
171     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
172
173     // Limit WebRTC to public IP addresses.
174     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
175
176     // Set the focus on the WebEngine view.
177     webEngineViewPointer->setFocus();
178 }
179
180 BrowserView::~BrowserView()
181 {
182     // Delay the deletion of the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
183     webEnginePagePointer->deleteLater();
184 }
185
186 // 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.
187 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
188 {
189     // Create a url.
190     QUrl url;
191
192     // 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>
193     if (!cookie.domain().startsWith(QStringLiteral(".")))
194     {
195         // Populate the URL.
196         url.setHost(cookie.domain());
197         url.setScheme(QStringLiteral("https"));
198
199         // Clear the domain from the cookie.
200         cookie.setDomain(QStringLiteral(""));
201     }
202
203     // Add the cookie to the store.
204     webEngineCookieStorePointer->setCookie(cookie, url);
205 }
206
207 void BrowserView::applyApplicationSettings()
208 {
209     // Set the search engine URL.
210     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
211
212     // Emit the update search engine actions signal.
213     emit updateSearchEngineActions(Settings::searchEngine(), true);
214 }
215
216 // 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.
217 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
218 void BrowserView::applyDomainSettingsAndReload()
219 {
220     // Apply the domain settings.  `true` reloads the website.
221     applyDomainSettings(webEngineViewPointer->url().host(), true);
222 }
223
224 // 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.
225 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
226 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
227 {
228     // Apply the domain settings  `false` does not reload the website.
229     applyDomainSettings(hostname, false);
230 }
231
232 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
233 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
234 {
235     // Get the record for the hostname.
236     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
237
238     // Check if the hostname has domain settings.
239     if (domainQuery.isValid())  // The hostname has domain settings.
240     {
241         // Get the domain record.
242         QSqlRecord domainRecord = domainQuery.record();
243
244         // Set the JavaScript status.
245         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
246         {
247             // Set the default JavaScript status.
248             case (DomainsDatabase::SYSTEM_DEFAULT):
249             {
250                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
251
252                 break;
253             }
254
255             // Disable JavaScript.
256             case (DomainsDatabase::DISABLED):
257             {
258                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
259
260                 break;
261             }
262
263             // Enable JavaScript.
264             case (DomainsDatabase::ENABLED):
265             {
266                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
267
268                 break;
269             }
270         }
271
272         // Set the local storage status.
273         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
274         {
275             // Set the default local storage status.
276             case (DomainsDatabase::SYSTEM_DEFAULT):
277             {
278                 currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
279
280                 break;
281             }
282
283             // Disable local storage.
284             case (DomainsDatabase::DISABLED):
285             {
286                 currentPrivacyWebEnginePointer->localStorageEnabled = false;
287
288                 break;
289             }
290
291             // Enable local storage.
292             case (DomainsDatabase::ENABLED):
293             {
294                 currentPrivacyWebEnginePointer->localStorageEnabled = true;
295
296                 break;
297             }
298         }
299
300         // Set the DOM storage status.
301         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
302         {
303             // Set the default DOM storage status.
304             case (DomainsDatabase::SYSTEM_DEFAULT):
305             {
306                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
307
308                 break;
309             }
310
311             // Disable DOM storage.
312             case (DomainsDatabase::DISABLED):
313             {
314                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
315
316                 break;
317             }
318
319             // Enable DOM storage.
320             case (DomainsDatabase::ENABLED):
321             {
322                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
323
324                 break;
325             }
326         }
327
328         // Set the user agent.
329         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
330
331         // Check if a custom zoom factor is set.
332         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
333         {
334             // Store the current zoom factor.
335             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
336         }
337         else
338         {
339             // Reset the current zoom factor.
340             currentZoomFactor = Settings::zoomFactor();
341         }
342
343         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
344         webEngineViewPointer->setZoomFactor(currentZoomFactor);
345
346         // Apply the domain settings palette to the URL line edit.
347         emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString());
348     }
349     else  // The hostname does not have domain settings.
350     {
351         // Set the JavaScript status.
352         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
353
354         // Set the local storage status.
355         currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
356
357         // Set DOM storage.
358         webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
359
360         // Set the user agent.
361         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
362
363         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
364         currentZoomFactor = Settings::zoomFactor();
365
366         // Set the zoom factor.
367         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
368
369         // Apply the no domain settings palette to the URL line edit.
370         emit updateDomainSettingsIndicator(false, QStringLiteral(""));
371     }
372
373     // Emit the update actions signals.
374     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
375     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
376     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
377     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), true);
378     emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
379
380     // Reload the website if requested.
381     if (reloadWebsite)
382         webEngineViewPointer->reload();
383 }
384
385 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
386 {
387     // Store the search engine name.
388     QString searchEngineName = searchEngineActionPointer->text();
389
390     // Strip out any `&` characters.
391     searchEngineName.remove('&');
392
393     // Store the search engine string.
394     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
395
396     // Update the search engine actionas.
397     emit updateSearchEngineActions(searchEngineName, false);
398 }
399
400 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
401 {
402     // Get the user agent name.
403     QString userAgentName = userAgentActionPointer->text();
404
405     // Strip out any `&` characters.
406     userAgentName.remove('&');
407
408     // Apply the user agent.
409     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
410
411     // Update the user agent actions.
412     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), false);
413
414     // Reload the website.
415     webEngineViewPointer->reload();
416 }
417
418 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
419 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
420 {
421     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
422     currentZoomFactor = zoomFactor;
423
424     // Set the zoom factor.
425     webEngineViewPointer->setZoomFactor(zoomFactor);
426 }
427
428 void BrowserView::back() const
429 {
430     // Go back.
431     webEngineViewPointer->back();
432 }
433
434 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
435 {
436     // Add the cookie to the cookie list.
437     emit addCookie(cookie);
438 }
439
440 void BrowserView::cookieRemoved(const QNetworkCookie &cookie) const
441 {
442     // Remove the cookie from the cookie list.
443     emit removeCookie(cookie);
444 }
445
446 void BrowserView::deleteAllCookies() const
447 {
448     // Delete all the cookies.
449     webEngineCookieStorePointer->deleteAllCookies();
450 }
451
452 void BrowserView::deleteCookieFromStore(const QNetworkCookie &cookie) const
453 {
454     // Delete the cookie.
455     webEngineCookieStorePointer->deleteCookie(cookie);
456 }
457
458 void BrowserView::forward() const
459 {
460     // Go forward.
461     webEngineViewPointer->forward();
462 }
463
464 void BrowserView::home() const
465 {
466     // Load the homepage.
467     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
468 }
469
470 void BrowserView::loadFinished() const
471 {
472     // Hide the progress bar.
473     emit hideProgressBar();
474 }
475
476 void BrowserView::loadInitialWebsite()
477 {
478     // Apply the application settings.
479     applyApplicationSettings();
480
481     // Get the arguments.
482     QStringList argumentsStringList = qApp->arguments();
483
484     // Check to see if the arguments lists contains a URL.
485     if (argumentsStringList.size() > 1)
486     {
487         // Load the URL from the arguments list.
488         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
489     }
490     else
491     {
492         // Load the homepage.
493         home();
494     }
495 }
496
497 void BrowserView::loadProgress(const int &progress) const
498 {
499     // Show the progress bar.
500     emit showProgressBar(progress);
501 }
502
503 void BrowserView::loadStarted() const
504 {
505     // Show the progress bar.
506     emit showProgressBar(0);
507 }
508
509 void BrowserView::loadUrlFromLineEdit(QString url) const
510 {
511     // Decide if the text is more likely to be a URL or a search.
512     if (url.startsWith("file://"))  // The text is likely a file URL.
513     {
514         // Load the URL.
515         webEngineViewPointer->load(QUrl::fromUserInput(url));
516     }
517     else if (url.contains("."))  // The text is likely a URL.
518     {
519         // Check if the URL does not start with a valid protocol.
520         if (!url.startsWith("http"))
521         {
522             // Add `https://` to the beginning of the URL.
523             url = "https://" + url;
524         }
525
526         // Load the URL.
527         webEngineViewPointer->load(QUrl::fromUserInput(url));
528     }
529     else  // The text is likely a search.
530     {
531         // Load the search.
532         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
533     }
534 }
535
536 void BrowserView::mouseBack() const
537 {
538     // Go back if possible.
539     if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoBack())
540     {
541         // Clear the URL line edit focus.
542         emit clearUrlLineEditFocus();
543
544         // Go back.
545         webEngineViewPointer->back();
546     }
547 }
548
549 void BrowserView::mouseForward() const
550 {
551     // Go forward if possible.
552     if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoForward())
553     {
554         // Clear the URL line edit focus.
555         emit clearUrlLineEditFocus();
556
557         // Go forward.
558         webEngineViewPointer->forward();
559     }
560 }
561
562 void BrowserView::pageLinkHovered(const QString &linkUrl) const
563 {
564     // Emit a signal so that the browser window can update the status bar.
565     emit linkHovered(linkUrl);
566 }
567
568 void BrowserView::refresh() const
569 {
570     // Reload the website.
571     webEngineViewPointer->reload();
572 }
573
574 void BrowserView::toggleDomStorage() const
575 {
576     // Toggle DOM storage.
577     webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
578
579     // Update the DOM storage action.
580     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
581
582     // Reload the website.
583     webEngineViewPointer->reload();
584 }
585
586 void BrowserView::toggleJavaScript() const
587 {
588     // Toggle JavaScript.
589     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
590
591     // Update the JavaScript action.
592     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
593
594     // Reload the website.
595     webEngineViewPointer->reload();
596 }
597
598 void BrowserView::toggleLocalStorage()
599 {
600     // Toggle local storeage.
601     currentPrivacyWebEnginePointer->localStorageEnabled = !currentPrivacyWebEnginePointer->localStorageEnabled;
602
603     // Update the local storage action.
604     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
605
606     // Reload the website.
607     webEngineViewPointer->reload();
608 }
609
610 void BrowserView::updateUrl(const QUrl &url) const
611 {
612     // Update the URL line edit.
613     emit updateUrlLineEdit(url);
614
615     // Update the status of the forward and back buttons.
616     emit updateBackAction(webEngineHistoryPointer->canGoBack());
617     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
618
619     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
620     webEngineViewPointer->setZoomFactor(currentZoomFactor);
621 }