]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/views/BrowserView.cpp
82e7e3ee0d0614d4e32e78e8984a2d6da3a8f59c
[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 "filters/MouseEventFilter.h"
25 #include "helpers/DomainsDatabaseHelper.h"
26 #include "helpers/SearchEngineHelper.h"
27 #include "helpers/UserAgentHelper.h"
28 #include "interceptors/UrlRequestInterceptor.h"
29 #include "windows/BrowserWindow.h"
30
31 // Qt framework headers.
32 #include <QAction>
33
34 // Initialize the public static variables.
35 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
36
37 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
38 {
39     // Instantiate the browser view UI.
40     Ui::BrowserView browserViewUi;
41
42     // Setup the UI.
43     browserViewUi.setupUi(this);
44
45     // Get handles for the views.
46     webEngineViewPointer = browserViewUi.webEngineView;
47
48     // Create an off-the-record profile (the default when no profile name is specified).
49     webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
50
51     // Create a WebEngine page.
52     webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
53
54     // Set the WebEngine page.
55     webEngineViewPointer->setPage(webEnginePagePointer);
56
57     // Get handles for the aspects of the WebEngine.
58     webEngineHistoryPointer = webEnginePagePointer->history();
59     webEngineSettingsPointer = webEngineViewPointer->settings();
60     webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
61
62     // Process cookie changes.
63     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
64     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), this, SLOT(cookieRemoved(QNetworkCookie)));
65
66     // Store a copy of the WebEngine default user agent.
67     webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
68
69     // Update the URL line edit when the URL changes.
70     connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
71
72     // Update the progress bar.
73     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
74     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
75     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
76
77     // Instantiate the mouse event filter pointer.
78     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
79
80     // Install the mouse event filter.
81     qApp->installEventFilter(mouseEventFilterPointer);
82
83     // Process mouse forward and back commands.
84     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
85     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
86
87     // Listen for hovered link URLs.
88     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
89
90     // Instantiate the URL request interceptor.
91     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
92
93     // Set the URL request interceptor.
94     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
95
96     // Reapply the domain settings when the host changes.
97     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
98
99     // Don't allow JavaScript to open windows.
100     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
101
102     // Allow keyboard navigation.
103     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
104
105     // Enable full screen support.
106     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
107
108     // Require user interaction to play media.
109     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
110
111     // Limit WebRTC to public IP addresses.
112     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
113
114     // Set the focus on the WebEngine view.
115     webEngineViewPointer->setFocus();
116 }
117
118 BrowserView::~BrowserView()
119 {
120     // Delay the deletion of the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
121     webEnginePagePointer->deleteLater();
122 }
123
124 // 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.
125 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
126 {
127     // Create a url.
128     QUrl url;
129
130     // 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>
131     if (!cookie.domain().startsWith(QStringLiteral(".")))
132     {
133         // Populate the URL.
134         url.setHost(cookie.domain());
135         url.setScheme(QStringLiteral("https"));
136
137         // Clear the domain from the cookie.
138         cookie.setDomain(QStringLiteral(""));
139     }
140
141     // Add the cookie to the store.
142     webEngineCookieStorePointer->setCookie(cookie, url);
143 }
144
145 void BrowserView::applyApplicationSettings()
146 {
147     // Set the search engine URL.
148     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
149
150     // Emit the update search engine actions signal.
151     emit updateSearchEngineActions(Settings::searchEngine());
152 }
153
154 // 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.
155 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
156 void BrowserView::applyDomainSettingsAndReload()
157 {
158     // Apply the domain settings.  `true` reloads the website.
159     applyDomainSettings(webEngineViewPointer->url().host(), true);
160 }
161
162 // 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.
163 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
164 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
165 {
166     // Apply the domain settings  `false` does not reload the website.
167     applyDomainSettings(hostname, false);
168 }
169
170 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
171 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
172 {
173     // Get the record for the hostname.
174     QSqlQuery domainQuery = DomainsDatabaseHelper::getDomainQuery(hostname);
175
176     // Check if the hostname has domain settings.
177     if (domainQuery.isValid())  // The hostname has domain settings.
178     {
179         // Get the domain record.
180         QSqlRecord domainRecord = domainQuery.record();
181
182         // Set the JavaScript status.
183         switch (domainRecord.field(DomainsDatabaseHelper::JAVASCRIPT).value().toInt())
184         {
185             case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
186             {
187                 // Set the default JavaScript status.
188                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
189
190                 break;
191             }
192
193             case (DomainsDatabaseHelper::DISABLED):
194             {
195                 // Disable JavaScript.
196                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
197
198                 break;
199             }
200
201             case (DomainsDatabaseHelper::ENABLED):
202             {
203                 // Enable JavaScript.
204                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
205
206                 break;
207             }
208         }
209
210         // Set local storage.
211         switch (domainRecord.field(DomainsDatabaseHelper::LOCAL_STORAGE).value().toInt())
212         {
213             case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
214             {
215                 // Set the default local storage status.
216                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::localStorage());
217
218                 break;
219             }
220
221             case (DomainsDatabaseHelper::DISABLED):
222             {
223                 // Disable local storage.
224                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
225
226                 break;
227             }
228
229             case (DomainsDatabaseHelper::ENABLED):
230             {
231                 // Enable local storage.
232                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
233
234                 break;
235             }
236         }
237
238         // Set the user agent.
239         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabaseHelper::USER_AGENT).value().toString()));
240
241         // Check if a custom zoom factor is set.
242         if (domainRecord.field(DomainsDatabaseHelper::ZOOM_FACTOR).value().toInt())
243         {
244             // Store the current zoom factor.
245             currentZoomFactor = domainRecord.field(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR).value().toDouble();
246         }
247         else
248         {
249             // Reset the current zoom factor.
250             currentZoomFactor = Settings::zoomFactor();
251         }
252
253         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
254         webEngineViewPointer->setZoomFactor(currentZoomFactor);
255
256         // Apply the domain settings palette to the URL line edit.
257         emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabaseHelper::DOMAIN_NAME).value().toString());
258     }
259     else  // The hostname does not have domain settings.
260     {
261         // Set the JavaScript status.
262         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
263
264         // Set local storage.
265         webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::localStorage());
266
267         // Set the user agent.
268         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
269
270         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
271         currentZoomFactor = Settings::zoomFactor();
272
273         // Set the zoom factor.
274         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
275
276         // Apply the no domain settings palette to the URL line edit.
277         emit updateDomainSettingsIndicator(false, QStringLiteral(""));
278     }
279
280     // Emit the update actions signals.
281     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
282     emit updateLocalStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
283     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent());
284     emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
285
286     // Reload the website if requested.
287     if (reloadWebsite)
288     {
289         webEngineViewPointer->reload();
290     }
291 }
292
293 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
294 {
295     // Store the search engine name.
296     QString searchEngineName = searchEngineActionPointer->text();
297
298     // Strip out any `&` characters.
299     searchEngineName.remove('&');
300
301     // Store the search engine string.
302     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
303 }
304
305 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
306 {
307     // Get the user agent name.
308     QString userAgentName = userAgentActionPointer->text();
309
310     // Strip out any `&` characters.
311     userAgentName.remove('&');
312
313     // Apply the user agent.
314     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
315
316     // Reload the website.
317     webEngineViewPointer->reload();
318 }
319
320 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
321 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
322 {
323     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
324     currentZoomFactor = zoomFactor;
325
326     // Set the zoom factor.
327     webEngineViewPointer->setZoomFactor(zoomFactor);
328 }
329
330 void BrowserView::back() const
331 {
332     // Go back.
333     webEngineViewPointer->back();
334 }
335
336 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
337 {
338     // Add the cookie to the cookie list.
339     emit addCookie(cookie);
340 }
341
342 void BrowserView::cookieRemoved(const QNetworkCookie &cookie) const
343 {
344     // Remove the cookie from the cookie list.
345     emit removeCookie(cookie);
346 }
347
348 void BrowserView::deleteAllCookies() const
349 {
350     // Delete all the cookies.
351     webEngineCookieStorePointer->deleteAllCookies();
352 }
353
354 void BrowserView::deleteCookieFromStore(const QNetworkCookie &cookie) const
355 {
356     // Delete the cookie.
357     webEngineCookieStorePointer->deleteCookie(cookie);
358 }
359
360 void BrowserView::forward() const
361 {
362     // Go forward.
363     webEngineViewPointer->forward();
364 }
365
366 void BrowserView::home() const
367 {
368     // Load the homepage.
369     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
370 }
371
372 void BrowserView::loadFinished() const
373 {
374     // Hide the progress bar.
375     emit hideProgressBar();
376 }
377
378 void BrowserView::loadInitialWebsite()
379 {
380     // Apply the application settings.
381     applyApplicationSettings();
382
383     // Get the arguments.
384     QStringList argumentsStringList = qApp->arguments();
385
386     // Check to see if the arguments lists contains a URL.
387     if (argumentsStringList.size() > 1)
388     {
389         // Load the URL from the arguments list.
390         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
391     }
392     else
393     {
394         // Load the homepage.
395         home();
396     }
397 }
398
399 void BrowserView::loadProgress(const int &progress) const
400 {
401     // Show the progress bar.
402     emit showProgressBar(progress);
403 }
404
405 void BrowserView::loadStarted() const
406 {
407     // Show the progress bar.
408     emit showProgressBar(0);
409 }
410
411 void BrowserView::loadUrlFromLineEdit(QString url) const
412 {
413     // Decide if the text is more likely to be a URL or a search.
414     if (url.startsWith("file://"))  // The text is likely a file URL.
415     {
416         // Load the URL.
417         webEngineViewPointer->load(QUrl::fromUserInput(url));
418     }
419     else if (url.contains("."))  // The text is likely a URL.
420     {
421         // Check if the URL does not start with a valid protocol.
422         if (!url.startsWith("http"))
423         {
424             // Add `https://` to the beginning of the URL.
425             url = "https://" + url;
426         }
427
428         // Load the URL.
429         webEngineViewPointer->load(QUrl::fromUserInput(url));
430     }
431     else  // The text is likely a search.
432     {
433         // Load the search.
434         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
435     }
436 }
437
438 void BrowserView::mouseBack() const
439 {
440     // Go back if possible.
441     if (webEngineHistoryPointer->canGoBack())
442     {
443         // Clear the URL line edit focus.
444         emit clearUrlLineEditFocus();
445
446         // Go back.
447         webEngineViewPointer->back();
448     }
449 }
450
451 void BrowserView::mouseForward() const
452 {
453     // Go forward if possible.
454     if (webEngineHistoryPointer->canGoForward())
455     {
456         // Clear the URL line edit focus.
457         emit clearUrlLineEditFocus();
458
459         // Go forward.
460         webEngineViewPointer->forward();
461     }
462 }
463
464 void BrowserView::pageLinkHovered(const QString &linkUrl) const
465 {
466     // Emit a signal so that the browser window can update the status bar.
467     emit linkHovered(linkUrl);
468 }
469
470 void BrowserView::refresh() const
471 {
472     // Reload the website.
473     webEngineViewPointer->reload();
474 }
475
476 void BrowserView::toggleJavaScript() const
477 {
478     // Toggle JavaScript.
479     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
480
481     // Update the JavaScript icon.
482     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
483
484     // Reload the website.
485     webEngineViewPointer->reload();
486 }
487
488 void BrowserView::toggleLocalStorage() const
489 {
490     // Toggle local storage.
491     webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
492
493     // Update the local storage icon.
494     emit updateLocalStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
495
496     // Reload the website.
497     webEngineViewPointer->reload();
498 }
499
500 void BrowserView::updateUrl(const QUrl &url) const
501 {
502     // Update the URL line edit.
503     emit updateUrlLineEdit(url);
504
505     // Update the status of the forward and back buttons.
506     emit updateBackAction(webEngineHistoryPointer->canGoBack());
507     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
508
509     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
510     webEngineViewPointer->setZoomFactor(currentZoomFactor);
511 }