Sur cette page

Exemple de navigateur simple basé sur les Widgets de WebEngine

Un navigateur simple basé sur Qt WebEngine Widgets.

Navigateur simple affichant une page web

Simple Browser montre comment utiliser les Widgets de WebEngine. Qt WebEngine C++ classes pour développer une petite application de navigateur Web qui contient les éléments suivants :

  • Barre de menu pour ouvrir les pages stockées et gérer les fenêtres et les onglets.
  • Barre de navigation pour saisir une URL et pour avancer ou reculer dans l'historique de navigation des pages web.
  • Zone multi-onglets pour l'affichage du contenu web dans les onglets.
  • Barre d'état pour afficher les liens survolés.
  • Un gestionnaire de téléchargement simple.

Le contenu web peut être ouvert dans de nouveaux onglets ou dans des fenêtres séparées. L'authentification HTTP et proxy peut être utilisée pour accéder aux pages web.

Exécution de l'exemple

Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutoriel : Construire et exécuter.

Hiérarchie des classes

Nous commençons par esquisser un diagramme des principales classes que nous allons mettre en œuvre :

Diagramme de classes UML montrant la structure d'un navigateur web simple. Il comprend des classes telles que Browser, BrowserWindow, TabWidget, WebView et WebPage, chacune associée à des classes Qt telles que QMainWindow, QTabWidget, QWebEngineView et QWebEnginePage.

  • Browser est une classe qui gère les fenêtres de l'application.
  • BrowserWindow est une classe QMainWindow montrant le menu, une barre de navigation, TabWidget, et une barre d'état.
  • TabWidget est une classe QTabWidget et contient un ou plusieurs onglets de navigateur.
  • WebView est un QWebEngineView, fournit une vue pour WebPage, et est ajouté en tant qu'onglet dans TabWidget.
  • WebPage est un QWebEnginePage qui représente le contenu du site web.

En outre, nous mettrons en œuvre quelques classes auxiliaires :

  • WebPopupWindow est une classe QWidget qui permet d'afficher des fenêtres contextuelles.
  • DownloadManagerWidget est une classe QWidget qui met en œuvre la liste des téléchargements.

Création de la fenêtre principale du navigateur

Cet exemple prend en charge plusieurs fenêtres principales appartenant à un objet Browser. Cette classe possède également l'objet DownloadManagerWidget et pourrait être utilisée pour d'autres fonctionnalités, telles que les gestionnaires de signets et d'historique.

Dans main.cpp, nous créons la première instance de BrowserWindow et l'ajoutons à l'objet Browser. Si aucun argument n'est fourni sur la ligne de commande, nous ouvrons la page d'accueil Qt:

int main(int argc, char **argv)
{
    QCoreApplication::setOrganizationName("QtExamples");

    QApplication app(argc, argv);
    app.setWindowIcon(QIcon(u":AppLogoColor.png"_s));
    QLoggingCategory::setFilterRules(u"qt.webenginecontext.debug=true"_s);

    QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
    QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);
    QWebEngineProfile::defaultProfile()->settings()->setAttribute(
            QWebEngineSettings::ScreenCaptureEnabled, true);

    QUrl url = commandLineUrlArgument();

    Browser browser;
    bool offTheRecord = isSingleProcessMode();
    BrowserWindow *window = browser.createHiddenWindow(offTheRecord);
    window->tabWidget()->setUrl(url);
    window->show();
    return app.exec();
}

Pour supprimer le scintillement lors du passage de la fenêtre au rendu OpenGL, nous appelons show après l'ajout du premier onglet du navigateur.

Création d'onglets

Le constructeur BrowserWindow initialise tous les objets nécessaires à l'interface utilisateur. Le centralWidget de BrowserWindow contient une instance de TabWidget. Le TabWidget contient une ou plusieurs instances de WebView en tant qu'onglets, et délègue ses signaux et ses emplacements à l'onglet actuellement sélectionné :

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 favIconChanged(const QIcon &icon);
    void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled);
    void devToolsRequested(QWebEnginePage *source);
    void findTextFinished(const QWebEngineFindTextResult &result);

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

Chaque onglet contient une instance de 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->setPage(webPage);
    setupView(webView);
    int index = addTab(webView, tr("(Untitled)"));
    setTabIcon(index, webView->favIcon());
    // Workaround for QTBUG-61770
    webView->resize(currentWidget()->size());
    webView->show();
    return webView;
}

Dans TabWidget::setupView(), nous nous assurons que TabWidget transmet toujours les signaux de l'instance WebView actuellement sélectionnée :

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);
            setTabToolTip(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);
    });
    connect(webPage, &QWebEnginePage::linkHovered, [this, webView](const QString &url) {
        if (currentIndex() == indexOf(webView))
            emit linkHovered(url);
    });
    connect(webView, &WebView::favIconChanged, [this, webView](const QIcon &icon) {
        int index = indexOf(webView);
        if (index != -1)
            setTabIcon(index, icon);
        if (currentIndex() == index)
            emit favIconChanged(icon);
    });
    connect(webView, &WebView::webActionEnabledChanged, [this, webView](QWebEnginePage::WebAction action, bool enabled) {
        if (currentIndex() ==  indexOf(webView))
            emit webActionEnabledChanged(action,enabled);
    });
    connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() {
        int index = indexOf(webView);
        if (webView->page()->inspectedPage())
            window()->close();
        else if (index >= 0)
            closeTab(index);
    });
    connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested);
    connect(webPage, &QWebEnginePage::findTextFinished, [this, webView](const QWebEngineFindTextResult &result) {
        if (currentIndex() == indexOf(webView))
            emit findTextFinished(result);
    });
}

Fermeture des onglets

Lorsque l'utilisateur ferme un onglet, nous déclenchons d'abord l'action web RequestClose sur le site WebView correspondant :

    connect(tabBar, &QTabBar::tabCloseRequested, [this](int index) {
        if (WebView *view = webView(index))
            view->page()->triggerAction(QWebEnginePage::WebAction::RequestClose);
    });

Cela permet à tous les récepteurs d'événements JavaScript beforeunload de se déclencher, ce qui peut inviter l'utilisateur à confirmer qu'il souhaite fermer la page dans une boîte de dialogue. Dans ce cas, l'utilisateur peut rejeter la demande de fermeture et laisser l'onglet ouvert, sinon le signal windowCloseRequested est émis et nous fermons l'onglet :

    connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() {
        int index = indexOf(webView);
        if (webView->page()->inspectedPage())
            window()->close();
        else if (index >= 0)
            closeTab(index);
    });

Mise en œuvre de la fonctionnalité WebView

Le site WebView est dérivé du site QWebEngineView pour prendre en charge les fonctionnalités suivantes :

  • Affichage de messages d'erreur en cas de défaillance de renderProcess
  • Traitement des requêtes createWindow
  • Ajout d'éléments de menu personnalisés aux menus contextuels

Tout d'abord, nous créons le site WebView avec les méthodes et les signaux nécessaires :

class WebView : public QWebEngineView
{
    Q_OBJECT

public:
    explicit WebView(QWidget *parent = nullptr);
    ...
protected:
    void contextMenuEvent(QContextMenuEvent *event) override;
    QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override;

signals:
    void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled);
    ...
};
Affichage de messages d'erreur

Si le processus de rendu est interrompu, nous affichons un QMessageBox avec un code d'erreur, puis nous rechargeons la page :

WebView::WebView(QWidget *parent)
    : QWebEngineView(parent)
{
    ...
    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, &WebView::reload);
    });
}
Gestion des fenêtres Web

La page chargée peut vouloir créer des fenêtres du type QWebEnginePage::WebWindowType, par exemple lorsqu'un programme JavaScript demande d'ouvrir un document dans une nouvelle fenêtre ou une nouvelle boîte de dialogue. Pour ce faire, il convient de surcharger QWebView::createWindow():

QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type)
{
    BrowserWindow *mainWindow = qobject_cast<BrowserWindow*>(window());
    if (!mainWindow)
        return nullptr;

    switch (type) {
    case QWebEnginePage::WebBrowserTab: {
        return mainWindow->tabWidget()->createTab();
    }

Dans le cas de QWebEnginePage::WebDialog, nous créons une instance d'une classe WebPopupWindow personnalisée :

class WebPopupWindow : public QWidget
{
    Q_OBJECT

public:
    explicit WebPopupWindow(QWebEngineProfile *profile);
    WebView *view() const;

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

private:
    QLineEdit *m_urlLineEdit;
    QAction *m_favAction;
    WebView *m_view;
};
Ajout d'éléments au menu contextuel

Nous ajoutons un élément de menu au menu contextuel, afin que les utilisateurs puissent cliquer avec le bouton droit de la souris pour ouvrir un inspecteur dans une nouvelle fenêtre. Nous remplaçons QWebEngineView::contextMenuEvent et utilisons QWebEngineView::createStandardContextMenu pour créer un QMenu par défaut avec une liste d'actions QWebEnginePage::WebAction par défaut.

Le nom par défaut de l'action QWebEnginePage::InspectElement est Inspect. Pour plus de clarté, nous la renommons Open Inspector In New Window lorsqu'il n'y a pas encore d'inspecteur, et Inspect Element lorsqu'il est déjà créé.

Nous vérifions également si l'action QWebEnginePage::ViewSource se trouve dans le menu, car si ce n'est pas le cas, nous devons également ajouter un séparateur.

void WebView::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();
    const QList<QAction *> actions = menu->actions();
    auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement));
    if (inspectElement == actions.cend()) {
        auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource));
        if (viewSource == actions.cend())
            menu->addSeparator();

        QAction *action = menu->addAction("Open inspector in new window");
        connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); });
    } else {
        (*inspectElement)->setText(tr("Inspect element"));
    }

    // add conext menu for image policy
    QMenu *editImageAnimation = new QMenu(tr("Image animation policy"));

    m_imageAnimationGroup = new QActionGroup(editImageAnimation);
    m_imageAnimationGroup->setExclusive(true);

    QAction *disableImageAnimation =
            editImageAnimation->addAction(tr("Disable all image animation"));
    disableImageAnimation->setCheckable(true);
    m_imageAnimationGroup->addAction(disableImageAnimation);
    connect(disableImageAnimation, &QAction::triggered, [this]() {
        handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Disallow);
    });
    QAction *allowImageAnimationOnce =
            editImageAnimation->addAction(tr("Allow animated images, but only once"));
    allowImageAnimationOnce->setCheckable(true);
    m_imageAnimationGroup->addAction(allowImageAnimationOnce);
    connect(allowImageAnimationOnce, &QAction::triggered,
            [this]() { handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::AnimateOnce); });
    QAction *allowImageAnimation = editImageAnimation->addAction(tr("Allow all animated images"));
    allowImageAnimation->setCheckable(true);
    m_imageAnimationGroup->addAction(allowImageAnimation);
    connect(allowImageAnimation, &QAction::triggered, [this]() {
        handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy::Allow);
    });

    switch (page()->settings()->imageAnimationPolicy()) {
    case QWebEngineSettings::ImageAnimationPolicy::Allow:
        allowImageAnimation->setChecked(true);
        break;
    case QWebEngineSettings::ImageAnimationPolicy::AnimateOnce:
        allowImageAnimationOnce->setChecked(true);
        break;
    case QWebEngineSettings::ImageAnimationPolicy::Disallow:
        disableImageAnimation->setChecked(true);
        break;
    default:
        allowImageAnimation->setChecked(true);
        break;
    }

    menu->addMenu(editImageAnimation);
    menu->popup(event->globalPos());
}

Mise en œuvre des fonctionnalités WebPage et WebView

Nous implémentons WebPage en tant que sous-classe de QWebEnginePage et WebView en tant que sous-classe de QWebEngineView pour activer HTTP, l'authentification par proxy, ainsi que pour ignorer les erreurs de certificat SSL lors de l'accès aux pages web :

class WebPage : public QWebEnginePage
{
    Q_OBJECT

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

signals:
    void createCertificateErrorDialog(QWebEngineCertificateError error);

private slots:
    void handleCertificateError(QWebEngineCertificateError error);
    void handleSelectClientCertificate(QWebEngineClientCertificateSelection clientCertSelection);
    void handleDesktopMediaRequest(const QWebEngineDesktopMediaRequest &request);
};

class WebView : public QWebEngineView
{
    Q_OBJECT

public:
    explicit WebView(QWidget *parent = nullptr);
    ~WebView();
    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 &icon);
    void devToolsRequested(QWebEnginePage *source);
private slots:
    void handleCertificateError(QWebEngineCertificateError error);
    void handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth);
    void handlePermissionRequested(QWebEnginePermission permission);
    void handleProxyAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth,
                                           const QString &proxyHost);
    void handleRegisterProtocolHandlerRequested(QWebEngineRegisterProtocolHandlerRequest request);
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
    void handleFileSystemAccessRequested(QWebEngineFileSystemAccessRequest request);
    void handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request);
#endif
    void handleImageAnimationPolicyChange(QWebEngineSettings::ImageAnimationPolicy policy);

private:
    void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction);
    void onStateChanged(QWebEngineWebAuthUxRequest::WebAuthUxState state);

private:
    int m_loadProgress = 100;
    WebAuthDialog *m_authDialog = nullptr;
    QActionGroup *m_imageAnimationGroup = nullptr;
};

Dans tous les cas ci-dessus, nous affichons la boîte de dialogue appropriée à l'utilisateur. Dans le cas de l'authentification, nous devons définir les valeurs correctes des informations d'identification sur l'objet QAuthenticator:

void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth)
{
    QDialog dialog(window());
    dialog.setModal(true);
    dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);

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

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

    QString introMessage(tr("Enter username and password for \"%1\" at %2")
                                 .arg(auth->realm(),
                                      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();
    }
}

Le gestionnaire de signal handleProxyAuthenticationRequired met en œuvre les mêmes étapes pour l'authentification des mandataires HTTP.

En cas d'erreurs SSL, nous vérifions si elles proviennent du cadre principal ou d'une ressource à l'intérieur de la page. Les erreurs provenant d'une ressource déclenchent automatiquement le rejet du certificat, car l'utilisateur ne dispose pas d'un contexte suffisant pour prendre une décision. Dans tous les autres cas, nous déclenchons une boîte de dialogue dans laquelle l'utilisateur peut autoriser ou rejeter le certificat.

void WebPage::handleCertificateError(QWebEngineCertificateError error)
{
    // Automatically block certificate errors from page resources without prompting the user.
    // This mirrors the behavior found in other major browsers.
    if (!error.isMainFrame()) {
        error.rejectCertificate();
        return;
    }

    error.defer();
    QTimer::singleShot(0, this,
                       [this, error]() mutable { emit createCertificateErrorDialog(error); });
}

Ouverture d'une page web

Cette section décrit le processus d'ouverture d'une nouvelle page. Lorsque l'utilisateur saisit un URL dans la barre de navigation et appuie sur Enter, le signal QLineEdit::returnPressed est émis et le nouvel URL est alors transmis à TabWidget::setUrl:

BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools)
{
    ...
        connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() {
            m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text()));
        });
    ...
}

L'appel est transmis à l'onglet actuellement sélectionné :

void TabWidget::setUrl(const QUrl &url)
{
    if (WebView *view = currentWebView()) {
        view->setUrl(url);
        view->setFocus();
    }
}

La méthode setUrl() de WebView ne fait que transmettre le signal url au signal WebPage qui, à son tour, lance le téléchargement du contenu de la page en arrière-plan.

Mise en œuvre de la navigation privée

Lanavigation privée, le mode incognito ou le mode "off-the-record" est une fonctionnalité de nombreux navigateurs dans laquelle les données normalement persistantes, telles que les cookies, le cache HTTP ou l'historique de navigation, sont conservées uniquement en mémoire, sans laisser de trace sur le disque. Dans cet exemple, nous mettrons en œuvre la navigation privée au niveau de la fenêtre avec des onglets dans une fenêtre, tous en mode normal ou privé. Nous pourrions également mettre en œuvre la navigation privée au niveau des onglets, certains onglets d'une fenêtre étant en mode normal, d'autres en mode privé.

La mise en œuvre de la navigation privée est très simple à l'aide de Qt WebEngine. Il suffit de créer un nouveau profil QWebEngineProfile et de l'utiliser dans QWebEnginePage à la place du profil par défaut. Dans l'exemple, ce nouveau profil appartient à l'objet Browser:

class Browser
{
public:
    ...
    BrowserWindow *createHiddenWindow(bool offTheRecord = false);
    BrowserWindow *createWindow(bool offTheRecord = false);
private:
    ...
    QScopedPointer<QWebEngineProfile> m_profile;
};

Le profil requis pour la navigation privée est créé avec sa première fenêtre. Le constructeur par défaut de QWebEngineProfile le met déjà en mode "off-the-record".

BrowserWindow *Browser::createHiddenWindow(bool offTheRecord)
{
    if (!offTheRecord && !m_profile) {
        const QString name = u"simplebrowser."_s + QLatin1StringView(qWebEngineChromiumVersion());
        QWebEngineProfileBuilder profileBuilder;
        m_profile.reset(profileBuilder.createProfile(name));
    ...

Il ne reste plus qu'à transmettre le profil approprié aux objets QWebEnginePage appropriés. L'objet Browser transmettra à chaque nouveau BrowserWindow soit le profil global par défaut (voir QWebEngineProfile::defaultProfile), soit une instance de profil hors enregistrement partagée :

    ...
        QObject::connect(m_profile.get(), &QWebEngineProfile::downloadRequested,
                         &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
    }
    auto profile = !offTheRecord ? m_profile.get() : QWebEngineProfile::defaultProfile();
    auto mainWindow = new BrowserWindow(this, profile, false);
    return mainWindow;
}

Les objets BrowserWindow et TabWidget veilleront alors à ce que tous les objets QWebEnginePage contenus dans une fenêtre utilisent ce profil.

Gestion des téléchargements

Les téléchargements sont associés à un QWebEngineProfile. Chaque fois qu'un téléchargement est déclenché sur une page web, le signal QWebEngineProfile::downloadRequested est émis avec un QWebEngineDownloadRequest, qui, dans cet exemple, est transmis à 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(), &QWebEngineProfile::downloadRequested,
        &m_downloadManagerWidget, &DownloadManagerWidget::downloadRequested);
}

Cette méthode demande à l'utilisateur un nom de fichier (avec une suggestion pré-remplie) et lance le téléchargement (à moins que l'utilisateur n'annule la boîte de dialogue Save As ) :

void DownloadManagerWidget::downloadRequested(QWebEngineDownloadRequest *download)
{
    Q_ASSERT(download && download->state() == QWebEngineDownloadRequest::DownloadRequested);

    QString path = QFileDialog::getSaveFileName(this, tr("Save as"), QDir(download->downloadDirectory()).filePath(download->downloadFileName()));
    if (path.isEmpty())
        return;

    download->setDownloadDirectory(QFileInfo(path).path());
    download->setDownloadFileName(QFileInfo(path).fileName());
    download->accept();
    add(new DownloadWidget(download));

    show();
}

L'objet QWebEngineDownloadRequest émet périodiquement le signal receivedBytesChanged pour informer les observateurs potentiels de la progression du téléchargement et le signal stateChanged lorsque le téléchargement est terminé ou qu'une erreur se produit. Voir downloadmanagerwidget.cpp pour un exemple de la manière dont ces signaux peuvent être gérés.

Gestion des demandes UX WebAuth/FIDO

Les demandes UX de WebAuth sont associées à QWebEnginePage. Chaque fois qu'un authentificateur nécessite une interaction avec l'utilisateur, une demande UX est déclenchée sur QWebEnginePage et le signal QWebEnginePage::webAuthUxRequested est émis avec QWebEngineWebAuthUxRequest, qui, dans cet exemple, est transmis à WebView::handleAuthenticatorRequired:

    connect(page, &QWebEnginePage::webAuthUxRequested, this, &WebView::handleWebAuthUxRequested);

Cette méthode crée un dialogue UX WebAuth et lance le flux de demandes UX.

void WebView::handleWebAuthUxRequested(QWebEngineWebAuthUxRequest *request)
{
    if (m_authDialog)
        delete m_authDialog;

    m_authDialog = new WebAuthDialog(request, window());
    m_authDialog->setModal(false);
    m_authDialog->setWindowFlags(m_authDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);

    connect(request, &QWebEngineWebAuthUxRequest::stateChanged, this, &WebView::onStateChanged);
    m_authDialog->show();
}

L'objet QWebEngineWebAuthUxRequest émet périodiquement le signal stateChanged pour informer les observateurs potentiels de l'état actuel de l'interface utilisateur WebAuth. Les observateurs mettent à jour le dialogue WebAuth en conséquence. Voir webview.cpp et webauthdialog.cpp pour un exemple de la façon dont ces signaux peuvent être gérés.

Exigence de signature pour macOS

Pour permettre aux sites web d'accéder à la localisation, à la caméra et au microphone lors de l'exécution de Simple Browser sur macOS, l'application doit être signée. Cela se fait automatiquement lors de la construction, mais vous devez configurer une identité de signature valide pour l'environnement de construction.

Fichiers et attributions

L'exemple utilise des icônes de la bibliothèque d'icônes Tango :

Exemple de projet @ code.qt.io

© 2026 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.