]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/views/BrowserView.cpp
2a23d8126ca54b80aa8afdf7747c79be853cacf0
[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 "databases/CookiesDatabase.h"
25 #include "databases/DomainsDatabase.h"
26 #include "dialogs/SaveDialog.h"
27 #include "filters/MouseEventFilter.h"
28 #include "helpers/SearchEngineHelper.h"
29 #include "helpers/UserAgentHelper.h"
30 #include "interceptors/UrlRequestInterceptor.h"
31 #include "windows/BrowserWindow.h"
32
33 // KDE Framework headers.
34 #include <KIO/FileCopyJob>
35 #include <KIO/JobUiDelegate>
36
37 // Qt toolkit headers.
38 #include <QAction>
39 #include <QFileDialog>
40 #include <QPrintDialog>
41 #include <QPrintPreviewDialog>
42 #include <QPrinter>
43
44 // Initialize the public static variables.
45 QString BrowserView::webEngineDefaultUserAgent = QStringLiteral("");
46
47 // Construct the class.
48 BrowserView::BrowserView(QWidget *parent) : QWidget(parent)
49 {
50     // Initialize the variables.
51     privacyWebEngineListPointer = new QList<PrivacyWebEngine*>;
52
53     // Instantiate the browser view UI.
54     Ui::BrowserView browserViewUi;
55
56     // Setup the UI.
57     browserViewUi.setupUi(this);
58
59     // Get handles for the views.
60     webEngineViewPointer = browserViewUi.webEngineView;
61
62     // Create an off-the-record profile (the default when no profile name is specified).
63     webEngineProfilePointer = new QWebEngineProfile(QStringLiteral(""));
64
65     // Create a WebEngine page.
66     webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);
67
68     // Set the WebEngine page.
69     webEngineViewPointer->setPage(webEnginePagePointer);
70
71     // Handle full screen requests.
72     connect(webEnginePagePointer, SIGNAL(fullScreenRequested(QWebEngineFullScreenRequest)), this, SLOT(fullScreenRequested(QWebEngineFullScreenRequest)));
73
74     // Get handles for the aspects of the WebEngine.
75     webEngineHistoryPointer = webEnginePagePointer->history();
76     webEngineSettingsPointer = webEngineViewPointer->settings();
77     webEngineCookieStorePointer = webEngineProfilePointer->cookieStore();
78
79     // Initialize the current privacy web engine pointer.
80     currentPrivacyWebEnginePointer = new PrivacyWebEngine(webEngineViewPointer);
81
82     // Populate the privacy web engine list.
83     privacyWebEngineListPointer->append(currentPrivacyWebEnginePointer);
84
85     // Set the local storage filter.
86     webEngineCookieStorePointer->setCookieFilter([this](const QWebEngineCookieStore::FilterRequest &filterRequest)
87     {
88         //qDebug().noquote().nospace() << "Page URL:  " << filterRequest.firstPartyUrl << ", Local storage URL:  " << filterRequest.origin << ",  Is third-party:  " << filterRequest.thirdParty;
89
90         // Block all third party local storage requests, including the sneaky ones that don't register a first party URL.
91         if (filterRequest.thirdParty || (filterRequest.firstPartyUrl == QStringLiteral("")))
92         {
93             //qDebug() << "Request blocked.";
94
95             // Return false.
96             return false;
97         }
98
99         /*  TODO.  Waiting for a solution to <https://redmine.stoutner.com/issues/857>.
100         // Check each tab to see if this local storage request should be allowed.
101         for (PrivacyWebEngine *privacyWebEnginePointer : *privacyWebEngineListPointer)
102         {
103             //qDebug().noquote().nospace() << "Local storage:  " << privacyWebEnginePointer->localStorageEnabled << ".  WebEngine URL:  " << webEngineViewPointer->url().host() << ".  Request Host:  " << filterRequest.firstPartyUrl.host();
104
105             // Allow this local storage request if it comes from a tab with local storage enabled.
106             if (privacyWebEnginePointer->localStorageEnabled && (webEngineViewPointer->url().host() == filterRequest.firstPartyUrl.host()))
107             {
108                 //qDebug() << "Request allowed.";
109
110                 // Return true.
111                 return true;
112             }
113         }
114         */
115
116         // Allow the request if it is first party and local storage is enabled.
117         if (!filterRequest.thirdParty && currentPrivacyWebEnginePointer->localStorageEnabled)
118         {
119             // Return true.
120             return true;
121         }
122
123         //qDebug() << "Request blocked.";
124
125         // Block any remaining local storage requests.
126         return false;
127     });
128
129     // Process cookie changes.
130     connect(webEngineCookieStorePointer, SIGNAL(cookieAdded(QNetworkCookie)), this, SLOT(cookieAdded(QNetworkCookie)));
131     connect(webEngineCookieStorePointer, SIGNAL(cookieRemoved(QNetworkCookie)), this, SLOT(cookieRemoved(QNetworkCookie)));
132
133     // Get a list of durable cookies.
134     QList<QNetworkCookie*> *durableCookiesListPointer = CookiesDatabase::getCookies();
135
136     // Add the durable cookies to the store.
137     for (QNetworkCookie *cookiePointer : *durableCookiesListPointer)
138         addCookieToStore(*cookiePointer);
139
140     // Store a copy of the WebEngine default user agent.
141     webEngineDefaultUserAgent = webEngineProfilePointer->httpUserAgent();
142
143     // Update the URL line edit when the URL changes.
144     connect(webEngineViewPointer, SIGNAL(urlChanged(const QUrl)), this, SLOT(updateUrl(const QUrl)));
145
146     // Update the progress bar.
147     connect(webEngineViewPointer, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
148     connect(webEngineViewPointer, SIGNAL(loadProgress(const int)), this, SLOT(loadProgress(const int)));
149     connect(webEngineViewPointer, SIGNAL(loadFinished(const bool)), this, SLOT(loadFinished()));
150
151     // Instantiate the mouse event filter pointer.
152     MouseEventFilter *mouseEventFilterPointer = new MouseEventFilter();
153
154     // Install the mouse event filter.
155     qApp->installEventFilter(mouseEventFilterPointer);
156
157     // Process mouse forward and back commands.
158     connect(mouseEventFilterPointer, SIGNAL(mouseBack()), this, SLOT(mouseBack()));
159     connect(mouseEventFilterPointer, SIGNAL(mouseForward()), this, SLOT(mouseForward()));
160
161     // Listen for hovered link URLs.
162     connect(webEnginePagePointer, SIGNAL(linkHovered(const QString)), this, SLOT(pageLinkHovered(const QString)));
163
164     // Instantiate the URL request interceptor.
165     UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();
166
167     // Set the URL request interceptor.
168     webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);
169
170     // Handle file downloads.
171     connect(webEngineProfilePointer, SIGNAL(downloadRequested(QWebEngineDownloadItem *)), this, SLOT(showSaveDialog(QWebEngineDownloadItem *)));
172
173     // Reapply the domain settings when the host changes.
174     connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(QString)), this, SLOT(applyDomainSettingsWithoutReloading(QString)));
175
176     // Don't allow JavaScript to open windows.
177     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
178
179     // Allow keyboard navigation.
180     webEngineSettingsPointer->setAttribute(QWebEngineSettings::SpatialNavigationEnabled, true);
181
182     // Enable full screen support.
183     webEngineSettingsPointer->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
184
185     // Require user interaction to play media.
186     webEngineSettingsPointer->setAttribute(QWebEngineSettings::PlaybackRequiresUserGesture, true);
187
188     // Limit WebRTC to public IP addresses.
189     webEngineSettingsPointer->setAttribute(QWebEngineSettings::WebRTCPublicInterfacesOnly, true);
190
191     // Set the focus on the WebEngine view.
192     webEngineViewPointer->setFocus();
193 }
194
195 BrowserView::~BrowserView()
196 {
197     // Delay the deletion of the WebEngine page to prevent the following error:  `Release of profile requested but WebEnginePage still not deleted. Expect troubles !`
198     webEnginePagePointer->deleteLater();
199 }
200
201 // 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.
202 void BrowserView::addCookieToStore(QNetworkCookie cookie) const
203 {
204     // Create a url.
205     QUrl url;
206
207     // 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>
208     if (!cookie.domain().startsWith(QStringLiteral(".")))
209     {
210         // Populate the URL.
211         url.setHost(cookie.domain());
212         url.setScheme(QStringLiteral("https"));
213
214         // Clear the domain from the cookie.
215         cookie.setDomain(QStringLiteral(""));
216     }
217
218     // Add the cookie to the store.
219     webEngineCookieStorePointer->setCookie(cookie, url);
220 }
221
222 void BrowserView::applyApplicationSettings()
223 {
224     // Set the search engine URL.
225     searchEngineUrl = SearchEngineHelper::getSearchUrl(Settings::searchEngine());
226
227     // Emit the update search engine actions signal.
228     emit updateSearchEngineActions(Settings::searchEngine(), true);
229 }
230
231 // 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.
232 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
233 void BrowserView::applyDomainSettingsAndReload()
234 {
235     // Apply the domain settings.  `true` reloads the website.
236     applyDomainSettings(webEngineViewPointer->url().host(), true);
237 }
238
239 // 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.
240 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
241 void BrowserView::applyDomainSettingsWithoutReloading(const QString &hostname)
242 {
243     // Apply the domain settings  `false` does not reload the website.
244     applyDomainSettings(hostname, false);
245 }
246
247 // Once <https://redmine.stoutner.com/issues/799> has been resolved this can be `const`.
248 void BrowserView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
249 {
250     // Get the record for the hostname.
251     QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);
252
253     // Check if the hostname has domain settings.
254     if (domainQuery.isValid())  // The hostname has domain settings.
255     {
256         // Get the domain record.
257         QSqlRecord domainRecord = domainQuery.record();
258
259         // Set the JavaScript status.
260         switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
261         {
262             // Set the default JavaScript status.
263             case (DomainsDatabase::SYSTEM_DEFAULT):
264             {
265                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
266
267                 break;
268             }
269
270             // Disable JavaScript.
271             case (DomainsDatabase::DISABLED):
272             {
273                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
274
275                 break;
276             }
277
278             // Enable JavaScript.
279             case (DomainsDatabase::ENABLED):
280             {
281                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
282
283                 break;
284             }
285         }
286
287         // Set the local storage status.
288         switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
289         {
290             // Set the default local storage status.
291             case (DomainsDatabase::SYSTEM_DEFAULT):
292             {
293                 currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
294
295                 break;
296             }
297
298             // Disable local storage.
299             case (DomainsDatabase::DISABLED):
300             {
301                 currentPrivacyWebEnginePointer->localStorageEnabled = false;
302
303                 break;
304             }
305
306             // Enable local storage.
307             case (DomainsDatabase::ENABLED):
308             {
309                 currentPrivacyWebEnginePointer->localStorageEnabled = true;
310
311                 break;
312             }
313         }
314
315         // Set the DOM storage status.
316         switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
317         {
318             // Set the default DOM storage status.
319             case (DomainsDatabase::SYSTEM_DEFAULT):
320             {
321                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
322
323                 break;
324             }
325
326             // Disable DOM storage.
327             case (DomainsDatabase::DISABLED):
328             {
329                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
330
331                 break;
332             }
333
334             // Enable DOM storage.
335             case (DomainsDatabase::ENABLED):
336             {
337                 webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
338
339                 break;
340             }
341         }
342
343         // Set the user agent.
344         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));
345
346         // Check if a custom zoom factor is set.
347         if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
348         {
349             // Store the current zoom factor.
350             currentZoomFactor = domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble();
351         }
352         else
353         {
354             // Reset the current zoom factor.
355             currentZoomFactor = Settings::zoomFactor();
356         }
357
358         // Set the zoom factor.    The use of `currentZoomFactor` can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
359         webEngineViewPointer->setZoomFactor(currentZoomFactor);
360
361         // Apply the domain settings palette to the URL line edit.
362         emit updateDomainSettingsIndicator(true, domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString());
363     }
364     else  // The hostname does not have domain settings.
365     {
366         // Set the JavaScript status.
367         webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());
368
369         // Set the local storage status.
370         currentPrivacyWebEnginePointer->localStorageEnabled = Settings::localStorageEnabled();
371
372         // Set DOM storage.
373         webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());
374
375         // Set the user agent.
376         webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));
377
378         // Store the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
379         currentZoomFactor = Settings::zoomFactor();
380
381         // Set the zoom factor.
382         webEngineViewPointer->setZoomFactor(Settings::zoomFactor());
383
384         // Apply the no domain settings palette to the URL line edit.
385         emit updateDomainSettingsIndicator(false, QStringLiteral(""));
386     }
387
388     // Emit the update actions signals.
389     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
390     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
391     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
392     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), true);
393     emit updateZoomFactorAction(webEngineViewPointer->zoomFactor());
394
395     // Reload the website if requested.
396     if (reloadWebsite)
397         webEngineViewPointer->reload();
398 }
399
400 void BrowserView::applyOnTheFlySearchEngine(QAction *searchEngineActionPointer)
401 {
402     // Store the search engine name.
403     QString searchEngineName = searchEngineActionPointer->text();
404
405     // Strip out any `&` characters.
406     searchEngineName.remove('&');
407
408     // Store the search engine string.
409     searchEngineUrl = SearchEngineHelper::getSearchUrl(searchEngineName);
410
411     // Update the search engine actionas.
412     emit updateSearchEngineActions(searchEngineName, false);
413 }
414
415 void BrowserView::applyOnTheFlyUserAgent(QAction *userAgentActionPointer) const
416 {
417     // Get the user agent name.
418     QString userAgentName = userAgentActionPointer->text();
419
420     // Strip out any `&` characters.
421     userAgentName.remove('&');
422
423     // Apply the user agent.
424     webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromTranslatedName(userAgentName));
425
426     // Update the user agent actions.
427     emit updateUserAgentActions(webEngineProfilePointer->httpUserAgent(), false);
428
429     // Reload the website.
430     webEngineViewPointer->reload();
431 }
432
433 // This can be const once <https://redmine.stoutner.com/issues/799> has been resolved.
434 void BrowserView::applyOnTheFlyZoomFactor(const double &zoomFactor)
435 {
436     // Update the current zoom factor.  This can be removed once <https://redmine.stoutner.com/issues/799> has been resolved.
437     currentZoomFactor = zoomFactor;
438
439     // Set the zoom factor.
440     webEngineViewPointer->setZoomFactor(zoomFactor);
441 }
442
443 void BrowserView::back() const
444 {
445     // Go back.
446     webEngineViewPointer->back();
447 }
448
449 void BrowserView::cookieAdded(const QNetworkCookie &cookie) const
450 {
451     // Add the cookie to the cookie list.
452     emit addCookie(cookie);
453 }
454
455 void BrowserView::cookieRemoved(const QNetworkCookie &cookie) const
456 {
457     // Remove the cookie from the cookie list.
458     emit removeCookie(cookie);
459 }
460
461 void BrowserView::deleteAllCookies() const
462 {
463     // Delete all the cookies.
464     webEngineCookieStorePointer->deleteAllCookies();
465 }
466
467 void BrowserView::deleteCookieFromStore(const QNetworkCookie &cookie) const
468 {
469     // Delete the cookie.
470     webEngineCookieStorePointer->deleteCookie(cookie);
471 }
472
473 void BrowserView::forward() const
474 {
475     // Go forward.
476     webEngineViewPointer->forward();
477 }
478
479 void BrowserView::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest) const
480 {
481     // Make it so.
482     emit fullScreenRequested(fullScreenRequest.toggleOn());
483
484     // Accept the request.
485     fullScreenRequest.accept();
486 }
487
488 void BrowserView::home() const
489 {
490     // Load the homepage.
491     webEngineViewPointer->load(QUrl::fromUserInput(Settings::homepage()));
492 }
493
494 void BrowserView::loadFinished() const
495 {
496     // Hide the progress bar.
497     emit hideProgressBar();
498 }
499
500 void BrowserView::loadInitialWebsite()
501 {
502     // Apply the application settings.
503     applyApplicationSettings();
504
505     // Get the arguments.
506     QStringList argumentsStringList = qApp->arguments();
507
508     // Check to see if the arguments lists contains a URL.
509     if (argumentsStringList.size() > 1)
510     {
511         // Load the URL from the arguments list.
512         webEngineViewPointer->load(QUrl::fromUserInput(argumentsStringList.at(1)));
513     }
514     else
515     {
516         // Load the homepage.
517         home();
518     }
519 }
520
521 void BrowserView::loadProgress(const int &progress) const
522 {
523     // Show the progress bar.
524     emit showProgressBar(progress);
525 }
526
527 void BrowserView::loadStarted() const
528 {
529     // Show the progress bar.
530     emit showProgressBar(0);
531 }
532
533 void BrowserView::loadUrlFromLineEdit(QString url) const
534 {
535     // Decide if the text is more likely to be a URL or a search.
536     if (url.startsWith("file://"))  // The text is likely a file URL.
537     {
538         // Load the URL.
539         webEngineViewPointer->load(QUrl::fromUserInput(url));
540     }
541     else if (url.contains("."))  // The text is likely a URL.
542     {
543         // Check if the URL does not start with a valid protocol.
544         if (!url.startsWith("http"))
545         {
546             // Add `https://` to the beginning of the URL.
547             url = "https://" + url;
548         }
549
550         // Load the URL.
551         webEngineViewPointer->load(QUrl::fromUserInput(url));
552     }
553     else  // The text is likely a search.
554     {
555         // Load the search.
556         webEngineViewPointer->load(QUrl::fromUserInput(searchEngineUrl + url));
557     }
558 }
559
560 void BrowserView::mouseBack() const
561 {
562     // Go back if possible.
563     if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoBack())
564     {
565         // Clear the URL line edit focus.
566         emit clearUrlLineEditFocus();
567
568         // Go back.
569         webEngineViewPointer->back();
570     }
571 }
572
573 void BrowserView::mouseForward() const
574 {
575     // Go forward if possible.
576     if (webEngineViewPointer->isActiveWindow() && webEngineHistoryPointer->canGoForward())
577     {
578         // Clear the URL line edit focus.
579         emit clearUrlLineEditFocus();
580
581         // Go forward.
582         webEngineViewPointer->forward();
583     }
584 }
585
586 void BrowserView::pageLinkHovered(const QString &linkUrl) const
587 {
588     // Emit a signal so that the browser window can update the status bar.
589     emit linkHovered(linkUrl);
590 }
591
592 void BrowserView::print() const
593 {
594     // Create a printer.
595     QPrinter printer;
596
597     // Set the resolution to be 300 dpi.
598     printer.setResolution(300);
599
600     // Create a printer dialog.
601     QPrintDialog printDialog(&printer, webEngineViewPointer);
602
603     // Display the dialog and print the page if instructed.
604     if (printDialog.exec() == QDialog::Accepted)
605         printWebpage(&printer);
606 }
607
608 void BrowserView::printPreview() const
609 {
610     // Create a printer.
611     QPrinter printer;
612
613     // Set the resolution to be 300 dpi.
614     printer.setResolution(300);
615
616     // Create a print preview dialog.
617     QPrintPreviewDialog printPreviewDialog(&printer, webEngineViewPointer);
618
619     // Generate the print preview.
620     connect(&printPreviewDialog, SIGNAL(paintRequested(QPrinter *)), this, SLOT(printWebpage(QPrinter *)));
621
622     // Display the dialog.
623     printPreviewDialog.exec();
624 }
625
626 void BrowserView::printWebpage(QPrinter *printerPointer) const
627 {
628     // Create an event loop.  For some reason, the print preview doesn't produce any output unless it is run inside an event loop.
629     QEventLoop eventLoop;
630
631     // Print the webpage, converting the callback above into a `QWebEngineCallback<bool>`.
632     // Printing requires that the printer be a pointer, not a reference, or it will crash with much cursing.
633     webEnginePagePointer->print(printerPointer, [&eventLoop](bool printSuccess)
634     {
635         // Instruct the compiler to ignore the unused parameter.
636         (void) printSuccess;
637
638         // Quit the loop.
639         eventLoop.quit();
640     });
641
642     // Execute the loop.
643     eventLoop.exec();
644 }
645
646 void BrowserView::refresh() const
647 {
648     // Reload the website.
649     webEngineViewPointer->reload();
650 }
651
652 void BrowserView::showSaveDialog(QWebEngineDownloadItem *downloadItemPointer) const
653 {
654     // Instantiate the save dialog.
655     SaveDialog *saveDialogPointer = new SaveDialog(downloadItemPointer);
656
657     // Connect the save button.
658     connect(saveDialogPointer, SIGNAL(showSaveFilePickerDialog(QUrl &, QString &)), this, SLOT(showSaveFilePickerDialog(QUrl &, QString &)));
659
660     // Show the dialog.
661     saveDialogPointer->show();
662 }
663
664 void BrowserView::showSaveFilePickerDialog(QUrl &downloadUrl, QString &suggestedFileName)
665 {
666     // Create a save file dialog.
667     QFileDialog *saveFileDialogPointer = new QFileDialog(this, i18nc("Save file dialog caption", "Save File"), QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
668
669     // Tell the dialog to use a save button.
670     saveFileDialogPointer->setAcceptMode(QFileDialog::AcceptSave);
671
672     // Populate the file name from the download item pointer.
673     saveFileDialogPointer->selectFile(suggestedFileName);
674
675     // Prevent interaction with the parent windows while the dialog is open.
676     saveFileDialogPointer->setWindowModality(Qt::WindowModal);
677
678     // Process the saving of the file.  The save file dialog pointer must be captured directly instead of by reference or nasty crashes occur.
679     auto saveFile = [saveFileDialogPointer, &downloadUrl] () {
680         // Get the save location.  The dialog box should only allow the selecting of one file location.
681         QUrl saveLocation = saveFileDialogPointer->selectedUrls().value(0);
682
683         // Create a file copy job.  `-1` creates the file with default permissions.
684         KIO::FileCopyJob *fileCopyJobPointer = KIO::file_copy(downloadUrl, saveLocation, -1, KIO::Overwrite);
685
686         // Set the download job to display any error messages.
687         fileCopyJobPointer->uiDelegate()->setAutoErrorHandlingEnabled(true);
688
689         // Start the download.
690         fileCopyJobPointer->start();
691     };
692
693     // Handle clicks on the save button.
694     connect(saveFileDialogPointer, &QDialog::accepted, this, saveFile);
695
696     // Show the dialog.
697     saveFileDialogPointer->show();
698 }
699
700 void BrowserView::toggleDomStorage() const
701 {
702     // Toggle DOM storage.
703     webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
704
705     // Update the DOM storage action.
706     emit updateDomStorageAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::LocalStorageEnabled));
707
708     // Reload the website.
709     webEngineViewPointer->reload();
710 }
711
712 void BrowserView::toggleJavaScript() const
713 {
714     // Toggle JavaScript.
715     webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, !webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
716
717     // Update the JavaScript action.
718     emit updateJavaScriptAction(webEngineSettingsPointer->testAttribute(QWebEngineSettings::JavascriptEnabled));
719
720     // Reload the website.
721     webEngineViewPointer->reload();
722 }
723
724 void BrowserView::toggleLocalStorage()
725 {
726     // Toggle local storeage.
727     currentPrivacyWebEnginePointer->localStorageEnabled = !currentPrivacyWebEnginePointer->localStorageEnabled;
728
729     // Update the local storage action.
730     emit updateLocalStorageAction(currentPrivacyWebEnginePointer->localStorageEnabled);
731
732     // Reload the website.
733     webEngineViewPointer->reload();
734 }
735
736 void BrowserView::updateUrl(const QUrl &url) const
737 {
738     // Update the URL line edit.
739     emit updateUrlLineEdit(url);
740
741     // Update the status of the forward and back buttons.
742     emit updateBackAction(webEngineHistoryPointer->canGoBack());
743     emit updateForwardAction(webEngineHistoryPointer->canGoForward());
744
745     // Reapply the zoom factor.  This is a bug in QWebEngineView that resets the zoom with every load.  <https://redmine.stoutner.com/issues/799>
746     webEngineViewPointer->setZoomFactor(currentZoomFactor);
747 }