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