WebEngine Widgets Permission Browser Example

Demonstrates how to handle website permission requests, and manage existing permissions.

Permission Browser demonstrates how to use the QWebEnginePermission class to manage website permissions. The example includes code for handling incoming permission requests, as well as modifying already existing permissions. Also demonstrated are the effects of the different permission persistence policies defined within the QWebEngineProfile class.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

Class Definitions

MainWindow

The MainWindow class inherits QMainWindow. Inside, we declare a convenience pointer to the QVBoxLayout which will lay out the widgets used to manipulate individual permissions, as well as another convenience pointer to the widget displaying the currently pending permission request. We also declare a QWebEngineView, which will be used to display the actual webpage contents.

class MainWindow : public QMainWindow, public Ui_MainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(const QUrl &url);
    ~MainWindow();

private slots:
    void handlePermissionRequested(QWebEnginePermission permission);
    void handleUrlChanged(const QUrl &url);

    void handlePermissionModified(PermissionWidget *widget);
    void handleDeleteAllClicked();
    void handleNewClicked();
    void handleRefreshClicked();
    void handleBackClicked();
    void handleForwardClicked();
    void handlePolicyComboBoxIndexChanged(int index);

private:
    bool containsPermission(const QWebEnginePermission &permission);
    PermissionWidget *createPermissionWidget(const QWebEnginePermission &permission);
    void loadStoredPermissions();

    QVBoxLayout *m_layout;
    QWebEngineProfile *m_profile;
    QWebEngineView *m_webview;
    PermissionWidget *m_pendingWidget;
};

The rest of the layout for the application is defined inside mainwindow.ui, and was created using Qt Creator Design mode. MainWindow is a child class of Ui_MainWindow, which is a C++ class generated at compile time from the definitions found inside mainwindow.ui.

PermissionWidget and PermissionDialog

The PermissionWidget class defines a widget corresponding to a single QWebEnginePermission instance. For convenience, the QWebEnginePermission object is stored within. The widget itself has controls for granting, denying, or deleting the permission; all of this is defined inside PermissionWidget.ui.

class PermissionWidget : public QWidget, public Ui_PermissionWidget
{
    Q_OBJECT
public:
    PermissionWidget(const QWebEnginePermission &permission, QWidget *parent = nullptr);

    QWebEnginePermission m_permission;

signals:
    void permissionModified(PermissionWidget *widget);

private:
    void updateState();
};

When clicking the "New" button in the main window's UI, a pop-up window will appear, allowing the user to pre-grant permission to a known origin. That pop-up is defined by the PermissionDialog class:

class PermissionDialog : public QDialog, public Ui_PermissionDialog
{
    Q_OBJECT
public:
    PermissionDialog(const QWebEngineProfile *profile, QWidget *parent = nullptr);
    ~PermissionDialog();
    QWebEnginePermission permission();

private:
    const QWebEngineProfile *m_profile;
    QWebEnginePermission *m_permission;
};

Handling incoming permission requests

Whenever a website uses an API that might compromise the user's privacy, the browser is expected to show them a prompt asking to either grant or deny permission. The PermissionBrowser example has a dedicated section at the bottom right, which gets populated with a PermissionWidget whenever that happens.

The PermissionWidget displays the permission's origin, the requested QWebEnginePermission::PermissionType, as well as the current status of that permission. It also has buttons for granting and denying the permission. Since the permission status is (by default) remembered, the delete button allows the user to remove the permission from the underlying storage.

To achieve all this, we first connect QWebEnginePage's permissionRequested signal to MainWindow's handlePermissionRequested slot:

    connect(m_webview->page(), &QWebEnginePage::permissionRequested, this, &MainWindow::handlePermissionRequested);

The signal handler is relatively simple: it attempts to create a PermissionWidget instance for the provided QWebEnginePermission object, and if it succeeds it plugs that widget into the QFrame designated for pending permissions. We also subscribe to PermissionWidget's permissionModified signal so that we can later move the PermissionWidget from the bottom right to the list of existing widgets above.

void MainWindow::handlePermissionRequested(QWebEnginePermission permission)
{
    PermissionWidget *widget = createPermissionWidget(permission);
    if (widget) {
        m_pendingFrame->layout()->addWidget(widget);
        connect(widget, &PermissionWidget::permissionModified, this, &MainWindow::handlePermissionModified);

        if (m_pendingWidget)
            m_pendingWidget->deleteLater();

        m_pendingWidget = widget;
    }
}

We only create a new PermissionWidget if we don't already have an existing one:

PermissionWidget *MainWindow::createPermissionWidget(const QWebEnginePermission &permission)
{
    if (containsPermission(permission))
        return nullptr;

    return new PermissionWidget(permission, this);
}

Modifying a permission and displaying it to the user

The QWebEnginePermission interface provides the grant() and {QWebEnginePermission::deny}{deny}() functions, which are all that's needed to change the status of a permission. If the application needs to forget about a permission, we use the {QWebEnginePermission::reset}{reset}() function.

Inside the PermissionWidget constructor, we hook those function up to the buttons' clicked signal, so that we can execute the relevant functionality on the QWebEnginePermission object.

Whenever a button is pressed, we emit the permissionModified signal, which MainWindow uses to know when it needs to move the widget from the bottom-right to the list of existing permissions. We also make sure to call updateState(), which handles visual updates to the widget. When the delete button is pressed, we also make sure mark the widget for deletion, since we only want to display existing permissions to the user.

PermissionWidget::PermissionWidget(const QWebEnginePermission &permission, QWidget *parent)
    : QWidget(parent)
    , m_permission(permission)
{
    setupUi(this);
    connect(m_deleteButton, &QPushButton::clicked, [this]() {
        m_permission.reset();
        emit permissionModified(this);
        deleteLater();
    });

    connect(m_grantButton, &QPushButton::clicked, [this]() {
        m_permission.grant();
        updateState();
        emit permissionModified(this);
    });

    connect(m_denyButton, &QPushButton::clicked, [this]() {
        m_permission.deny();
        updateState();
        emit permissionModified(this);
    });

    updateState();
}

The updateState() function displays the data supplied by QWebEnginePermission to the user. It also makes sure that, when a permission is in the QWebEnginePermission::Invalid state, the buttons for granting or denying it are disabled.

void PermissionWidget::updateState()
{
    switch (m_permission.state()) {
    case QWebEnginePermission::State::Invalid:
        m_stateLabel->setText("<font color='gray'>Invalid</font>");
        m_grantButton->setEnabled(false);
        m_denyButton->setEnabled(false);
        break;
    case QWebEnginePermission::State::Ask:
        m_stateLabel->setText("<font color='yellow'>Waiting for response</font>");
        break;
    case QWebEnginePermission::State::Granted:
        m_stateLabel->setText("<font color='green'>Granted</font>");
        break;
    case QWebEnginePermission::State::Denied:
        m_stateLabel->setText("<font color='red'>Denied</font>");
        break;
    }

    m_typeLabel->setText(QMetaEnum::fromType<QWebEnginePermission::PermissionType>().valueToKey((quint8)m_permission.permissionType()));
    m_originLabel->setText(m_permission.origin().toDisplayString());
}

When a pending permission is granted or denied, we want to move the associated widget to the list above, which contains all currently existing permissions. We do this in the MainWindow::handlePermissionModified slot.

void MainWindow::handlePermissionModified(PermissionWidget *widget)
{
    if (!m_pendingWidget || m_pendingWidget != widget)
        return;

    m_pendingFrame->layout()->removeWidget(widget);
    m_pendingWidget = nullptr;

    if (!QWebEnginePermission::isPersistent(widget->m_permission.permissionType())
        || widget->m_permission.state() == QWebEnginePermission::State::Ask
        || m_profile->persistentPermissionsPolicy() == QWebEngineProfile::PersistentPermissionsPolicy::AskEveryTime) {

        widget->deleteLater();
        return;
    }

    m_layout->insertWidget(0, widget);
}

Notably, we only do this in cases where we know the permission is remembered; some PermissionTypes are non-persistent, which means they require a permission prompt be shown to the user every time they're used. We also exclude permissions with a QWebEnginePermission::Ask state, which indicates that the permission was reset(), and we don't add anything to the list of existing permissions when QWebEngineProfile::persistentPermissionsPolicy is set to AskEveryTime.

Note: Check the QWebEnginePermission::PermissionType documentation to see which PermissionTypes are persistent.

Displaying and modifying existing permissions

By default, permissions are stored to disk and retrieved again on application startup. To get a list of all existing website permissions, we call QWebEngineProfile::listAllPermissions():

void MainWindow::loadStoredPermissions()
{
    QList<QWebEnginePermission> permissionsList = m_profile->listAllPermissions();
    for (QWebEnginePermission &permission : permissionsList) {
        PermissionWidget *widget = createPermissionWidget(permission);
        if (widget)
            m_layout->insertWidget(0, widget);
    }
}

For every permission in the list, we simply construct a new PermissionWidget, and add it to the list on the right-hand side of the screen. Existing permissions are modified using the exact same API as pending ones.

Pre-granting permissions

Certain permissions may be granted in advance, provided the origin and permission type are known. Clicking on the "New" button in the top right will create a pop-up dialog that allows you to do just that. The dialog is implemented by the PermissionDialog class:

PermissionDialog::PermissionDialog(const QWebEngineProfile *profile, QWidget *parent)
    : QDialog(parent)
    , m_profile(profile)
    , m_permission(nullptr)
{
    setupUi(this);

    auto metaEnum = QMetaEnum::fromType<QWebEnginePermission::PermissionType>();
    Q_ASSERT(metaEnum.value((quint8)QWebEnginePermission::PermissionType::Unsupported) == 0);
    for (int i = 1; i < metaEnum.keyCount(); ++i) {
        QWebEnginePermission::PermissionType permissionType = QWebEnginePermission::PermissionType(metaEnum.value(i));
        if (QWebEnginePermission::isPersistent(permissionType))
            m_permissionTypeComboBox->addItem(metaEnum.valueToKey((quint8)permissionType), QVariant::fromValue(permissionType));
    }
}

We populate the QComboBox using the QMetaEnum type associated with QWebEnginePermission::PermissionType. We make sure to filter out non-persistent permission types, since pre-granting these is not supported.

We display the dialog and add show the resulting PermissionWidget in the UI inside the MainWindow::handleNewClicked slot. The new permission is handled the same way we would if a website requested it: by calling handlePermissionRequested().

void MainWindow::handleNewClicked()
{
    PermissionDialog dialog(m_profile);
    if (dialog.exec() == QDialog::Accepted) {
        handlePermissionRequested(dialog.permission());
    }
}

Changing the permission persistence policy

By default, permissions are stored to disk for every named QWebEngineProfile, and in memory for every unnamed/off-the-record one. Normally, this setting won't be changed at runtime, but this example explores the effects of each option.

  • QWebEngineProfile::StoreOnDisk is the default, and it ensures that any permissions that have been granted in the current application run will be loaded back up at next startup. A permission onlycneeds to be granted once, and subsequent uses of the API that triggered the request will automatically be granted, until the application calls QWebEnginePermission::reset(). li QWebEngineProfile::StoreInMemory Has the same behavior as above, except that permissions will be destroyed at application exit, and not committed to disk. li QWebEngineProfile::AskEveryTime makes sure permissions are never remembered, and all act as if they're non-persistent. Thus, every time a web API needs a permission, a new prompt will be shown to the user. This option is intended for backwards compatibility and applications which implement their own permission storage.

To ensure the user will be shown previously existing permissions, we need to call QWebEngineProfile::listAllPermissions():

void MainWindow::loadStoredPermissions()
{
    QList<QWebEnginePermission> permissionsList = m_profile->listAllPermissions();
    for (QWebEnginePermission &permission : permissionsList) {
        PermissionWidget *widget = createPermissionWidget(permission);
        if (widget)
            m_layout->insertWidget(0, widget);
    }
}

This is done one time at startup, as well as whenever the user changes the policy from the QComboBox from the top right.

void MainWindow::handlePolicyComboBoxIndexChanged(int index)
{
    QWebEngineProfile::PersistentPermissionsPolicy policy;
    switch (index) {
    case 0:
        policy = QWebEngineProfile::PersistentPermissionsPolicy::AskEveryTime;
        break;
    case 1:
        policy = QWebEngineProfile::PersistentPermissionsPolicy::StoreInMemory;
        break;
    case 2:
        policy = QWebEngineProfile::PersistentPermissionsPolicy::StoreOnDisk;
        break;
    }

    if (policy == m_profile->persistentPermissionsPolicy())
        return;

    for (int i = m_layout->count() - 1; i >= 0; i--) {
        PermissionWidget *widget = qobject_cast<PermissionWidget *>(m_layout->itemAt(i)->widget());
        if (!widget)
            continue;

        widget->deleteLater();
    }

    m_profile->setPersistentPermissionsPolicy(policy);
    loadStoredPermissions();
}

Example project @ code.qt.io

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.