From db77a6d34d5528ae89cb2e39d300b6fad2d8ed16 Mon Sep 17 00:00:00 2001 From: Yusuf Suleman Date: Sat, 4 Apr 2026 00:49:26 -0500 Subject: [PATCH] perf: re-warm WebKit GPU on Reader tab appear if idle >60s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPU process can exit due to idle timeout if user waits before opening Reader. reWarmIfNeeded() checks elapsed time since last warm — if >60s, re-attaches WKWebView to window and loads minimal HTML to restart the GPU process. Called on ReaderTabView.onAppear. No timers, no keep-alive loops. Just a timestamp check on tab appear. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Reader/Views/ArticleWebView.swift | 23 +++++++++++++++---- .../Features/Reader/Views/ReaderTabView.swift | 3 +++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ios/Platform/Platform/Features/Reader/Views/ArticleWebView.swift b/ios/Platform/Platform/Features/Reader/Views/ArticleWebView.swift index 79da55b..bdde9cf 100644 --- a/ios/Platform/Platform/Features/Reader/Views/ArticleWebView.swift +++ b/ios/Platform/Platform/Features/Reader/Views/ArticleWebView.swift @@ -8,6 +8,7 @@ final class ArticleRenderer { static let shared = ArticleRenderer() let webView: WKWebView + private var lastWarmTime: CFAbsoluteTime = 0 private init() { let config = WKWebViewConfiguration() @@ -38,11 +39,11 @@ final class ArticleRenderer { """ ), baseURL: nil) + lastWarmTime = CFAbsoluteTimeGetCurrent() } /// Attach WKWebView to the app window (invisible) to force GPU process - /// launch. Must be called after window is available. Keeps the webview - /// attached — it will be moved to the article container on first use. + /// launch. Must be called after window is available. func attachToWindow() { guard webView.window == nil, let window = UIApplication.shared.connectedScenes @@ -51,10 +52,22 @@ final class ArticleRenderer { webView.alpha = 0 window.addSubview(webView) + } - // Keep attached (don't remove) so GPU process stays alive. - // The webview will be reparented to the article container - // via removeFromSuperview + addSubview in makeUIView. + /// Re-warm if the GPU process may have exited due to idle timeout. + /// Lightweight: only fires if >60s since last warm, and just reloads + /// a tiny HTML page + re-attaches to window if needed. + func reWarmIfNeeded() { + let elapsed = CFAbsoluteTimeGetCurrent() - lastWarmTime + guard elapsed > 60 else { return } + + attachToWindow() + webView.loadHTMLString( + "" + + "

warm

", + baseURL: nil + ) + lastWarmTime = CFAbsoluteTimeGetCurrent() } } diff --git a/ios/Platform/Platform/Features/Reader/Views/ReaderTabView.swift b/ios/Platform/Platform/Features/Reader/Views/ReaderTabView.swift index ca0cf40..848e163 100644 --- a/ios/Platform/Platform/Features/Reader/Views/ReaderTabView.swift +++ b/ios/Platform/Platform/Features/Reader/Views/ReaderTabView.swift @@ -139,6 +139,9 @@ struct ReaderTabView: View { FeedManagementSheet(vm: vm) } } + .onAppear { + ArticleRenderer.shared.reWarmIfNeeded() + } } private var subTabs: [String] { ["Unread", "Starred", "All"] }