Qt 5.13 | Qt WebEngine | WebEngine Widgets WebUI Example

Qt 5.13.2 Reference Documentation

# WebEngine Widgets WebUI Example

Displays HTML over a custom scheme.

WebUI demonstrates how to implement a custom scheme in a secure way.

Aside from the built-in URL schemes, such as `http` and `qrc`, Qt WebEngine may be extended with custom schemes by creating custom scheme handlers. This example shows:

- How to create a custom scheme handler which serves HTML and handles HTML form submissions.
- How to prevent ordinary web content from accessing the custom scheme.
- How to prevent any other scheme from submitting HTML form data.

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

#### Overview

The example program consists of a single QWebEngineView showing a simple HTML page loaded from the URL `webui:about`, over our custom scheme. Pressing the button at the bottom of the page will trigger an HTML form submission via POST to the same URL, at which point our custom scheme handler will cause the application to exit.

The program is divided into two parts, the `main` function for setting everything up, and the `WebUiHandler` class for implementing our custom scheme handler. The `main` function is quite short:

      int main(int argc, char *argv[])
      {
          QCoreApplication::setOrganizationName("QtExamples");
          QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

          WebUiHandler::registerUrlScheme();

          QApplication app(argc, argv);

          QWebEngineProfile profile;

          WebUiHandler handler;
          profile.installUrlSchemeHandler(WebUiHandler::schemeName, &handler);

          QWebEnginePage page(&profile);
          page.load(WebUiHandler::aboutUrl);

          QWebEngineView view;
          view.setPage(&page);
          view.setContextMenuPolicy(Qt::NoContextMenu);
          view.resize(500, 600);
          view.show();

          return app.exec();
      }

Aside from the relatively standard setup of widgets, two points are noteworthy. First, we call the static method `WebUiHandler::registerUrlScheme()` to register our custom scheme with the web engine. Second, we create and install our custom scheme handler `WebUiHandler` using installUrlSchemeHandler(). The following sections describe these aspects in more detail.

#### Registering the Scheme

As custom schemes are integrated directly into the web engine, they do not necessarily need to follow the standard security rules which apply to ordinary web content. Depending on the chosen configuration, content served over a custom scheme may be given access to local resources, be set to ignore Content-Security-Policy rules, or conversely, be denied access to any other content entirely.

In order to take advantage of these possibilities, the custom scheme must first be registered. This means creating and configuring a QWebEngineUrlScheme object and then handing it over to QWebEngineUrlScheme::registerScheme(). The example program does exactly this in the static method `WebUiHandler::registerUrlScheme()`:

      void WebUiHandler::registerUrlScheme()
      {
          QWebEngineUrlScheme webUiScheme(schemeName);
          webUiScheme.setFlags(QWebEngineUrlScheme::SecureScheme |
                               QWebEngineUrlScheme::LocalScheme |
                               QWebEngineUrlScheme::LocalAccessAllowed);
          QWebEngineUrlScheme::registerScheme(webUiScheme);
      }

A custom scheme needs a name, which can be set by passing it to the constructor of `QWebEngineUrlScheme` or by calling QWebEngineUrlScheme::setName. In the above, the name `webui` is set through the constructor. Additionally, we activate the flags SecureScheme, LocalScheme and LocalAccessAllowed. Since our custom scheme handler will not deliver resources received from insecure network connections, we can safely mark it as a `SecureScheme`. The `LocalScheme` flag prevents content from non-local schemes (such as `http`) from interacting with our custom scheme. Without this flag it would be possible, for example, to embed the `webui:about` page in an `<iframe>` element on a remotely loaded HTML page, perhaps to attempt a phishing attack. We also need the `LocalAccessAllowed` flag without which we would not be able to access the `webui` scheme from our `webui:about` page.

Earlier we saw that the call to `WebUiHandler::registerUrlScheme()` is made already at the top of the `main` function. This is so because custom schemes need to be registered as early as possible so that that they can be passed to all subprocesses. Specifically, custom schemes need to be registered before any other Qt WebEngine classes are instantiated by the application.

#### Handling Requests

A custom scheme handler is, broadly speaking, similar to a web application served over HTTP. However, because custom schemes are integrated directly into the web engine, they have the advantage in terms of efficiency: there's no need for generating and parsing HTTP messages or for transferring data over sockets.

Implementing a handler means creating a subclass of QWebEngineUrlSchemeHandler, which is just what is done by the `WebUiHandler` class of the example program:

      class WebUiHandler : public QWebEngineUrlSchemeHandler
      {
          Q_OBJECT
      public:
          explicit WebUiHandler(QObject *parent = nullptr);

          void requestStarted(QWebEngineUrlRequestJob *job) override;

          static void registerUrlScheme();

          const static QByteArray schemeName;
          const static QUrl aboutUrl;
      };

For each request to a `webui` URL, the `WebUiHandler::requestStarted()` method will be called:

      void WebUiHandler::requestStarted(QWebEngineUrlRequestJob *job)
      {
          static const QUrl webUiOrigin(QStringLiteral(SCHEMENAME ":"));
          static const QByteArray GET(QByteArrayLiteral("GET"));
          static const QByteArray POST(QByteArrayLiteral("POST"));

          QByteArray method = job->requestMethod();
          QUrl url = job->requestUrl();
          QUrl initiator = job->initiator();

          if (method == GET && url == aboutUrl) {
              QFile *file = new QFile(QStringLiteral(":/about.html"), job);
              file->open(QIODevice::ReadOnly);
              job->reply(QByteArrayLiteral("text/html"), file);
          } else if (method == POST && url == aboutUrl && initiator == webUiOrigin) {
              job->fail(QWebEngineUrlRequestJob::RequestAborted);
              QApplication::exit();
          } else {
              job->fail(QWebEngineUrlRequestJob::UrlNotFound);
          }
      }

The QWebEngineUrlRequestJob object `job` contains the request's attributes and provides methods for replying to the request with a response. Responses are generated asynchronously by reading them from the QIODevice that the application passes to reply().

Warning: The `requestStarted()` method is not called from the main thread, but from the web engine's IO thread. Care must be taken to synchronize access to any resources on the main thread.

Aside from the usual fare of requestMethod and requestUrl, there is also the initiator, holding the origin of the content which initiated the request. An empty `initiator` means the request was initiated directly by the application (via QWebEnginePage::setUrl(), for example). The special value `"null"` corresponds to an opaque origin (a sandboxed `<iframe>` element, for example). Otherwise, the `initiator` will contain the URL scheme, hostname, and port of the content which initiated the request.

In this example, the `initiator` is used to ensure that `POST` requests to `webui:about` will only trigger the application's exit if they originate from the `webui` scheme. This prevents content loaded over other schemes from triggering the application's exit.

Example project @ code.qt.io