diff --git a/assets/tabler/loader-2.svg b/assets/tabler/loader-2.svg
new file mode 100644
index 00000000..8a878940
--- /dev/null
+++ b/assets/tabler/loader-2.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/app/resources/browser/welcome.html b/src/app/resources/browser/welcome.html
index 9503d8f8..47947c06 100644
--- a/src/app/resources/browser/welcome.html
+++ b/src/app/resources/browser/welcome.html
@@ -3,7 +3,7 @@
- Welcome
+ New tab
diff --git a/src/app/zeal.qrc b/src/app/zeal.qrc
index 29c9ef3d..3a3bd3ea 100644
--- a/src/app/zeal.qrc
+++ b/src/app/zeal.qrc
@@ -16,6 +16,7 @@
../../assets/tabler/dots-vertical.svg
../../assets/tabler/info-circle.svg
../../assets/tabler/library.svg
+ ../../assets/tabler/loader-2.svg
../../assets/tabler/logout.svg
../../assets/tabler/minus.svg
../../assets/tabler/plus.svg
diff --git a/src/libs/browser/webcontrol.cpp b/src/libs/browser/webcontrol.cpp
index 6587baa6..c926e3b4 100644
--- a/src/libs/browser/webcontrol.cpp
+++ b/src/libs/browser/webcontrol.cpp
@@ -11,6 +11,10 @@
#include
#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -19,6 +23,13 @@
namespace Zeal::Browser {
+namespace {
+Q_LOGGING_CATEGORY(log, "zeal.browser.webcontrol")
+
+// Bound recovery attempts so a deterministically crashing page does not thrash forever.
+constexpr int MaxRenderProcessReloadAttempts = 3;
+} // namespace
+
WebControl::WebControl(QWidget *parent)
: QWidget(parent)
{
@@ -40,6 +51,57 @@ WebControl::WebControl(QWidget *parent)
connect(m_webView, &QWebEngineView::urlChanged, this, &WebControl::urlChanged);
connect(m_webView, &WebView::zoomLevelChanged, this, &WebControl::zoomLevelChanged);
+ // On a failed load that set no title, relabel the tab to signal the failure.
+ connect(m_webView, &QWebEngineView::loadFinished, this, [this](bool ok) {
+ if (ok) {
+ m_renderProcessReloadAttempts = 0;
+ return;
+ }
+
+ qCWarning(log, "Failed to load '%s'.", qPrintable(m_webView->url().toString()));
+ if (m_webView->title().isEmpty()) {
+ emit titleChanged(tr("Couldn't load page"));
+ }
+ });
+
+ // A crashed render process leaves a blank page and never updates the title. Reload a
+ // bounded number of times to recover, then relabel the tab if it keeps dying.
+ connect(m_webView,
+ &QWebEngineView::renderProcessTerminated,
+ this,
+ [this](QWebEnginePage::RenderProcessTerminationStatus status, int exitCode) {
+ if (status == QWebEnginePage::NormalTerminationStatus) {
+ return;
+ }
+
+ const char *statusName = QMetaEnum::fromType().valueToKey(
+ status);
+ qCWarning(log,
+ "Render process for '%s' terminated (%s, exit code %d).",
+ qPrintable(m_webView->url().toString()),
+ statusName,
+ exitCode);
+
+ if (m_renderProcessReloadAttempts < MaxRenderProcessReloadAttempts) {
+ ++m_renderProcessReloadAttempts;
+ // Reloading directly from the termination handler is not supported; defer it.
+ // Skip if the user has navigated away in the meantime.
+ const QUrl crashedUrl = m_webView->url();
+ QTimer::singleShot(0, m_webView, [this, crashedUrl]() {
+ if (m_webView->url() == crashedUrl) {
+ m_webView->reload();
+ }
+ });
+ return;
+ }
+
+ qCWarning(log,
+ "Giving up reloading '%s' after %d attempts.",
+ qPrintable(m_webView->url().toString()),
+ MaxRenderProcessReloadAttempts);
+ emit titleChanged(tr("Couldn't load page"));
+ });
+
layout->addWidget(m_webView);
setLayout(layout);
@@ -97,6 +159,7 @@ void WebControl::setWebBridgeObject(const QString &name, QObject *object)
void WebControl::load(const QUrl &url)
{
+ m_renderProcessReloadAttempts = 0;
m_webView->load(url);
}
diff --git a/src/libs/browser/webcontrol.h b/src/libs/browser/webcontrol.h
index a3a7cf34..cd4dc07e 100644
--- a/src/libs/browser/webcontrol.h
+++ b/src/libs/browser/webcontrol.h
@@ -63,6 +63,7 @@ private:
WebView *m_webView = nullptr;
SearchToolBar *m_searchToolBar = nullptr;
+ int m_renderProcessReloadAttempts = 0;
};
} // namespace Zeal::Browser
diff --git a/src/libs/ui/mainwindow.cpp b/src/libs/ui/mainwindow.cpp
index 8f684e32..222c901f 100644
--- a/src/libs/ui/mainwindow.cpp
+++ b/src/libs/ui/mainwindow.cpp
@@ -230,7 +230,9 @@ void MainWindow::addTab(BrowserTab *tab, int index, bool activate)
}
m_webViewStack->insertWidget(index, tab);
- m_tabBar->insertTab(index, tr("Loading…"));
+ // Seed with "New tab" to match browsers and avoid a stranded label.
+ // The placeholder icon reserves the slot so the label does not jump when the favicon loads.
+ m_tabBar->insertTab(index, IconHelper::fromResource(QStringLiteral(":/icons/tabler/loader-2.svg")), tr("New tab"));
m_tabBar->setTabData(index, QVariant::fromValue(tab));
if (activate) {
diff --git a/src/libs/ui/widgets/iconhelper.cpp b/src/libs/ui/widgets/iconhelper.cpp
index 7602ee0d..8bb98af9 100644
--- a/src/libs/ui/widgets/iconhelper.cpp
+++ b/src/libs/ui/widgets/iconhelper.cpp
@@ -125,12 +125,17 @@ bool useThemeIcons()
#endif
}
+QIcon fromResource(const QString &svgResource)
+{
+ return QIcon(new TintedSvgIconEngine(svgResource));
+}
+
QIcon fromTheme(const QString &themeName, const QString &fallbackSvgResource)
{
if (useThemeIcons()) {
- return QIcon::fromTheme(themeName, QIcon(new TintedSvgIconEngine(fallbackSvgResource)));
+ return QIcon::fromTheme(themeName, fromResource(fallbackSvgResource));
}
- return QIcon(new TintedSvgIconEngine(fallbackSvgResource));
+ return fromResource(fallbackSvgResource);
}
} // namespace Zeal::WidgetUi::IconHelper
diff --git a/src/libs/ui/widgets/iconhelper.h b/src/libs/ui/widgets/iconhelper.h
index 5fe473ea..cd43c757 100644
--- a/src/libs/ui/widgets/iconhelper.h
+++ b/src/libs/ui/widgets/iconhelper.h
@@ -19,6 +19,11 @@ bool useThemeIcons();
// color schemes. Use freedesktop icon naming for themeName (e.g. "go-previous").
QIcon fromTheme(const QString &themeName, const QString &fallbackSvgResource);
+// Returns a monochrome Tabler SVG loaded from svgResource and tinted to the active
+// palette, without any desktop icon theme lookup. Use this for glyphs that have no
+// suitable freedesktop equivalent (e.g. the tab loading placeholder).
+QIcon fromResource(const QString &svgResource);
+
} // namespace Zeal::WidgetUi::IconHelper
#endif // ZEAL_WIDGETUI_ICONHELPER_H