WebEngine Widgets Simple Browser Example | Qt WebEngine 5.8

Qt 5.8 | Qt WebEngine | Qt WebEngine Widgets Examples | WebEngine Widgets Simple Browser Example

Qt 5.8.0 Reference Documentation

# 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.

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 classes that we are going to implement:

- `Browser` is a singleton 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.

## Creating the Browser Main Window

This example supports multiple main windows that are owned by a `Browser` singleton object. This class could also be used for further functionality, such as downloading files, 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::setAttribute(Qt::AA_EnableHighDpiScaling);

          QApplication app(argc, argv);
          app.setWindowIcon(QIcon(QLatin1String(":simplebrowser.svg")));

          QWebEngineSettings::defaultSettings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);

          BrowserWindow *window = new BrowserWindow();
          Browser::instance().addWindow(window);

          const QString url = getCommandLineUrlArgument();
          if (!url.isEmpty())
              window->loadPage(url);
          else
              window->loadHomePage();

          return app.exec();
      }

## 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 &link);
          void loadProgress(int progress);
          void titleChanged(const QString &title);
          void urlChanged(const QUrl &url);
          void iconChanged(const QIcon &icon);
          void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled);

      public slots:
          // current tab/page slots
          void setUrl(const QUrl &url);
          void triggerWebPageAction(QWebEnginePage::WebAction action);

          ...
      };

Each tab contains an instance of `WebView`:

      WebView *TabWidget::createTab(bool makeCurrent)
      {
          WebView *webView = new WebView;
          WebPage *webPage = new WebPage(QWebEngineProfile::defaultProfile(), webView);
          webView->setPage(webPage);
          setupView(webView);
          addTab(webView, tr("(Untitled)"));
          if (makeCurrent)
              setCurrentWidget(webView);
          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->page();

          connect(webView, &QWebEngineView::titleChanged, [this, webView](const QString &title) {
              int index = indexOf(webView);
              if (index != -1)
                  setTabText(index, title);
              if (currentIndex() == index)
                  emit titleChanged(title);
          });
          connect(webView, &QWebEngineView::urlChanged, [this, webView](const QUrl &url) {
              int index = indexOf(webView);
              if (index != -1)
                  tabBar()->setTabData(index, url);
              if (currentIndex() == index)
                  emit urlChanged(url);
          });
          connect(webView, &QWebEngineView::loadProgress, [this, webView](int progress) {
              if (currentIndex() == indexOf(webView))
                  emit loadProgress(progress);
          });
          ...
      }

## 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(0)
      {
          ...
          connect(this, &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)
      {
          switch (type) {
          case QWebEnginePage::WebBrowserTab: {
              BrowserWindow *mainWindow = qobject_cast<BrowserWindow*>(window());
              return mainWindow->tabWidget()->createTab();
          }
          case QWebEnginePage::WebBrowserBackgroundTab: {
              BrowserWindow *mainWindow = qobject_cast<BrowserWindow*>(window());
              return mainWindow->tabWidget()->createTab(false);
          }
          case QWebEnginePage::WebBrowserWindow: {
              BrowserWindow *mainWindow = new BrowserWindow();
              Browser::instance().addWindow(mainWindow);
              return mainWindow->currentTab();
          }
          case QWebEnginePage::WebDialog: {
              WebPopupWindow *popup = new WebPopupWindow(page()->profile());
              return popup->view();
          }
          }
          return nullptr;
      }

In case of `QWebEnginePage::WebDialog`, we create an instance of a custom `WebPopupWindow` class:

      class WebPopupWindow : public QWidget
      {
          Q_OBJECT

      public:
          WebPopupWindow(QWebEngineProfile *profile);
          QWebEngineView *view() const;
          void setUrl(const QUrl &url);

      private slots:
          void handleGeometryChangeRequested(const QRect &newGeometry);
          void handleIconChanged(const QIcon &icon);

      private:
          UrlLineEdit *m_addressBar;
          WebView *m_view;
      };

### Adding Context Menu Items

We add menu items to the context menu, so that users can right-click a link to have it opened in the same tab, a new window, or a new tab. We override QWebEngineView::contextMenuEvent and use QWebEnginePage::createStandardContextMenu to create a default QMenu with a default list of QWebEnginePage::WebAction actions.

The default name for QWebEnginePage::OpenLinkInThisWindow action is Follow. For clarity, we rename it Open Link in This Tab. Also, we add the actions for opening links in a separate window or in a new tab:

      void WebView::contextMenuEvent(QContextMenuEvent *event)
      {
          QMenu *menu = page()->createStandardContextMenu();
          const QList<QAction*> actions = menu->actions();
          auto it = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::OpenLinkInThisWindow));
          if (it != actions.cend()) {
              (*it)->setText(tr("Open Link in This Tab"));
              ++it;
              QAction *before(it == actions.cend() ? nullptr : *it);
              menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewWindow));
              menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewTab));
          }
          connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
          menu->popup(event->globalPos());
      }

## Implementing WebPage Functionality

As mentioned earlier, each `WebView` contains a `WebPage` instance that was created by using QWebEngineProfile::defaultProfile().

We implement `WebPage` as a subclass of QWebEnginePage to enable HTTP, proxy authentication, and ignoring SSL certificate errors when accessing web pages:

      class WebPage : public QWebEnginePage
      {
          Q_OBJECT

      public:
          WebPage(QWebEngineProfile *profile, QObject *parent = nullptr);

      protected:
          bool certificateError(const QWebEngineCertificateError &error) override;

      private slots:
          void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth);
          void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth, const QString &proxyHost);
      };

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 WebPage::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
      {
          QWidget *mainWindow = view()->window();
          QDialog dialog(mainWindow);
          dialog.setModal(true);
          dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);

          Ui::PasswordDialog passwordDialog;
          passwordDialog.setupUi(&dialog);

          passwordDialog.m_iconLabel->setText(QString());
          QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxQuestion, 0, mainWindow));
          passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));

          QString introMessage(tr("Enter username and password for \"%1\" at %2")
                               .arg(auth->realm()).arg(requestUrl.toString().toHtmlEscaped()));
          passwordDialog.m_infoLabel->setText(introMessage);
          passwordDialog.m_infoLabel->setWordWrap(true);

          if (dialog.exec() == QDialog::Accepted) {
              auth->setUser(passwordDialog.m_userNameLineEdit->text());
              auth->setPassword(passwordDialog.m_passwordLineEdit->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.

      bool WebPage::certificateError(const QWebEngineCertificateError &error)
      {
          QWidget *mainWindow = view()->window();
          if (error.isOverridable()) {
              QDialog dialog(mainWindow);
              dialog.setModal(true);
              dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
              Ui::CertificateErrorDialog certificateDialog;
              certificateDialog.setupUi(&dialog);
              certificateDialog.m_iconLabel->setText(QString());
              QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, mainWindow));
              certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32));
              certificateDialog.m_errorLabel->setText(error.errorDescription());
              dialog.setWindowTitle(tr("Certificate Error"));
              return dialog.exec() == QDialog::Accepted;
          }

          QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription());
          return false;
      }

## 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, `QLineEdit::returnPressed` is emitted, which lets `BrowserWindow` load the requested page:

          connect(m_urlLineEdit, &QLineEdit::returnPressed, this, [this]() {
              m_urlLineEdit->setFavIcon(QIcon(QStringLiteral(":defaulticon.png")));
              loadPage(m_urlLineEdit->url());
          });

The `loadPage()` method calls the `setUrl()` method of `TabWidget`:

      void BrowserWindow::loadPage(const QUrl &url)
      {
          if (url.isValid()) {
              m_urlLineEdit->setUrl(url);
              m_tabWidget->setUrl(url);
          }
      }

The call is forwarded to the currently selected tab:

      void TabWidget::setUrl(const QUrl &url)
      {
          if (WebView *view = currentWebView()) {
              view->setUrl(url);
              view->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.

Files:

- webenginewidgets/simplebrowser/browser.cpp
- webenginewidgets/simplebrowser/browser.h
- webenginewidgets/simplebrowser/browserwindow.cpp
- webenginewidgets/simplebrowser/browserwindow.h
- webenginewidgets/simplebrowser/certificateerrordialog.ui
- webenginewidgets/simplebrowser/passworddialog.ui
- webenginewidgets/simplebrowser/tabwidget.cpp
- webenginewidgets/simplebrowser/tabwidget.h
- webenginewidgets/simplebrowser/urllineedit.cpp
- webenginewidgets/simplebrowser/urllineedit.h
- webenginewidgets/simplebrowser/webpage.cpp
- webenginewidgets/simplebrowser/webpage.h
- webenginewidgets/simplebrowser/webpopupwindow.cpp
- webenginewidgets/simplebrowser/webpopupwindow.h
- webenginewidgets/simplebrowser/webview.cpp
- webenginewidgets/simplebrowser/webview.h
- webenginewidgets/simplebrowser/data/simplebrowser.svg
- webenginewidgets/simplebrowser/main.cpp
- webenginewidgets/simplebrowser/simplebrowser.pro
- webenginewidgets/simplebrowser/data/simplebrowser.qrc