- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态 - 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试 - 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证) - 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互 - 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache) - 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy - 更新 epub-bridge.js 与 JS bridge 通信协议 - 全面更新现有 UI 测试以适配新的 helper 和状态管理
125 lines
5.4 KiB
Swift
125 lines
5.4 KiB
Swift
// RDEPUBWebViewDebug.swift
|
||
// WebView 调试日志工具
|
||
// 提供统一的调试日志输出接口,支持导航事件、JS 执行、
|
||
// 消息接收、URL Scheme 任务等场景的日志记录。
|
||
// DEBUG 模式默认开启,Release 模式默认关闭,可通过 UserDefaults 覆盖。
|
||
|
||
import Foundation
|
||
import WebKit
|
||
|
||
/// WebView 调试日志工具集
|
||
enum RDEPUBWebViewDebug {
|
||
/// 调试日志是否启用(DEBUG 默认开启,可通过 UserDefaults "RDEPUBWebViewDebugEnabled" 覆盖)
|
||
static var isEnabled: Bool = {
|
||
if let configured = UserDefaults.standard.object(forKey: "RDEPUBWebViewDebugEnabled") as? Bool {
|
||
return configured
|
||
}
|
||
#if DEBUG
|
||
return true
|
||
#else
|
||
return false
|
||
#endif
|
||
}()
|
||
|
||
/// 详细日志模式是否启用(默认关闭,可通过 UserDefaults "RDEPUBWebViewVerboseEnabled" 开启)
|
||
/// 开启后 logMessage 将输出完整消息体,关闭时仅输出消息名、字段名和文本长度
|
||
static var isVerboseEnabled: Bool = {
|
||
if let configured = UserDefaults.standard.object(forKey: "RDEPUBWebViewVerboseEnabled") as? Bool {
|
||
return configured
|
||
}
|
||
return false
|
||
}()
|
||
|
||
/// 是否允许开启 inspectable。
|
||
/// 默认关闭,仅在阅读器配置显式允许或 UserDefaults 覆盖时开启。
|
||
static var isInspectableEnabled: Bool = {
|
||
if let configured = UserDefaults.standard.object(forKey: "RDEPUBInspectableWebViewsEnabled") as? Bool {
|
||
return configured
|
||
}
|
||
return false
|
||
}()
|
||
|
||
/// 将阅读器配置同步到调试策略,保证默认安全策略由配置显式控制。
|
||
static func applyDebugPolicy(inspectableEnabled: Bool, verboseLoggingEnabled: Bool) {
|
||
isInspectableEnabled = inspectableEnabled
|
||
isVerboseEnabled = verboseLoggingEnabled
|
||
}
|
||
|
||
/// 获取 WebView 的十六进制标识符(用于日志区分多个 WebView 实例)
|
||
static func webViewID(_ webView: WKWebView?) -> String {
|
||
guard let webView else { return "nil-webview" }
|
||
return String(ObjectIdentifier(webView).hashValue, radix: 16)
|
||
}
|
||
|
||
/// 输出通用日志
|
||
static func log(_ scope: String, message: String) {
|
||
guard isEnabled else { return }
|
||
print("[RDReaderWK][\(scope)] \(message)")
|
||
}
|
||
|
||
/// 输出导航事件日志(含 WebView ID、事件类型、URL、错误信息)
|
||
static func logNavigationEvent(_ scope: String, webView: WKWebView?, event: String, url: URL? = nil, error: Error? = nil) {
|
||
guard isEnabled else { return }
|
||
let webViewToken = webViewID(webView)
|
||
let urlText = summarizedURL(url)
|
||
if let error {
|
||
log(scope, message: "webView=\(webViewToken) event=\(event) url=\(urlText) error=\(error.localizedDescription)")
|
||
} else {
|
||
log(scope, message: "webView=\(webViewToken) event=\(event) url=\(urlText)")
|
||
}
|
||
}
|
||
|
||
/// 输出 JavaScript 执行日志
|
||
static func logJavaScript(_ scope: String, webView: WKWebView?, action: String, details: String) {
|
||
guard isEnabled else { return }
|
||
log(scope, message: "webView=\(webViewID(webView)) js=\(action) \(details)")
|
||
}
|
||
|
||
/// 输出 JS 消息接收日志(默认仅输出字段名和文本长度,verbose 模式输出完整消息体)
|
||
static func logMessage(_ scope: String, webView: WKWebView?, name: String, body: Any) {
|
||
guard isEnabled else { return }
|
||
if isVerboseEnabled {
|
||
log(scope, message: "webView=\(webViewID(webView)) message=\(name) body=\(String(describing: body))")
|
||
return
|
||
}
|
||
if let dict = body as? [String: Any] {
|
||
let keys = dict.keys.sorted().joined(separator: ",")
|
||
var meta = "keys=[\(keys)]"
|
||
if let text = dict["text"] as? String {
|
||
meta += " textLen=\(text.count)"
|
||
}
|
||
if let urlString = dict["url"] as? String ?? dict["href"] as? String {
|
||
meta += " url=\(summarizedURL(URL(string: urlString)))"
|
||
}
|
||
log(scope, message: "webView=\(webViewID(webView)) message=\(name) \(meta)")
|
||
} else {
|
||
log(scope, message: "webView=\(webViewID(webView)) message=\(name) type=\(Swift.type(of: body))")
|
||
}
|
||
}
|
||
|
||
/// 输出 URL Scheme 任务日志(含请求 URL、文件 URL、事件类型、错误信息)
|
||
static func logSchemeTask(_ scope: String, requestURL: URL?, fileURL: URL? = nil, event: String, error: Error? = nil) {
|
||
guard isEnabled else { return }
|
||
let requestText = summarizedURL(requestURL)
|
||
let fileText = summarizedURL(fileURL)
|
||
if let error {
|
||
log(scope, message: "scheme=\(event) request=\(requestText) file=\(fileText) error=\(error.localizedDescription)")
|
||
} else {
|
||
log(scope, message: "scheme=\(event) request=\(requestText) file=\(fileText)")
|
||
}
|
||
}
|
||
|
||
/// 截取 URL 的摘要信息(ss-reader:// 协议显示完整 URL,其他显示最后路径组件)
|
||
static func summarizedURL(_ url: URL?) -> String {
|
||
guard let url else { return "nil" }
|
||
if let scheme = url.scheme, scheme == RDEPUBResourceURLSchemeHandler.scheme {
|
||
return url.absoluteString
|
||
}
|
||
let path = url.path
|
||
if path.isEmpty {
|
||
return url.absoluteString
|
||
}
|
||
return url.lastPathComponent.isEmpty ? path : url.lastPathComponent
|
||
}
|
||
}
|