Ejemplo de navegador simple con WebEngine Widgets
Un navegador sencillo basado en Qt WebEngine Widgets.

Simple Browser demuestra cómo utilizar los Qt WebEngine C++ classes para desarrollar una pequeña aplicación de navegador Web que contiene los siguientes elementos:
- Barra de menús para abrir páginas almacenadas y gestionar ventanas y pestañas.
- Barra de navegación para introducir una URL y para retroceder y avanzar en el historial de navegación de páginas web.
- Área multipestaña para mostrar contenido web en pestañas.
- Barra de estado para mostrar los enlaces sobre los que se pasa el ratón.
- Un sencillo gestor de descargas.
El contenido web puede abrirse en nuevas pestañas o ventanas independientes. Se puede utilizar autenticación HTTP y proxy para acceder a las páginas web.
Ejecución del ejemplo
Para ejecutar el ejemplo desde Qt Creator, abra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.
Jerarquía de clases
Comenzamos esbozando un diagrama de las principales clases que vamos a implementar:

Browseres una clase que gestiona las ventanas de la aplicación.BrowserWindowes un QMainWindow que muestra el menú, una barra de navegación,TabWidget, y una barra de estado.TabWidgetes un QTabWidget y contiene una o varias pestañas del navegador.WebViewes un QWebEngineView, proporciona una vista paraWebPage, y se añade como una pestaña enTabWidget.WebPagees un QWebEnginePage que representa el contenido del sitio web.
Además, implementaremos algunas clases auxiliares:
WebPopupWindowes un QWidget para mostrar ventanas emergentes.DownloadManagerWidgetes un QWidget que implementa la lista de descargas.
Creación de la ventana principal del navegador
Este ejemplo soporta múltiples ventanas principales que son propiedad de un objeto Browser. Esta clase también es propietaria de DownloadManagerWidget y podría ser utilizada para otras funcionalidades, como marcadores y gestores de historial.
En main.cpp, creamos la primera instancia de BrowserWindow y la añadimos al objeto Browser. Si no se pasan argumentos en la línea de comandos, abrimos la página de inicio de 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(); }
Para suprimir el parpadeo al cambiar la ventana a renderizado OpenGL, llamamos a mostrar después de que se haya añadido la primera pestaña del navegador.
Creando Pestañas
El constructor BrowserWindow inicializa todos los objetos necesarios relacionados con la interfaz de usuario. El centralWidget de BrowserWindow contiene una instancia de TabWidget. El TabWidget contiene una o varias instancias de WebView como pestañas, y delega sus señales y ranuras a la actualmente seleccionada:
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); ... };
Cada pestaña contiene una instancia 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; }
En TabWidget::setupView(), nos aseguramos de que TabWidget siempre reenvíe las señales de WebView seleccionado en ese momento:
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); }); }
Cierre de pestañas
Cuando el usuario cierra una pestaña, primero activamos la acción web RequestClose en la WebView correspondiente:
connect(tabBar, &QTabBar::tabCloseRequested, [this](int index) { if (WebView *view = webView(index)) view->page()->triggerAction(QWebEnginePage::WebAction::RequestClose); });
Esto permite que se active cualquier escucha de eventos JavaScript beforeunload, que puede solicitar al usuario un diálogo para confirmar que desea cerrar la página. En este caso, el usuario puede rechazar la petición de cierre y dejar la pestaña abierta, de lo contrario se emite la señal windowCloseRequested y cerramos la pestaña:
connect(webPage, &QWebEnginePage::windowCloseRequested, [this, webView]() { int index = indexOf(webView); if (webView->page()->inspectedPage()) window()->close(); else if (index >= 0) closeTab(index); });
Implementación de la funcionalidad WebView
El WebView se deriva de QWebEngineView para soportar la siguiente funcionalidad:
- Mostrar mensajes de error en caso de que
renderProcessmuera - Manejo de peticiones a
createWindow - Añadir elementos de menú personalizados a los menús contextuales
Primero, creamos el WebView con los métodos y señales necesarios:
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); ... };
Visualización de mensajes de error
Si el proceso de renderizado se termina, mostramos un QMessageBox con un código de error, y luego recargamos la página:
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); }); }
Gestión de WebWindows
La página cargada puede querer crear ventanas del tipo QWebEnginePage::WebWindowType, por ejemplo, cuando un programa JavaScript solicita abrir un documento en una nueva ventana o diálogo. Esto se gestiona anulando 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(); }
En el caso de QWebEnginePage::WebDialog, creamos una instancia de una clase personalizada WebPopupWindow:
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; };
Añadir elementos al menú contextual
Añadimos un elemento de menú al menú contextual, para que los usuarios puedan hacer clic con el botón derecho para que se abra un inspector en una nueva ventana. Sobreescribimos QWebEngineView::contextMenuEvent y usamos QWebEngineView::createStandardContextMenu para crear un QMenu por defecto con una lista de acciones QWebEnginePage::WebAction por defecto.
El nombre por defecto de la acción QWebEnginePage::InspectElement es Inspect. Para mayor claridad, la renombramos a Open Inspector In New Window cuando todavía no hay Inspector, y a Inspect Element cuando ya está creada.
También comprobamos si la acción QWebEnginePage::ViewSource está en el menú, porque si no lo está tenemos que añadir un separador también.
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()); }
Implementando la Funcionalidad WebPage y WebView
Implementamos WebPage como subclase de QWebEnginePage y WebView como subclase de QWebEngineView para habilitar HTTP, autenticación proxy, así como ignorar errores de certificado SSL al acceder a páginas 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; };
En todos los casos anteriores, mostramos el diálogo apropiado al usuario. En caso de autenticación, necesitamos establecer los valores de credenciales correctos en el objeto 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(); } }
El manejador de señales handleProxyAuthenticationRequired implementa los mismos pasos para la autenticación de proxies HTTP.
En caso de errores SSL, comprobamos si provienen del marco principal o de un recurso dentro de la página. Los errores de recurso activan automáticamente un rechazo de certificado, ya que el usuario no tendrá suficiente contexto para tomar una decisión. En todos los demás casos, se abre un cuadro de diálogo en el que el usuario puede aceptar o rechazar el certificado.
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); }); }
Apertura de una página Web
Esta sección describe el flujo de trabajo para abrir una nueva página. Cuando el usuario introduce una URL en la barra de navegación y pulsa Enter, se emite la señal QLineEdit::returnPressed y la nueva URL pasa a TabWidget::setUrl:
BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) { ... connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); }); ... }
La llamada se desvía a la pestaña seleccionada en ese momento:
void TabWidget::setUrl(const QUrl &url) { if (WebView *view = currentWebView()) { view->setUrl(url); view->setFocus(); } }
El método setUrl() de WebView sólo reenvía la url al asociado WebPage, que a su vez inicia la descarga del contenido de la página en segundo plano.
Implementación de la navegación privada
Lanavegación privada, modo incógnito o modo fuera de registro es una característica de muchos navegadores en la que los datos normalmente persistentes, como las cookies, la caché HTTP o el historial de navegación, se guardan sólo en memoria, sin dejar rastro en el disco. En este ejemplo implementaremos la navegación privada a nivel de ventana con pestañas en una ventana, todas en modo normal o privado. También podríamos implementar la navegación privada a nivel de pestañas, con algunas pestañas de una ventana en modo normal y otras en modo privado.
Implementar la navegación privada es bastante fácil utilizando Qt WebEngine. Todo lo que hay que hacer es crear un nuevo QWebEngineProfile y utilizarlo en QWebEnginePage en lugar del perfil predeterminado. En el ejemplo, este nuevo perfil pertenece al objeto Browser:
class Browser { public: ... BrowserWindow *createHiddenWindow(bool offTheRecord = false); BrowserWindow *createWindow(bool offTheRecord = false); private: ... QScopedPointer<QWebEngineProfile> m_profile; };
El perfil requerido para la navegación privada se crea junto con su primera ventana. El constructor por defecto de QWebEngineProfile ya lo pone en modo 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)); ...
Sólo queda pasar el perfil adecuado a los objetos QWebEnginePage correspondientes. El objeto Browser entregará a cada nuevo BrowserWindow el perfil global por defecto (ver QWebEngineProfile::defaultProfile) o una instancia de perfil off-the-record compartida:
...
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;
}Los objetos BrowserWindow y TabWidget se asegurarán entonces de que todos los objetos QWebEnginePage contenidos en una ventana utilicen este perfil.
Gestión de descargas
Las descargas se asocian a un QWebEngineProfile. Cada vez que se activa una descarga en una página web, la señal QWebEngineProfile::downloadRequested se emite con un QWebEngineDownloadRequest, que en este ejemplo se reenvía a 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); }
Este método solicita al usuario un nombre de archivo (con una sugerencia previamente rellenada) e inicia la descarga (a menos que el usuario cancele el diálogo 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(); }
El objeto QWebEngineDownloadRequest emitirá periódicamente la señal receivedBytesChanged para notificar a los posibles observadores el progreso de la descarga y la señal stateChanged cuando finalice la descarga o se produzca un error. Consulte downloadmanagerwidget.cpp para ver un ejemplo de cómo se pueden gestionar estas señales.
Gestión de solicitudes WebAuth/FIDO UX
Las solicitudes WebAuth UX están asociadas a QWebEnginePage. Cada vez que un autenticador requiere la interacción del usuario, se activa una solicitud UX en QWebEnginePage y se emite la señal QWebEnginePage::webAuthUxRequested con QWebEngineWebAuthUxRequest, que en este ejemplo se reenvía a WebView::handleAuthenticatorRequired:
connect(page, &QWebEnginePage::webAuthUxRequested, this, &WebView::handleWebAuthUxRequested);
Este método crea un diálogo WebAuth UX e inicia el flujo de solicitud 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(); }
El objeto QWebEngineWebAuthUxRequest emite periódicamente la señal stateChanged para notificar a los observadores potenciales los estados actuales de WebAuth UX. Los observadores actualizan el diálogo WebAuth en consecuencia. Consulte webview.cpp y webauthdialog.cpp para ver un ejemplo de cómo se pueden gestionar estas señales.
Requisito de firma para macOS
Para permitir que los sitios web accedan a la ubicación, la cámara y el micrófono cuando se ejecuta Simple Browser en macOS, es necesario firmar la aplicación. Esto se hace automáticamente durante la compilación, pero es necesario configurar una identidad de firma válida para el entorno de compilación.
Archivos y atribuciones
El ejemplo utiliza iconos de la Tango Icon Library:
| Biblioteca de iconos Tango | Dominio Público |
© 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.