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