perf: re-warm WebKit GPU on Reader tab appear if idle >60s
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) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ final class ArticleRenderer {
|
|||||||
static let shared = ArticleRenderer()
|
static let shared = ArticleRenderer()
|
||||||
|
|
||||||
let webView: WKWebView
|
let webView: WKWebView
|
||||||
|
private var lastWarmTime: CFAbsoluteTime = 0
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
let config = WKWebViewConfiguration()
|
let config = WKWebViewConfiguration()
|
||||||
@@ -38,11 +39,11 @@ final class ArticleRenderer {
|
|||||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" width="100" height="60">
|
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" width="100" height="60">
|
||||||
"""
|
"""
|
||||||
), baseURL: nil)
|
), baseURL: nil)
|
||||||
|
lastWarmTime = CFAbsoluteTimeGetCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attach WKWebView to the app window (invisible) to force GPU process
|
/// Attach WKWebView to the app window (invisible) to force GPU process
|
||||||
/// launch. Must be called after window is available. Keeps the webview
|
/// launch. Must be called after window is available.
|
||||||
/// attached — it will be moved to the article container on first use.
|
|
||||||
func attachToWindow() {
|
func attachToWindow() {
|
||||||
guard webView.window == nil,
|
guard webView.window == nil,
|
||||||
let window = UIApplication.shared.connectedScenes
|
let window = UIApplication.shared.connectedScenes
|
||||||
@@ -51,10 +52,22 @@ final class ArticleRenderer {
|
|||||||
|
|
||||||
webView.alpha = 0
|
webView.alpha = 0
|
||||||
window.addSubview(webView)
|
window.addSubview(webView)
|
||||||
|
}
|
||||||
|
|
||||||
// Keep attached (don't remove) so GPU process stays alive.
|
/// Re-warm if the GPU process may have exited due to idle timeout.
|
||||||
// The webview will be reparented to the article container
|
/// Lightweight: only fires if >60s since last warm, and just reloads
|
||||||
// via removeFromSuperview + addSubview in makeUIView.
|
/// a tiny HTML page + re-attaches to window if needed.
|
||||||
|
func reWarmIfNeeded() {
|
||||||
|
let elapsed = CFAbsoluteTimeGetCurrent() - lastWarmTime
|
||||||
|
guard elapsed > 60 else { return }
|
||||||
|
|
||||||
|
attachToWindow()
|
||||||
|
webView.loadHTMLString(
|
||||||
|
"<html><head><style>body{font-family:-apple-system;}</style></head>" +
|
||||||
|
"<body><p>warm</p></body></html>",
|
||||||
|
baseURL: nil
|
||||||
|
)
|
||||||
|
lastWarmTime = CFAbsoluteTimeGetCurrent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,9 @@ struct ReaderTabView: View {
|
|||||||
FeedManagementSheet(vm: vm)
|
FeedManagementSheet(vm: vm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
ArticleRenderer.shared.reWarmIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var subTabs: [String] { ["Unread", "Starred", "All"] }
|
private var subTabs: [String] { ["Unread", "Starred", "All"] }
|
||||||
|
|||||||
Reference in New Issue
Block a user