]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/views/BrowserView.cpp
Fix the loading of websites when the user agent changes. https://redmine.stoutner...
[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.  TODO.  Remove.
61     connect(urlLineEditPointer, SIGNAL(returnKeyPressed(const QString)), this, SLOT(loadUrlFromLineEdit(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.  TODO.  Remove.
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(home()));
73     connect(javaScriptButtonPointer, SIGNAL(clicked()), this, SLOT(toggleJavaScript()));
74     connect(domainSettingsButtonPointer, SIGNAL(clicked()), this, SLOT(openDomainSettings()));
75
76     // Get the URL line edit palettes.  TODO.  Remove.
77     noDomainSettingsPalette = urlLineEditPointer->palette();
78     domainSettingsPalette = urlLineEditPointer->palette();
79
80     // Modify the domain settings palette.  TODO.  Remove.
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);  // TODO.  Remove.
181         emit updateDomainSettingsIndicator(true);
182     }
183     else  // The hostname does not have domain settings.
184     {
185         // Set the JavaScript status.
186         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScript());
187
188         // Set the user agent.
189         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
190
191         // Set the zoom factor.
192         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
193
194         // Apply the no domain settings palette to the URL line edit.
195         urlLineEditPointer->setPalette(noDomainSettingsPalette);  // TODO.  Remove.
196         emit updateDomainSettingsIndicator(false);
197     }
198
199     // Update the JavaScript button.
200     if (webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled))
201     {
202         javaScriptButtonPointer->setIcon(QIcon(":/icons/javascript-warning"));
203     }
204     else
205     {
206         javaScriptButtonPointer->setIcon(QIcon(":/icons/privacy-mode"));
207     }
208
209     // Emit the on-the-fly menu update signals.
210     emit userAgentUpdated(webEngineProfilePointer->httpUserAgent());
211     emit zoomFactorUpdated(Settings::zoomFactor());
212
213     // Reload the website if requested.
214     if (reloadWebsite)
215     {
216         webEngineViewPointer->reload();
217     }
218 }
219
220 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
221 {
222     // Store the search engine name.
223     QString searchEngineName = searchEngineActionPointer->text();
224
225     // Strip out any `&` characters.
226     searchEngineName.remove('&');
227
228     // Store the search engine string.
229     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
230 }
231
232 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
233 {
234     // Get the user agent name.
235     QString userAgentName = userAgentActionPointer->text();
236
237     // Strip out any `&` characters.
238     userAgentName.remove('&');
239
240     // Apply the user agent.
241     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
242
243     // Reload the website.
244     webEngineViewPointer->reload();
245 }
246
247 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor) const
248 {
249     // Set the zoom factor.
250     webEngineViewPointer->setZoomFactor(zoomFactor);
251 }
252
253 void BrowserView::back() const
254 {
255     // Go back.
256     webEngineViewPointer->back();
257 }
258
259 void BrowserView::forward() const
260 {
261     // Go forward.
262     webEngineViewPointer->forward();
263 }
264
265 void BrowserView::home() const
266 {
267     // Load the homepage.
268     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
269 }
270
271 void BrowserView::loadInitialWebsite()
272 {
273     // Apply the application settings.
274     applyApplicationSettings();
275
276     // Get the arguments.
277     QStringList argumentsStringList = qApp->arguments();
278
279     // Check to see if the arguments lists contains a URL.
280     if (argumentsStringList.size() > 1)
281     {
282         // Load the URL from the arguments list.
283         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
284     }
285     else
286     {
287         // Load the homepage.
288         home();
289     }
290 }
291
292 void BrowserView::loadUrlFromLineEdit(QString url) const
293 {
294     // Remove the focus from the URL line edit.  TODO.  Remove.
295     urlLineEditPointer->clearFocus();
296
297     // Decide if the text is more likely to be a URL or a search.
298     if (url.contains("."))  // The text is likely a URL.
299     {
300         // Check if the URL does not start with a valid protocol.
301         if (!url.startsWith("http") && !url.startsWith("file://"))
302         {
303             // Add `https://` to the beginning of the URL.
304             url = "https://" + url;
305         }
306
307         // Load the URL.
308         webEngineViewPointer->load(QUrl::fromUserInput(url));
309     }
310     else  // The text is likely a search.
311     {
312         // Load the search.
313         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
314     }
315 }
316
317 void BrowserView::openDomainSettings() const
318 {
319     // Instantiate the domain settings window.
320     DomainSettingsDialog *domainSettingsDialogPointer = new DomainSettingsDialog();
321
322     // Set the dialog window title.
323     domainSettingsDialogPointer->setWindowTitle(i18nc("The domain settings dialog title", "Domain Settings"));
324
325     // Set the modality.
326     domainSettingsDialogPointer->setWindowModality(Qt::WindowModality::WindowModal);;
327
328     // Show the dialog.
329     domainSettingsDialogPointer->show();
330
331     // Reload the tabs when domain settings are updated.
332     connect(domainSettingsDialogPointer, SIGNAL(domainSettingsUpdated()), this, SLOT(applyDomainSettingsAndReload()));
333 }
334
335 void BrowserView::pageLinkHovered(const QString &linkUrl) const
336 {
337     // Emit a signal so that the browser window can update the status bar.
338     emit linkHovered(linkUrl);
339 }
340
341 void BrowserView::refresh() const
342 {
343     // Reload the website.
344     webEngineViewPointer->reload();
345 }
346
347 void BrowserView::toggleJavaScript() const
348 {
349     // Toggle JavaScript.
350     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
351
352     // Update the JavaScript button.
353     if (webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled))
354     {
355         javaScriptButtonPointer->setIcon(QIcon(":/icons/javascript-warning"));
356     }
357     else
358     {
359         javaScriptButtonPointer->setIcon(QIcon(":/icons/privacy-mode"));
360     }
361
362     // Reload the website.
363     webEngineViewPointer->reload();
364 }
365
366 void BrowserView::updateInterface() const
367 {
368     // Update the URL line edit if it does not have focus.  TODO.  Remove block.
369     if (!urlLineEditPointer->hasFocus())
370     {
371         // Update the URL line edit.
372         urlLineEditPointer->setText(webEngineViewPointer->url().toString());
373     }
374
375     // Update the URL line edit.
376     emit updateUrlLineEdit(webEngineViewPointer->url().toString());
377
378     // Update the status of the forward and back buttons.
379     backButtonPointer->setEnabled(webEngineHistoryPointer->canGoBack());  // TODO Remove.
380     emit updateBackAction(webEngineHistoryPointer->canGoBack());
381     forwardButtonPointer->setEnabled(webEngineHistoryPointer->canGoForward());  // TODO Remove.
382     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
383
384     // 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>
385     webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
386 }