Add User Agent to Domain Settings.
[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 "UrlRequestInterceptor.h"
26 #include "dialogs/DomainSettingsDialog.h"
27 #include "helpers/DomainsDatabaseHelper.h"
28 #include "helpers/SearchEngineHelper.h"
29 #include "helpers/UserAgentHelper.h"
30 #include "windows/BrowserWindow.h"
31
32 // Qt framework headers.
33 #include <QAction>
34 #include <QWebEngineProfile>
35
36 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
37 {
38     // Instantiate the browser view UI.
39     Ui::BrowserView browserViewUi;
40
41     // Setup the UI.
42     browserViewUi.setupUi(this);
43
44     // Get handles for the views.
45     backButtonPointer = browserViewUi.backButton;
46     forwardButtonPointer = browserViewUi.forwardButton;
47     QPushButton *refreshButtonPointer = browserViewUi.refreshButton;
48     QPushButton *homeButtonPointer = browserViewUi.homeButton;
49     urlLineEditPointer = browserViewUi.urlLineEdit;
50     javaScriptButtonPointer = browserViewUi.javaScript;
51     QPushButton *domainSettingsButtonPointer = browserViewUi.domainSettingsButton;
52     webEngineViewPointer = browserViewUi.webEngineView;
53
54     // Get handles for the aspects of the WebEngine.
55     QWebEnginePage *webEnginePagePointer = webEngineViewPointer->page();
56     webEngineHistoryPointer = webEnginePagePointer->history();
57     webEngineProfilePointer = webEnginePagePointer->profile();
58     webEngineSettingsPointer = webEngineViewPointer->settings();
59
60     // Update the webengine view from the URL line edit.
61     connect(urlLineEditPointer, SIGNAL(returnKeyPressed(const QString)), this, SLOT(loadUrlFromTextBox(const QString)));
62
63     // Update the URL line edit from the webengine view.
64     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(updateInterface()));
65     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(updateInterface()));
66     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(updateInterface()));
67
68     // Setup the URL bar buttons.
69     connect(backButtonPointer, SIGNAL(clicked()), webEngineViewPointer, SLOT(back()));
70     connect(forwardButtonPointer, SIGNAL(clicked()), webEngineViewPointer, SLOT(forward()));
71     connect(refreshButtonPointer, SIGNAL(clicked()), webEngineViewPointer, SLOT(reload()));
72     connect(homeButtonPointer, SIGNAL(clicked()), this, SLOT(goHome()));
73     connect(javaScriptButtonPointer, SIGNAL(clicked()), this, SLOT(toggleJavaScript()));
74     connect(domainSettingsButtonPointer, SIGNAL(clicked()), this, SLOT(openDomainSettings()));
75
76     // Get the URL line edit palettes.
77     noDomainSettingsPalette = urlLineEditPointer->palette();
78     domainSettingsPalette = urlLineEditPointer->palette();
79
80     // Modify the domain settings palette.
81     domainSettingsPalette.setColor(QPalette::Base, QColor("#C8E6C9"));
82
83     // Instantiate the mouse event pointer.
84     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter(webEngineViewPointer);
85
86     // Install the mouse event filter.
87     qApp->installEventFilter(mouseEventFilterPointer);
88
89     // Listen for hovered link URLs.
90     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
91
92     // Instantiate the URL request interceptor.
93     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
94
95     // Set the URL request interceptor.
96     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
97
98     // Reapply the domain settings when the host changes.
99     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
100
101     // Disable the cache.
102     webEngineProfilePointer->setHttpCacheType(QWebEngineProfile::NoCache);
103
104     // Don't allow JavaScript to open windows.
105     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
106
107     // Set the focus on the WebEngine view.
108     webEngineViewPointer->setFocus();
109 }
110
111 void BrowserView::applyApplicationSettings()
112 {
113     // Set the search engine URL.
114     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
115
116     // Emit the search engine updated signal, which causes the on-the-fly menu to be updated.
117     emit searchEngineUpdated(Settings::searchEngine());
118 }
119
120 // 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.
121 void BrowserView::applyDomainSettingsAndReload() const
122 {
123     // Apply the domain settings.  `true` reloads the website.
124     applyDomainSettings(webEngineViewPointer->url().host(), true);
125 }
126
127 // 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.
128 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname) const
129 {
130     // Apply the domain settings  `false` does not reload the website.
131     applyDomainSettings(hostname, false);
132 }
133
134 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite) const
135 {
136     // Get the record for the hostname.
137     QSqlQuery domainQuery = DomainsDatabaseHelper::getDomainQuery(hostname);
138
139     // Check if the hostname has domain settings.
140     if (domainQuery.isValid())  // The hostname has domain settings.
141     {
142         // Get the domain record.
143         QSqlRecord domainRecord = domainQuery.record();
144
145         // Set the JavaScript status.
146         switch (domainRecord.field(DomainsDatabaseHelper::JAVASCRIPT).value().toInt())
147         {
148             case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
149             {
150                 // Set the default JavaScript status.
151                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
152
153                 break;
154             }
155
156             case (DomainsDatabaseHelper::DISABLED):
157             {
158                 // Disable JavaScript.
159                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
160
161                 break;
162             }
163
164             case (DomainsDatabaseHelper::ENABLED):
165             {
166                 // Enable JavaScript.
167                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
168
169                 break;
170             }
171         }
172
173         // Set the user agent.
174         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabaseHelper::USER_AGENT).value().toString()));
175
176         // Set the zoom factor.
177         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
178
179         // Apply the domain settings palette to the URL line edit.
180         urlLineEditPointer->setPalette(domainSettingsPalette);
181     }
182     else  // The hostname does not have domain settings.
183     {
184         // Set the JavaScript status.
185         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
186
187         // Set the user agent.
188         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
189
190         // Set the zoom factor.
191         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
192
193         // Apply the no domain settings palette to the URL line edit.
194         urlLineEditPointer->setPalette(noDomainSettingsPalette);
195     }
196
197     // Update the JavaScript button.
198     if (webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled))
199     {
200         javaScriptButtonPointer->setIcon(QIcon(":/icons/javascript-warning"));
201     }
202     else
203     {
204         javaScriptButtonPointer->setIcon(QIcon(":/icons/privacy-mode"));
205     }
206
207     // Emit the on-the-fly menu update signals.
208     emit userAgentUpdated(webEngineProfilePointer->httpUserAgent());
209     emit zoomFactorUpdated(Settings::zoomFactor());
210
211     // Reload the website if requested.
212     if (reloadWebsite)
213     {
214         webEngineViewPointer->reload();
215     }
216 }
217
218 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
219 {
220     // Store the search engine name.
221     QString searchEngineName = searchEngineActionPointer->text();
222
223     // Strip out any `&` characters.
224     searchEngineName.remove('&');
225
226     // Store the search engine string.
227     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
228 }
229
230 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
231 {
232     // Get the user agent name.
233     QString userAgentName = userAgentActionPointer->text();
234
235     // Strip out any `&` characters.
236     userAgentName.remove('&');
237
238     // Apply the user agent.
239     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
240
241     // Reload the website.
242     webEngineViewPointer->reload();
243 }
244
245 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor) const
246 {
247     // Set the zoom factor.
248     webEngineViewPointer->setZoomFactor(zoomFactor);
249 }
250
251 void BrowserView::goHome() const
252 {
253     // Load the homepage.
254     webEngineViewPointer->setUrl(QUrl::fromUserInput(Settings::homepage()));
255 }
256
257 void BrowserView::loadInitialWebsite()
258 {
259     // Apply the application settings.
260     applyApplicationSettings();
261
262     // Get the arguments.
263     QStringList argumentsStringList = qApp->arguments();
264
265     // Check to see if the arguments lists contains a URL.
266     if (argumentsStringList.size() > 1)
267     {
268         // Load the URL from the arguments list.
269         webEngineViewPointer->setUrl(QUrl::fromUserInput(argumentsStringList.at(1)));
270     }
271     else
272     {
273         // Load the homepage.
274         goHome();
275     }
276 }
277
278 void BrowserView::loadUrlFromTextBox(QString urlFromUser) const
279 {
280     // Remove the focus from the URL line edit.
281     urlLineEditPointer->clearFocus();
282
283     // Decide if the text is more likely to be a URL or a search.
284     if (urlFromUser.contains("."))  // The text is likely a URL.
285     {
286         // Check if the URL does not start with a valid protocol.
287         if (!urlFromUser.startsWith("http") && !urlFromUser.startsWith("file://"))
288         {
289             // Add `https://` to the beginning of the URL.
290             urlFromUser = "https://" + urlFromUser;
291         }
292
293         // Load the URL.
294         webEngineViewPointer->setUrl(QUrl::fromUserInput(urlFromUser));
295     }
296     else  // The text is likely a search.
297     {
298         // Load the search.
299         webEngineViewPointer->setUrl(QUrl::fromUserInput(searchEngineUrl + urlFromUser));
300     }
301 }
302
303 void BrowserView::openDomainSettings() const
304 {
305     // Instantiate the domain settings window.
306     DomainSettingsDialog *domainSettingsDialogPointer = new DomainSettingsDialog();
307
308     // Set the dialog window title.
309     domainSettingsDialogPointer->setWindowTitle(i18nc("The domain settings dialog title", "Domain Settings"));
310
311     // Set the modality.
312     domainSettingsDialogPointer->setWindowModality(Qt::WindowModality::WindowModal);;
313
314     // Show the dialog.
315     domainSettingsDialogPointer->show();
316
317     // Reload the tabs when domain settings are updated.
318     connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), this, SLOT(applyDomainSettingsAndReload()));
319 }
320
321 void BrowserView::pageLinkHovered(const QString &linkUrl) const
322 {
323     // Emit a signal so that the browser window can update the status bar.
324     emit linkHovered(linkUrl);
325 }
326
327 void BrowserView::toggleJavaScript() const
328 {
329     // Toggle JavaScript.
330     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
331
332     // Update the JavaScript button.
333     if (webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled))
334     {
335         javaScriptButtonPointer->setIcon(QIcon(":/icons/javascript-warning"));
336     }
337     else
338     {
339         javaScriptButtonPointer->setIcon(QIcon(":/icons/privacy-mode"));
340     }
341
342     // Reload the website.
343     webEngineViewPointer->reload();
344 }
345
346 void BrowserView::updateInterface() const
347 {
348     // Update the URL line edit if it does not have focus.
349     if (!urlLineEditPointer->hasFocus())
350     {
351         // Update the URL line edit.
352         urlLineEditPointer->setText(webEngineViewPointer->url().toString());
353     }
354
355     // Update the status of the forward and back buttons.
356     backButtonPointer->setEnabled(webEngineHistoryPointer->canGoBack());
357     forwardButtonPointer->setEnabled(webEngineHistoryPointer->canGoForward());
358
359     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  Hopefully it will be fixed in Qt6.  <https://bugreports.qt.io/browse/QTBUG-51992>
360     webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
361 }