- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态 - 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试 - 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证) - 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互 - 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache) - 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy - 更新 epub-bridge.js 与 JS bridge 通信协议 - 全面更新现有 UI 测试以适配新的 helper 和状态管理
758 lines
22 KiB
Markdown
758 lines
22 KiB
Markdown
# 当前阅读器问题修复开发清单
|
||
|
||
> 最后更新:2026-06-13
|
||
> 依据说明:本清单仅基于当前仓库代码实现整理,不以现有说明文档是否准确为前提。
|
||
|
||
---
|
||
|
||
## 1. 文档目标
|
||
|
||
本文将“当前阅读器还存在的问题”展开为可执行的逐任务开发清单,目标是把工作从“方向建议”落到“具体改哪里、怎么改、如何验收”。
|
||
|
||
本文聚焦以下四类问题:
|
||
|
||
1. 核心交互链路不稳定:选区、高亮、书签、工具栏状态分散。
|
||
2. 默认安全策略偏弱:外链、解压、日志、持久化默认行为需要收口。
|
||
3. 资源与分页稳定性不足:大资源内存峰值、离屏分页 WebView 边界控制不足。
|
||
4. SDK 契约与回归手段不足:默认 no-op 容易误用,自动化观测点不够结构化。
|
||
|
||
---
|
||
|
||
## 2. 总体实施顺序
|
||
|
||
建议按以下顺序执行,减少返工:
|
||
|
||
1. `bookmark-chrome-state-unification`
|
||
2. `selection-state-refactor`
|
||
3. `external-link-policy-hardening`
|
||
4. `epub-archive-path-validation`
|
||
5. `webview-debug-sanitization`
|
||
6. `persistence-defaults-hardening`
|
||
7. `resource-scheme-streaming-and-limits`
|
||
8. `pagination-webview-hardening`
|
||
9. `cache-hardening`
|
||
10. `sdk-contract-and-test-hardening`
|
||
|
||
原因:
|
||
|
||
- 任务 2 是已确认的重复 UI 状态写入问题,收益最高、风险相对低,应优先处理。
|
||
- 任务 1 更偏“状态模型收口优化”,问题真实存在,但严重度低于任务 2。
|
||
- 第 3-6 项收口默认安全行为,改动局部、收益高。
|
||
- 第 7-9 项涉及资源与分页底层,副作用更大,放在前面稳定后更容易验证。
|
||
- 最后一项负责把前面改动固化为稳定契约和测试基线。
|
||
|
||
---
|
||
|
||
## 3. 任务清单
|
||
|
||
## 任务 1:`selection-state-refactor`
|
||
|
||
### 3.1 目标
|
||
|
||
在不破坏当前清晰分层的前提下,收口选区相关状态的冗余表达,降低 view 层与 controller 层各持有一份选区状态所带来的维护成本和时序问题。
|
||
|
||
### 3.2 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBUI/TextPage/RDEPUBTextSelectionController.swift`
|
||
- `Sources/RDReaderView/EPUBUI/TextPage/RDEPUBTextContentView.swift`
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAnnotationCoordinator.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
|
||
|
||
### 3.3 修改方案
|
||
|
||
当前代码并不是“混乱散写”,而是相对清晰的分层流:
|
||
|
||
- gesture
|
||
- view
|
||
- coordinator
|
||
- context/controller
|
||
|
||
其中真正值得优化的点主要有两个:
|
||
|
||
1. view 层和 controller 层各自持有一份 `currentSelection`
|
||
2. `menuSelection` 是为 `UIMenuController` 时序保底引入的 workaround,属于合理存在,但可以尝试被更明确的状态模型替代
|
||
|
||
新增一个统一的选区状态模型仍然是可行方案,例如:
|
||
|
||
```swift
|
||
enum RDEPUBSelectionState: Equatable {
|
||
case idle
|
||
case selecting(anchor: Int)
|
||
case selected(RDEPUBSelection)
|
||
case committingAction(RDEPUBSelection, action: RDEPUBAnnotationMenuAction)
|
||
}
|
||
```
|
||
|
||
建议新增文件:
|
||
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBSelectionState.swift`
|
||
|
||
建议在 `RDEPUBReaderAnnotationCoordinator` 内新增或抽出一个单一入口:
|
||
|
||
- `applySelectionState(_ state: RDEPUBSelectionState)`
|
||
- `currentSelectionState`
|
||
|
||
具体修改:
|
||
|
||
1. `RDEPUBTextSelectionController`
|
||
- 移除“自己就是最终状态源”的职责。
|
||
- 保留字符范围计算与 `RDEPUBSelection?` 构建。
|
||
- `isSelecting` 可保留为手势内部临时状态,但不再作为 UI 的最终依据。
|
||
|
||
2. `RDEPUBTextContentView`
|
||
- 不再直接通过 `currentSelection != nil` 作为所有 UI 行为的唯一判断条件。
|
||
- 长按、拖拽、结束只上报“开始选择 / 更新选择 / 结束选择 / 取消选择”事件。
|
||
- `menuSelection` 不建议直接删除。
|
||
- 第一版应先保留 `menuSelection`,并把它明确标注为菜单时序缓冲状态。
|
||
- 待状态模型稳定后,再评估是否能安全移除。
|
||
|
||
3. `RDEPUBReaderAnnotationCoordinator.updateCurrentSelection(_:)`
|
||
- 改造成 `applySelectionState(_:)`。
|
||
- 在 `.selected` 时统一:
|
||
- 更新 `controller.currentSelection`
|
||
- 决定是否展示工具栏
|
||
- 更新底部高亮按钮可用性
|
||
- 通知 delegate
|
||
- 在 `.idle` 时统一清理:
|
||
- 清空 `controller.currentSelection`
|
||
- 关闭菜单
|
||
- 刷新按钮状态
|
||
|
||
4. `RDEPUBReaderController`
|
||
- `currentSelection` 继续保留为公开只读语义,但底层来源改为 `selectionState` 推导,而不是多处直接赋值。
|
||
|
||
### 3.4 实施步骤
|
||
|
||
1. 新增 `RDEPUBSelectionState.swift`
|
||
2. 在 `RDEPUBReaderAnnotationCoordinator` 中接管选区状态
|
||
3. 重写 `RDEPUBTextContentView` 中选区相关回调
|
||
4. 先收口零散的 `currentSelection != nil` 控制逻辑
|
||
5. 保留 `menuSelection`,待验证不再需要后再移除
|
||
6. 统一 delegate 通知路径
|
||
|
||
### 3.5 风险点
|
||
|
||
- 选区菜单弹出时机可能变化。
|
||
- `menuSelection` 是现有时序补丁,若贸然删除,菜单动作可能拿不到正确 selection。
|
||
- Web 路径和 Native Text 路径都要走统一状态流,避免只修一条。
|
||
|
||
### 3.6 验收标准
|
||
|
||
- 长按后必然进入“选区中”状态。
|
||
- 松手后有有效文本时必然进入“已选中”状态。
|
||
- 复制/高亮/批注后必然回到空闲状态。
|
||
- `selection=1` 的 Demo 状态与按钮启用状态始终一致。
|
||
|
||
### 3.7 优先级修正
|
||
|
||
本任务应视为“结构优化 + 降低后续维护成本”,不是当前代码库里最严重的缺陷。建议优先级下调到与任务 2 同级,排在任务 2 之后实施。
|
||
|
||
---
|
||
|
||
## 任务 2:`bookmark-chrome-state-unification`
|
||
|
||
### 3.8 目标
|
||
|
||
统一顶部书签按钮、底部书签按钮、高亮按钮、添加高亮按钮的可用状态计算,避免不同协调器各自改一部分 UI。
|
||
|
||
### 3.9 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderChromeCoordinator.swift`
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAnnotationCoordinator.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderTopToolView.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderBottomToolView.swift`
|
||
|
||
### 3.10 修改方案
|
||
|
||
新增统一 UI 状态模型:
|
||
|
||
```swift
|
||
struct RDEPUBReaderUIState {
|
||
let canToggleBookmark: Bool
|
||
let hasBookmarkAtCurrentLocation: Bool
|
||
let canShowBookmarks: Bool
|
||
let canAddHighlight: Bool
|
||
let canShowHighlights: Bool
|
||
}
|
||
```
|
||
|
||
建议新增文件:
|
||
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderUIState.swift`
|
||
|
||
具体修改:
|
||
|
||
1. `RDEPUBReaderChromeCoordinator`
|
||
- 新增 `makeUIState()` 和 `applyUIState(_:)`
|
||
- `updateReaderChrome()` 内不再直接散写多个 `setXxxEnabled`
|
||
|
||
2. `RDEPUBReaderAnnotationCoordinator`
|
||
- `updateBookmarkChrome()` 不再直接写 view,改为触发 `context.updateReaderChrome()`
|
||
|
||
3. `RDEPUBReaderTopToolView` / `RDEPUBReaderBottomToolView`
|
||
- 保持 dumb view,只接收状态,不参与规则判断
|
||
|
||
### 3.11 实施步骤
|
||
|
||
1. 新增 `RDEPUBReaderUIState.swift`
|
||
2. 改造 `updateReaderChrome()`
|
||
3. 不要简单删除 `AnnotationCoordinator` 内的按钮状态刷新
|
||
4. 区分两类触发时机:
|
||
- `ChromeCoordinator` 负责整页/整轮 chrome 同步
|
||
- `AnnotationCoordinator` 负责用户操作后的即时反馈触发
|
||
5. 将二者统一收口到同一个状态计算入口,例如 `context.updateReaderChrome()`
|
||
6. 调整书签新增/删除/跳转后的刷新入口为统一 `updateReaderChrome()`
|
||
|
||
### 3.12 风险点
|
||
|
||
- 如果简单删除 `AnnotationCoordinator` 内的调用,可能丢失“用户操作后立即反馈”的刷新时机。
|
||
- 如果某些按钮当前依赖副作用显示,需要顺便梳理显示时机。
|
||
|
||
### 3.13 验收标准
|
||
|
||
- 添加第一个书签后,底部书签列表按钮立即可用。
|
||
- 删除最后一个书签后,底部书签列表按钮立即禁用。
|
||
- 当前位置已加书签时,顶部书签按钮始终是选中态。
|
||
|
||
---
|
||
|
||
## 任务 3:`external-link-policy-hardening`
|
||
|
||
### 3.14 目标
|
||
|
||
把“外链点击后直接打开系统应用”改成受控策略,避免 SDK 默认行为过于激进。
|
||
|
||
### 3.15 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBWebView+JavaScriptBridge.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController+ContentDelegates.swift`
|
||
- `Sources/RDReaderView/EPUBUI/Settings/RDEPUBReaderConfiguration.swift`
|
||
|
||
### 3.16 修改方案
|
||
|
||
在 `RDEPUBReaderConfiguration` 新增:
|
||
|
||
- `allowedExternalURLSchemes: Set<String>`
|
||
- `requiresExternalLinkConfirmation: Bool`
|
||
- 可选:`allowedExternalURLHosts: Set<String>?`
|
||
|
||
默认值建议:
|
||
|
||
- scheme 仅允许 `https`
|
||
- 需要确认框
|
||
|
||
具体修改:
|
||
|
||
1. `RDEPUBWebView+JavaScriptBridge.swift`
|
||
- 继续识别外链,但不再表示“允许直接打开”。
|
||
- 只负责把外链事件上抛。
|
||
|
||
2. `RDEPUBReaderController+ContentDelegates.swift`
|
||
- 新增私有方法:
|
||
- `shouldAllowExternalURL(_:)`
|
||
- `presentExternalLinkConfirmation(for:)`
|
||
- `openExternalURLIfAllowed(_:)`
|
||
- `didActivateExternalLink` 改为:
|
||
- 先 delegate 回调
|
||
- 再走配置校验
|
||
- 再弹确认框
|
||
- 最后调用 `UIApplication.shared.open`
|
||
|
||
3. 可选扩展 `RDEPUBReaderDelegate`
|
||
- 增加可选拦截钩子:
|
||
- `epubReader(_:shouldOpenExternalURL:) -> Bool`
|
||
|
||
### 3.17 实施步骤
|
||
|
||
1. 改配置模型与默认值
|
||
2. 改控制器外链处理逻辑
|
||
3. 增加确认对话框
|
||
4. 增加测试用 mock URL 覆盖 `https`、`mailto`、未知 scheme`
|
||
|
||
### 3.18 风险点
|
||
|
||
- 某些 Demo 书可能依赖 `mailto` 或 `tel`,要明确它们现在默认不再直接打开。
|
||
|
||
### 3.19 验收标准
|
||
|
||
- `https` 外链在确认后打开。
|
||
- 默认配置下 `mailto` 和 `tel` 不直接打开。
|
||
- 未知 scheme 一律拒绝。
|
||
|
||
---
|
||
|
||
## 任务 4:`epub-archive-path-validation`
|
||
|
||
### 3.20 目标
|
||
|
||
为 EPUB 解压路径增加显式安全校验,防止恶意压缩包通过相对路径写出解压根目录。
|
||
|
||
### 3.21 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBParser+Archive.swift`
|
||
|
||
### 3.22 修改方案
|
||
|
||
新增私有方法:
|
||
|
||
- `validatedExtractionDestination(for:extractionRoot:) -> URL?`
|
||
|
||
校验规则:
|
||
|
||
1. 拒绝绝对路径。
|
||
2. 拒绝包含 `..` 的路径段。
|
||
3. `standardizedFileURL.path` 必须位于 `extractionRoot.standardizedFileURL.path` 下。
|
||
|
||
具体修改:
|
||
|
||
1. `extractArchiveIfNeeded(epubURL:)`
|
||
- 在循环内先调用 `validatedExtractionDestination`
|
||
- 无效 entry 可:
|
||
- 直接抛错终止解析,或
|
||
- 记日志并跳过
|
||
- 建议优先抛错,行为更清晰
|
||
|
||
2. 增加自定义错误:
|
||
- 若当前 `RDEPUBParserError` 里没有合适 case,可新增 `invalidArchiveEntryPath(String)`
|
||
|
||
### 3.23 实施步骤
|
||
|
||
1. 增加路径校验方法
|
||
2. 替换原始 `destinationURL` 生成逻辑
|
||
3. 增加针对恶意 entry path 的单元测试
|
||
|
||
### 3.24 风险点
|
||
|
||
- 少数历史 EPUB 可能带奇怪路径分隔符,需要兼容性验证。
|
||
|
||
### 3.25 验收标准
|
||
|
||
- 正常 EPUB 仍可正常解压。
|
||
- 带 `../` 的恶意 entry 会被拒绝。
|
||
|
||
---
|
||
|
||
## 任务 5:`webview-debug-sanitization`
|
||
|
||
### 3.26 目标
|
||
|
||
减少默认调试日志泄露阅读内容的风险,并关闭不必要的 inspectable 默认值。
|
||
|
||
### 3.27 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBWebViewDebug.swift`
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBPaginator.swift`
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBWebView+Configuration.swift`
|
||
|
||
### 3.28 修改方案
|
||
|
||
1. `RDEPUBWebViewDebug`
|
||
- `logMessage` 改为只打印:
|
||
- message name
|
||
- 字段名列表
|
||
- 文本长度
|
||
- URL 摘要
|
||
- 不再打印完整 message body
|
||
|
||
2. 新增更细粒度配置
|
||
- `RDEPUBWebViewDebugEnabled`
|
||
- `RDEPUBVerboseWebViewDebugEnabled`
|
||
|
||
3. `RDEPUBPaginator` 与正常阅读 WebView
|
||
- `isInspectable` 改为受配置控制
|
||
- 默认关闭
|
||
|
||
4. `RDEPUBReaderConfiguration`
|
||
- 增加:
|
||
- `allowsInspectableWebViews`
|
||
- `enablesVerboseWebViewLogging`
|
||
|
||
### 3.29 实施步骤
|
||
|
||
1. 改日志输出格式
|
||
2. 改 inspectable 的默认逻辑
|
||
3. 为 verbose 模式保留调试后门
|
||
|
||
### 3.30 风险点
|
||
|
||
- 调试某些 JS 问题时信息会变少,因此要保留显式 verbose 开关。
|
||
|
||
### 3.31 验收标准
|
||
|
||
- 默认 DEBUG 包不输出完整选中文本。
|
||
- 默认分页 WebView 不可 inspect。
|
||
- 打开 verbose 开关后仍能深度调试。
|
||
|
||
---
|
||
|
||
## 任务 6:`persistence-defaults-hardening`
|
||
|
||
### 3.32 目标
|
||
|
||
降低默认 `UserDefaults` 持久化的误用风险和数据膨胀风险,同时不破坏当前 API 可用性。
|
||
|
||
### 3.33 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderPersistence.swift`
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/RDEPUBReaderAnnotationCoordinator.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderController.swift`
|
||
|
||
### 3.34 修改方案
|
||
|
||
短期目标:
|
||
|
||
1. 明确 `RDEPUBUserDefaultsPersistence` 是轻量实现。
|
||
2. 高亮持久化优先依赖:
|
||
- `location`
|
||
- `rangeInfo`
|
||
- `style`
|
||
- `note`
|
||
3. `text` 字段作为冗余缓存,而不是唯一恢复依据。
|
||
|
||
中期可选目标:
|
||
|
||
新增文件持久化实现:
|
||
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBFilePersistence.swift`
|
||
|
||
### 3.35 具体修改
|
||
|
||
1. `RDEPUBReaderPersistence.swift`
|
||
- 为默认实现增加 DEBUG 警告,提示 bookmarks/settings 默认实现是 no-op
|
||
|
||
2. `RDEPUBUserDefaultsPersistence`
|
||
- 增加大小保护,例如当高亮集合序列化后超阈值时打印告警
|
||
- 可选增加版本字段,便于后续迁移
|
||
|
||
3. `RDEPUBReaderController`
|
||
- 初始化默认 persistence 时,在注释与命名上明确这是默认轻量实现
|
||
|
||
### 3.36 实施步骤
|
||
|
||
1. 增加 warning/assert 机制
|
||
2. 优化高亮持久化字段策略
|
||
3. 视时间增加 `RDEPUBFilePersistence`
|
||
|
||
### 3.37 风险点
|
||
|
||
- 如果外部代码依赖 `text` 永远存在,需要做好兼容。
|
||
|
||
### 3.38 验收标准
|
||
|
||
- 未实现书签持久化时,DEBUG 下能看到明确提示。
|
||
- 高亮即使 `text` 丢失,也可基于范围信息恢复定位。
|
||
|
||
---
|
||
|
||
## 任务 7:`resource-scheme-streaming-and-limits`
|
||
|
||
### 3.39 目标
|
||
|
||
降低 `WKURLSchemeHandler` 对大资源的内存冲击,避免每次都整块 `Data(contentsOf:)` 读入。
|
||
|
||
### 3.40 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBResourceURLSchemeHandler.swift`
|
||
|
||
### 3.41 修改方案
|
||
|
||
分两步做:
|
||
|
||
第一步,先加大小阈值和告警:
|
||
|
||
- 读取文件属性获取大小
|
||
- 超过阈值时走分块发送
|
||
|
||
第二步,再补流式发送:
|
||
|
||
- 使用 `FileHandle` 或 `InputStream`
|
||
- 按固定 chunk size 反复 `didReceive(data)`
|
||
|
||
建议新增私有方法:
|
||
|
||
- `resourceMetadata(for:)`
|
||
- `respondWithStreaming(fileURL:requestURL:taskID:urlSchemeTask:)`
|
||
- `respondWithInMemoryData(...)`
|
||
|
||
### 3.42 实施步骤
|
||
|
||
1. 先加文件大小检测
|
||
2. 小文件保留内存直读
|
||
3. 大文件切换到分块发送
|
||
4. 增加取消任务时的中断判断
|
||
|
||
### 3.43 风险点
|
||
|
||
- `WKURLSchemeHandler` 分块发送要注意 stop 之后不继续回调。
|
||
|
||
### 3.44 验收标准
|
||
|
||
- 大图片/字体加载时内存峰值下降。
|
||
- 取消任务不会继续发送数据。
|
||
|
||
---
|
||
|
||
## 任务 8:`pagination-webview-hardening`
|
||
|
||
### 3.45 目标
|
||
|
||
加强离屏分页 WebView 的边界控制,减少外部资源波动和调试副作用。
|
||
|
||
### 3.46 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBCore/RDEPUBPaginator.swift`
|
||
|
||
### 3.47 修改方案
|
||
|
||
1. 为分页 WebView 增加严格导航限制
|
||
- 仅允许 `file://`
|
||
- 仅允许 `ss-reader://`
|
||
- 拒绝 `http/https`
|
||
|
||
2. 将 `isInspectable` 受配置控制
|
||
|
||
3. 增加测量稳定性日志
|
||
- 记录三轮测量结果
|
||
- 当三轮差异过大时告警
|
||
|
||
建议新增私有方法:
|
||
|
||
- `shouldAllowPaginatorNavigation(url:)`
|
||
- `recordMeasurement(pass:value:)`
|
||
|
||
### 3.48 实施步骤
|
||
|
||
1. 扩展 `WKNavigationDelegate`
|
||
2. 加入导航 allowlist
|
||
3. 记录多轮测量差异
|
||
|
||
### 3.49 风险点
|
||
|
||
- 某些 EPUB 样式若依赖外链资源,分页结果可能与当前行为不同,但这本身就是更安全的策略。
|
||
|
||
### 3.50 验收标准
|
||
|
||
- 分页 WebView 不发起外部导航。
|
||
- 三轮测量波动可观测。
|
||
|
||
---
|
||
|
||
## 任务 9:`cache-hardening`
|
||
|
||
### 3.51 目标
|
||
|
||
提升章节摘要磁盘缓存的健壮性、可观测性和可清理能力。
|
||
|
||
### 3.52 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterSummaryDiskCache.swift`
|
||
|
||
### 3.53 修改方案
|
||
|
||
1. 原子写入
|
||
- 先写 `.tmp`
|
||
- 再 replace 到正式文件
|
||
|
||
2. 读失败分类
|
||
- 文件不存在
|
||
- 读取失败
|
||
- 解码失败
|
||
|
||
3. 精细化清理
|
||
- `removeAll()`
|
||
- `removeAll(forBookID:)`
|
||
- `removeAll(forRenderSignature:)`
|
||
|
||
4. 增加统计接口
|
||
- 缓存文件数
|
||
- 总大小
|
||
|
||
### 3.54 实施步骤
|
||
|
||
1. 改写入策略
|
||
2. 增加读失败日志
|
||
3. 增加清理接口
|
||
4. 补测试覆盖损坏文件、半写文件
|
||
|
||
### 3.55 风险点
|
||
|
||
- 清理策略接口若公开,需要评估是否作为 public API。
|
||
|
||
### 3.56 验收标准
|
||
|
||
- 缓存文件损坏不会影响整本书重新打开。
|
||
- 能按需清理缓存,而不是只能全删。
|
||
|
||
---
|
||
|
||
## 任务 10:`sdk-contract-and-test-hardening`
|
||
|
||
### 3.57 目标
|
||
|
||
把前面所有修复固化为更清晰的 SDK 契约和更稳定的测试观测点。
|
||
|
||
### 3.58 主要问题代码
|
||
|
||
- `Sources/RDReaderView/EPUBUI/RDEPUBReaderPersistence.swift`
|
||
- `Sources/RDReaderView/EPUBUI/RDURLReaderController.swift`
|
||
- `ReadViewDemo/ReadViewDemoUITests/`
|
||
|
||
### 3.59 修改方案
|
||
|
||
1. 强化 persistence 契约
|
||
- 继续保留默认实现,但在 DEBUG 下对 no-op 行为给出明确信号
|
||
|
||
2. Demo 状态能力修正
|
||
- 当前仓库并非完全没有结构化状态。
|
||
- `ReadViewDemo/ReadViewDemoUITests/Helpers/DemoReaderState.swift` 已经提供了状态解析器和 `waitForDemoReaderState(...)` 结构化等待能力。
|
||
- 真正需要推进的不是再造一个 `DemoReaderSnapshot`,而是把仍然大量存在的 `waitForReaderState(containing:)` 迁移到结构化断言。
|
||
|
||
建议方向:
|
||
|
||
- 继续保留隐藏 label 输出
|
||
- 保持 `key=value` 格式
|
||
- 统一测试侧只通过 `DemoReaderState` 解析和断言
|
||
- 逐步淘汰 `containing:` 子串匹配,避免 `highlights=1` 命中 `highlights=10` 之类误判
|
||
|
||
3. 补测试
|
||
- 单元测试:
|
||
- `RDEPUBParserArchiveSafetyTests`
|
||
- `RDEPUBExternalLinkPolicyTests`
|
||
- `RDEPUBChapterSummaryDiskCacheTests`
|
||
- UI 测试:
|
||
- 选区状态流
|
||
- 书签启用/禁用
|
||
- 外链确认框
|
||
|
||
### 3.60 实施步骤
|
||
|
||
1. 改 Demo 状态串生成逻辑
|
||
2. 优先将 `waitForReaderState(containing:)` 迁移为 `waitForDemoReaderState(...)`
|
||
3. 调整 UI 测试断言
|
||
4. 增加单元测试 target 或现有测试目录内的新文件
|
||
|
||
### 3.61 风险点
|
||
|
||
- 现有 UI 测试如果强依赖旧状态串,需要同步迁移。
|
||
|
||
### 3.62 验收标准
|
||
|
||
- UI 测试失败时能明确知道是选区状态、书签状态还是导航状态失配。
|
||
- persistence 的默认 no-op 行为在开发期不再“静默”。
|
||
|
||
---
|
||
|
||
## 4. 补充清理项
|
||
|
||
以下问题已在代码中可见,但未纳入前 10 个主任务,建议作为补充任务或穿插清理项处理。
|
||
|
||
### 4.1 `pageBreakBefore/pageBreakAfter` 相关死代码审计
|
||
|
||
涉及代码:
|
||
|
||
- `Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRenderer.swift`
|
||
- `Sources/RDReaderView/EPUBTextRendering/Typesetter/RDEPUBSemanticMarkerInjector.swift`
|
||
- `Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBPageBreakPolicy.swift`
|
||
- `Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift`
|
||
|
||
建议动作:
|
||
|
||
- 审计 `pageBreakBefore/pageBreakAfter` 从注入到消费的完整链路
|
||
- 判断是否有“已定义、已注入、但实际无有效触发”的死路径
|
||
- 若确认无效,删除或补测试锁定其真实行为
|
||
|
||
### 4.2 `PAGINATION-DEBUG` 输出清理
|
||
|
||
涉及代码:
|
||
|
||
- `Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBCoreTextPageFrameFactory.swift`
|
||
- `Sources/RDReaderView/EPUBTextRendering/Pagination/RDEPUBChapterPageCounter.swift`
|
||
|
||
建议动作:
|
||
|
||
- 将裸 `print` 改为受控调试开关
|
||
- 默认关闭
|
||
- 避免分页细节和正文片段直接进入控制台
|
||
|
||
### 4.3 `ChapterRuntime` 旧代码与遗留路径清理
|
||
|
||
建议动作:
|
||
|
||
- 梳理 `ChapterRuntime` 下是否存在已不再被主流程使用的辅助类型或过渡实现
|
||
- 对“仍被引用但语义已经过时”的代码先补注释标记
|
||
- 对“完全无调用”的代码建立删除候选清单
|
||
|
||
---
|
||
|
||
## 5. 推荐里程碑切分
|
||
|
||
### 里程碑 A:核心交互稳定
|
||
|
||
包含任务:
|
||
|
||
- `selection-state-refactor`
|
||
- `bookmark-chrome-state-unification`
|
||
|
||
完成标准:
|
||
|
||
- 选区、高亮、书签相关 UI 测试恢复稳定
|
||
|
||
### 里程碑 B:默认安全行为收口
|
||
|
||
包含任务:
|
||
|
||
- `external-link-policy-hardening`
|
||
- `epub-archive-path-validation`
|
||
- `webview-debug-sanitization`
|
||
- `persistence-defaults-hardening`
|
||
|
||
完成标准:
|
||
|
||
- 默认配置下不再直接打开高风险外链
|
||
- 解压路径具备显式校验
|
||
- 默认调试日志不泄露完整阅读内容
|
||
|
||
### 里程碑 C:底层稳定性优化
|
||
|
||
包含任务:
|
||
|
||
- `resource-scheme-streaming-and-limits`
|
||
- `pagination-webview-hardening`
|
||
- `cache-hardening`
|
||
|
||
完成标准:
|
||
|
||
- 大资源加载和分页更稳定
|
||
- 缓存更健壮、可清理
|
||
|
||
### 里程碑 D:契约与测试固化
|
||
|
||
包含任务:
|
||
|
||
- `sdk-contract-and-test-hardening`
|
||
|
||
完成标准:
|
||
|
||
- SDK 默认行为更清晰
|
||
- 回归测试对关键状态具备稳定观测能力
|
||
|
||
---
|
||
|
||
## 6. 建议的开发节奏
|
||
|
||
若按 2 周一个小迭代,可参考:
|
||
|
||
1. 第 1 周:`bookmark-chrome-state-unification` + `selection-state-refactor`
|
||
2. 第 2 周:`external-link-policy-hardening` + `epub-archive-path-validation` + `webview-debug-sanitization`
|
||
3. 第 3 周:`persistence-defaults-hardening` + `resource-scheme-streaming-and-limits` + `pagination-webview-hardening`
|
||
4. 第 4 周:`cache-hardening` + `sdk-contract-and-test-hardening` 与回归收口
|
||
|
||
如果只想先做最值的部分,建议最小交付集合为:
|
||
|
||
1. `bookmark-chrome-state-unification`
|
||
2. `selection-state-refactor`
|
||
3. `external-link-policy-hardening`
|
||
4. `epub-archive-path-validation`
|
||
|
||
这四项完成后,阅读器的“主观可用性”和“默认安全性”会先提升一个台阶。
|