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