WebEngine Widgets Simple Browser Example▲
Simple Browser demonstrates how to use the Qt WebEngine C++ classes to develop a small Web browser application that contains the following elements:
-
Menu bar for opening stored pages and managing windows and tabs.
-
Navigation bar for entering a URL and for moving backward and forward in the web page browsing history.
-
Multi-tab area for displaying web content within tabs.
-
Status bar for displaying hovered links.
-
A simple download manager.
The web content can be opened in new tabs or separate windows. HTTP and proxy authentication can be used for accessing web pages.
Running the Example▲
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
Class Hierarchy▲
We start with sketching a diagram of the main classes that we are going to implement:
-
Browser is a class managing the application windows.
-
BrowserWindow is a QMainWindow showing the menu, a navigation bar, TabWidget, and a status bar.
-
TabWidget is a QTabWidget and contains one or multiple browser tabs.
-
WebView is a QWebEngineView, provides a view for WebPage, and is added as a tab in TabWidget.
-
WebPage is a QWebEnginePage that represents website content.
Additionally, we will implement some auxiliary classes:
Creating the Browser Main Window▲
This example supports multiple main windows that are owned by a Browser object. This class also owns the DownloadManagerWidget and could be used for further functionality, such as bookmarks and history managers.
In main.cpp, we create the first BrowserWindow instance and add it to the Browser object. If no arguments are passed on the command line, we open the Qt Homepage:
int
main(int
argc, char
**
argv)
{
QCoreApplication::
setOrganizationName("QtExamples"
);
QApplication app(argc, argv);
app.setWindowIcon(QIcon(QStringLiteral(":AppLogoColor.png"
)));
QWebEngineProfile::
defaultProfile()-&
gt;settings()-&
gt;setAttribute(QWebEngineSettings::
PluginsEnabled, true
);
QWebEngineProfile::
defaultProfile()-&
gt;settings()-&
gt;setAttribute(QWebEngineSettings::
DnsPrefetchEnabled, true
);
QUrl url =
commandLineUrlArgument();
Browser browser;
BrowserWindow *
window =
browser.createHiddenWindow();
window-&
gt;tabWidget()-&
gt;setUrl(url);
window-&
gt;show();
return
app.exec();
}
To suppress flicker when switching the window to OpenGL rendering, we call show after the first browser tab has been added.
Creating Tabs▲
The BrowserWindow constructor initializes all the necessary user interface related objects. The centralWidget of BrowserWindow contains an instance of TabWidget. The TabWidget contains one or several WebView instances as tabs, and delegates it's signals and slots to the currently selected one:
class
TabWidget : public
QTabWidget
{
...
signals
:
// current tab/page signals
void
linkHovered(const
QString &
amp;link);
void
loadProgress(int
progress);
void
titleChanged(const
QString &
amp;title);
void
urlChanged(const
QUrl &
amp;url);
void
favIconChanged(const
QIcon &
amp;icon);
void
webActionEnabledChanged(QWebEnginePage::
WebAction action, bool
enabled);
void
devToolsRequested(QWebEnginePage *
source);
void
findTextFinished(const
QWebEngineFindTextResult &
amp;result);
public
slots:
// current tab/page slots
void
setUrl(const
QUrl &
amp;url);
void
triggerWebPageAction(QWebEnginePage::
WebAction action);
...
}
;
Each tab contains an instance of WebView:
WebView *
TabWidget::
createTab()
{
WebView *
webView =
createBackgroundTab();
setCurrentWidget(webView);
return
webView;
}
WebView *
TabWidget::
createBackgroundTab()
{
WebView *
webView =
new
WebView;
WebPage *
webPage =
new
WebPage(m_profile, webView);
webView-&
gt;setPage(webPage);
setupView(webView);
int
index =
addTab(webView, tr("(Untitled)"
));
setTabIcon(index, webView-&
gt;favIcon());
// Workaround for QTBUG-61770
webView-&
gt;resize(currentWidget()-&
gt;size());
webView-&
gt;show();
return
webView;
}
In TabWidget::setupView(), we make sure that the TabWidget always forwards the signals of the currently selected WebView:
void
TabWidget::
setupView(WebView *
webView)
{
QWebEnginePage *
webPage =
webView-&
gt;page();
connect(webView, &
amp;QWebEngineView::
titleChanged, [this
, webView](const
QString &
amp;title) {
int
index =
indexOf(webView);
if
(index !=
-
1
) {
setTabText(index, title);
setTabToolTip(index, title);
}
if
(currentIndex() ==
index)
emit titleChanged(title);
}
);
connect(webView, &
amp;QWebEngineView::
urlChanged, [this
, webView](const
QUrl &
amp;url) {
int
index =
indexOf(webView);
if
(index !=
-
1
)
tabBar()-&
gt;setTabData(index, url);
if
(currentIndex() ==
index)
emit urlChanged(url);
}
);
connect(webView, &
amp;QWebEngineView::
loadProgress, [this
, webView](int
progress) {
if
(currentIndex() ==
indexOf(webView))
emit loadProgress(progress);
}
);
connect(webPage, &
amp;QWebEnginePage::
linkHovered, [this
, webView](const
QString &
amp;url) {
if
(currentIndex() ==
indexOf(webView))
emit linkHovered(url);
}
);
connect(webView, &
amp;WebView::
favIconChanged, [this
, webView](const
QIcon &
amp;icon) {
int
index =
indexOf(webView);
if
(index !=
-
1
)
setTabIcon(index, icon);
if
(currentIndex() ==
index)
emit favIconChanged(icon);
}
);
connect(webView, &
amp;WebView::
webActionEnabledChanged, [this
, webView](QWebEnginePage::
WebAction action, bool
enabled) {
if
(currentIndex() ==
indexOf(webView))
emit webActionEnabledChanged(action,enabled);
}
);
connect(webPage, &
amp;QWebEnginePage::
windowCloseRequested, [this
, webView]() {
int
index =
indexOf(webView);
if
(webView-&
gt;page()-&
gt;inspectedPage())
window()-&
gt;close();
else
if
(index &
gt;=
0
)
closeTab(index);
}
);
connect(webView, &
amp;WebView::
devToolsRequested, this
, &
amp;TabWidget::
devToolsRequested);
connect(webPage, &
amp;QWebEnginePage::
findTextFinished, [this
, webView](const
QWebEngineFindTextResult &
amp;result) {
if
(currentIndex() ==
indexOf(webView))
emit findTextFinished(result);
}
);
}
Implementing WebView Functionality▲
The WebView is derived from QWebEngineView to support the following functionality:
-
Displaying error messages in case renderProcess dies
-
Handling createWindow requests
-
Adding custom menu items to context menus
First, we create the WebView with the necessary methods and signals:
class
WebView : public
QWebEngineView
{
Q_OBJECT
public
:
WebView(QWidget *
parent =
nullptr
);
...
protected
:
void
contextMenuEvent(QContextMenuEvent *
event) override
;
QWebEngineView *
createWindow(QWebEnginePage::
WebWindowType type) override
;
signals
:
void
webActionEnabledChanged(QWebEnginePage::
WebAction webAction, bool
enabled);
...
}
;
Displaying Error Messages▲
If the render process is terminated, we display a QMessageBox with an error code, and then we reload the page:
WebView::
WebView(QWidget *
parent)
:
QWebEngineView(parent)
, m_loadProgress(100
)
{
...
connect(this
, &
amp;QWebEngineView::
renderProcessTerminated,
[this
](QWebEnginePage::
RenderProcessTerminationStatus termStatus, int
statusCode) {
QString status;
switch
(termStatus) {
case
QWebEnginePage::
NormalTerminationStatus:
status =
tr("Render process normal exit"
);
break
;
case
QWebEnginePage::
AbnormalTerminationStatus:
status =
tr("Render process abnormal exit"
);
break
;
case
QWebEnginePage::
CrashedTerminationStatus:
status =
tr("Render process crashed"
);
break
;
case
QWebEnginePage::
KilledTerminationStatus:
status =
tr("Render process killed"
);
break
;
}
QMessageBox::
StandardButton btn =
QMessageBox::
question(window(), status,
tr("Render process exited with code: %1
\n
"
"Do you want to reload the page ?"
).arg(statusCode));
if
(btn ==
QMessageBox::
Yes)
QTimer::
singleShot(0
, [this
] {
reload(); }
);
}
);
}
Managing WebWindows▲
The loaded page might want to create windows of the type QWebEnginePage::WebWindowType, for example, when a JavaScript program requests to open a document in a new window or dialog. This is handled by overriding QWebView::createWindow():
QWebEngineView *
WebView::
createWindow(QWebEnginePage::
WebWindowType type)
{
BrowserWindow *
mainWindow =
qobject_cast&
lt;BrowserWindow*&
gt;(window());
if
(!
mainWindow)
return
nullptr
;
switch
(type) {
case
QWebEnginePage::
WebBrowserTab: {
return
mainWindow-&
gt;tabWidget()-&
gt;createTab();
}
In case of QWebEnginePage::WebDialog, we create an instance of a custom WebPopupWindow class:
class
WebPopupWindow : public
QWidget
{
Q_OBJECT
public
:
WebPopupWindow(QWebEngineProfile *
profile);
WebView *
view() const
;
private
slots:
void
handleGeometryChangeRequested(const
QRect &
amp;newGeometry);
private
:
QLineEdit *
m_urlLineEdit;
QAction *
m_favAction;
WebView *
m_view;
}
;
Adding Context Menu Items▲
The default name for QWebEnginePage::InspectElement action is Inspect. For clarity, we rename it to Open Inspector In New Window when there is no Inspector present yet, and Inspect Element when it's already created.
We also check if the QWebEnginePage::ViewSource action is in the menu, because if it's not we have to add a separator as well.
void
WebView::
contextMenuEvent(QContextMenuEvent *
event)
{
QMenu *
menu =
createStandardContextMenu();
const
QList&
lt;QAction *&
gt; actions =
menu-&
gt;actions();
auto
inspectElement =
std::
find(actions.cbegin(), actions.cend(), page()-&
gt;action(QWebEnginePage::
InspectElement));
if
(inspectElement ==
actions.cend()) {
auto
viewSource =
std::
find(actions.cbegin(), actions.cend(), page()-&
gt;action(QWebEnginePage::
ViewSource));
if
(viewSource ==
actions.cend())
menu-&
gt;addSeparator();
QAction *
action =
new
QAction(menu);
action-&
gt;setText("Open inspector in new window"
);
connect(action, &
amp;QAction::
triggered, [this
]() {
emit devToolsRequested(page()); }
);
QAction *
before(inspectElement ==
actions.cend() ? nullptr
: *
inspectElement);
menu-&
gt;insertAction(before, action);
}
else
{
(*
inspectElement)-&
gt;setText(tr("Inspect element"
));
}
menu-&
gt;popup(event-&
gt;globalPos());
}
Implementing WebPage and WebView Functionality▲
We implement WebPage as a subclass of QWebEnginePage and WebView as as subclass of QWebEngineView to enable HTTP, proxy authentication, as well as ignoring SSL certificate errors when accessing web pages:
class
WebPage : public
QWebEnginePage
{
Q_OBJECT
public
:
WebPage(QWebEngineProfile *
profile, QObject *
parent =
nullptr
);
signals
:
void
createCertificateErrorDialog(QWebEngineCertificateError error);
private
slots:
void
handleCertificateError(QWebEngineCertificateError error);
void
handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection);
}
;
class
WebView : public
QWebEngineView
{
Q_OBJECT
public
:
WebView(QWidget *
parent =
nullptr
);
void
setPage(WebPage *
page);
int
loadProgress() const
;
bool
isWebActionEnabled(QWebEnginePage::
WebAction webAction) const
;
QIcon favIcon() const
;
protected
:
void
contextMenuEvent(QContextMenuEvent *
event) override
;
QWebEngineView *
createWindow(QWebEnginePage::
WebWindowType type) override
;
signals
:
void
webActionEnabledChanged(QWebEnginePage::
WebAction webAction, bool
enabled);
void
favIconChanged(const
QIcon &
amp;icon);
void
devToolsRequested(QWebEnginePage *
source);
private
slots:
void
handleCertificateError(QWebEngineCertificateError error);
void
handleAuthenticationRequired(const
QUrl &
amp;requestUrl, QAuthenticator *
auth);
void
handleFeaturePermissionRequested(const
QUrl &
amp;securityOrigin,
QWebEnginePage::
Feature feature);
void
handleProxyAuthenticationRequired(const
QUrl &
amp;requestUrl, QAuthenticator *
auth,
const
QString &
amp;proxyHost);
void
handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
void
handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request);
#endif
private
:
void
createWebActionTrigger(QWebEnginePage *
page, QWebEnginePage::
WebAction);
private
:
int
m_loadProgress;
}
;
In all the cases above, we display the appropriate dialog to the user. In case of authentication, we need to set the correct credential values on the QAuthenticator object:
void
WebView::
handleAuthenticationRequired(const
QUrl &
amp;requestUrl, QAuthenticator *
auth)
{
QDialog dialog(window());
dialog.setModal(true
);
dialog.setWindowFlags(dialog.windowFlags() &
amp; ~
Qt::
WindowContextHelpButtonHint);
Ui::
PasswordDialog passwordDialog;
passwordDialog.setupUi(&
amp;dialog);
passwordDialog.m_iconLabel-&
gt;setText(QString());
QIcon icon(window()-&
gt;style()-&
gt;standardIcon(QStyle::
SP_MessageBoxQuestion, 0
, window()));
passwordDialog.m_iconLabel-&
gt;setPixmap(icon.pixmap(32
, 32
));
QString introMessage(tr("Enter username and password for
\"
%1
\"
at %2"
)
.arg(auth-&
gt;realm())
.arg(requestUrl.toString().toHtmlEscaped()));
passwordDialog.m_infoLabel-&
gt;setText(introMessage);
passwordDialog.m_infoLabel-&
gt;setWordWrap(true
);
if
(dialog.exec() ==
QDialog::
Accepted) {
auth-&
gt;setUser(passwordDialog.m_userNameLineEdit-&
gt;text());
auth-&
gt;setPassword(passwordDialog.m_passwordLineEdit-&
gt;text());
}
else
{
// Set authenticator null if dialog is cancelled
*
auth =
QAuthenticator();
}
}
The handleProxyAuthenticationRequired signal handler implements the very same steps for the authentication of HTTP proxies.
In case of SSL errors, we just need to return a boolean value indicating whether the certificate should be ignored.
void
WebPage::
handleCertificateError(QWebEngineCertificateError error)
{
error.defer();
QTimer::
singleShot(0
, this
,
[this
, error]() mutable
{
emit createCertificateErrorDialog(error); }
);
}
Opening a Web Page▲
This section describes the workflow for opening a new page. When the user enters a URL in the navigation bar and presses Enter, the QLineEdit::returnPressed signal is emitted and the new URL is then handed over to TabWidget::setUrl:
BrowserWindow::
BrowserWindow(Browser *
browser, QWebEngineProfile *
profile, bool
forDevTools)
{
...
connect(m_urlLineEdit, &
amp;QLineEdit::
returnPressed, [this
]() {
m_tabWidget-&
gt;setUrl(QUrl::
fromUserInput(m_urlLineEdit-&
gt;text()));
}
);
...
}
The call is forwarded to the currently selected tab:
void
TabWidget::
setUrl(const
QUrl &
amp;url)
{
if
(WebView *
view =
currentWebView()) {
view-&
gt;setUrl(url);
view-&
gt;setFocus();
}
}
The setUrl() method of WebView just forwards the url to the associated WebPage, which in turn starts the downloading of the page's content in the background.
Implementing Private Browsing▲
Private browsing, incognito mode, or off-the-record mode is a feature of many browsers where normally persistent data, such as cookies, the HTTP cache, or browsing history, is kept only in memory, leaving no trace on disk. In this example we will implement private browsing on the window level with tabs in one window all in either normal or private mode. Alternatively we could implement private browsing on the tab-level, with some tabs in a window in normal mode, others in private mode.
Implementing private browsing is quite easy using Qt WebEngine. All one has to do is to create a new QWebEngineProfile and use it in the QWebEnginePage instead of the default profile. In the example this new profile is owned by the Browser object:
class
Browser
{
public
:
...
BrowserWindow *
createHiddenWindow(bool
offTheRecord =
false
);
BrowserWindow *
createWindow(bool
offTheRecord =
false
);
private
:
...
QScopedPointer&
lt;QWebEngineProfile&
gt; m_profile;
}
;
Required profile for private browsing is created together with its first window. The default constructor for QWebEngineProfile already puts it in off-the-record mode.
BrowserWindow *
Browser::
createHiddenWindow(bool
offTheRecord)
{
if
(!
offTheRecord &
amp;&
amp; !
m_profile) {
m_profile.reset(new
QWebEngineProfile(
...
All that is left to do is to pass the appropriate profile down to the appropriate QWebEnginePage objects. The Browser object will hand to each new BrowserWindow either the global default profile (see QWebEngineProfile::defaultProfile) or one shared off-the-record profile instance:
...
QObject::
connect(m_profile.get(), &
amp;QWebEngineProfile::
downloadRequested,
&
amp;m_downloadManagerWidget, &
amp;DownloadManagerWidget::
downloadRequested);
}
auto
profile =
!
offTheRecord ? m_profile.get() : QWebEngineProfile::
defaultProfile();
auto
mainWindow =
new
BrowserWindow(this
, profile, false
);
return
mainWindow;
}
The BrowserWindow and TabWidget objects will then ensure that all QWebEnginePage objects contained in a window will use this profile.
Managing Downloads▲
Downloads are associated with a QWebEngineProfile. Whenever a download is triggered on a web page the QWebEngineProfile::downloadRequested signal is emitted with a QWebEngineDownloadRequest, which in this example is forwarded to DownloadManagerWidget::downloadRequested:
Browser::
Browser()
{
// Quit application if the download manager window is the only remaining window
m_downloadManagerWidget.setAttribute(Qt::
WA_QuitOnClose, false
);
QObject::
connect(
QWebEngineProfile::
defaultProfile(), &
amp;QWebEngineProfile::
downloadRequested,
&
amp;m_downloadManagerWidget, &
amp;DownloadManagerWidget::
downloadRequested);
}
This method prompts the user for a file name (with a pre-filled suggestion) and starts the download (unless the user cancels the Save As dialog):
void
DownloadManagerWidget::
downloadRequested(QWebEngineDownloadRequest *
download)
{
Q_ASSERT(download &
amp;&
amp; download-&
gt;state() ==
QWebEngineDownloadRequest::
DownloadRequested);
QString path =
QFileDialog::
getSaveFileName(this
, tr("Save as"
), QDir(download-&
gt;downloadDirectory()).filePath(download-&
gt;downloadFileName())