// 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 } }