Skip to content

fix(reader): 修正按索引跳章因 decodeURIComponent 静默失败 (#747) - #752

Open
chy5301 wants to merge 1 commit into
codedogQBY:mainfrom
chy5301:fix/section-index-navigation
Open

fix(reader): 修正按索引跳章因 decodeURIComponent 静默失败 (#747)#752
chy5301 wants to merge 1 commit into
codedogQBY:mainfrom
chy5301:fix/section-index-navigation

Conversation

@chy5301

@chy5301 chy5301 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Closes #747

问题

按索引跳章静默失败:点了没反应,也没有任何报错。受影响的入口:

  • 桌面端 FoliateViewer.goToIndex —— TTS 章节切换、AI 引用在缺少 CFI 时的兜底跳转、目录 page: 前缀跳转
  • 移动端经 WebView 桥接的 window.goToSection —— TTS 上/下一章

根因

view.jsgoTo 无条件解码 target:

async goTo(target) {
    target = decodeURIComponent(target)

decodeURIComponent 会先对参数做 ToString:

decodeURIComponent(5)           // → "5"(string)
decodeURIComponent({index: 5})  // → "[object Object]"

于是 resolveNavigation 的第一个分支 typeof target === "number" 永远为 false,最终落到 book.resolveHref("5") 找不到目标 → 返回 nullrenderer.goTo(null)resolved.index 抛 TypeError → 又被 goTo 自己的 catch 吞掉,只在 console 留下一行 Could not go to 5

改动

只对字符串解码:

if (typeof target === "string") target = decodeURIComponent(target);

href / CFI 路径行为完全不变(它们本来就是字符串,仍然照常解码)。

数字 target 本就是这个库的一等公民,两处佐证:

  1. view.js 自己就在 history.pushState(0) 里存数字
  2. popstate 处理器走 resolveNavigation(detail.state)不经过解码,数字能正常命中 number 分支

也就是说那句无条件解码与库自身的设计相矛盾。修复顺带解决了索引跳转后的前进/后退 —— 此前存进 history 的是解码后的 "5",回退时同样解析不出来。

移动端 window.goToSection 一并简化

它之前会先自行 resolveNavigation(index)、再把结果对象交给 view.goTo

const resolved = typeof view.resolveNavigation === 'function'
  ? await Promise.resolve(view.resolveNavigation(index))
  : { index };
return view.goTo(resolved);

但这个对象同样会被 decodeURIComponent 变成 "[object Object]",所以这个绕过从未生效;外层 try/catch 也永远不会触发,因为 goTo 自己吞掉了异常。现在直接把索引交给已修好的 goTo

桌面端 goToIndex 传的就是裸数字,无需改动。

测试

新增 packages/app-expo/src/screens/reader/section-navigation-contract.test.ts,沿用仓库已有的 contract test 写法:断言解码守卫存在、resolveNavigation 的 number 分支仍在、移动端桥接直接传索引,并附一条可执行的根因说明(decodeURIComponent 对非字符串的 ToString 行为)。

已验证:

  • pnpm --filter @readany/app-expo test 全绿(7 文件 17 测试),并反向验证过——还原改动后新测试会失败
  • pnpm --filter app exec tsc --noEmit 通过
  • biome 对改动文件无新增问题(既有 error 数改动前后一致)

尚未做真机手测。建议 review 时开 devtools 确认:跳章后 console 不再出现 Could not go to <数字>,同时回归确认搜索结果、脚注、书签/高亮等字符串 CFI/href 跳转不受影响。

关于 reader.html 产物 diff

#745 的 PR:产物 diff 中约 94 行不是本次逻辑改动,而是补上 72274af8 遗漏的产物同步(那个 merge commit 更新了 reader.template.htmlpaginator.js,却没有同步 reader.html,导致该页边距修复目前在移动端实际未生效)。在干净的 main 上只跑一次 pnpm run build:reader 即可复现这 94 行。

两个 PR 都带了这份同步,先合并哪个都行,另一个 rebase 后重跑 build:reader 即可。

一处相邻的既有问题(本 PR 未处理)

decodeURIComponent 这行仍在 goTotry 之外,所以它是 goTo 里唯一会 reject 而非记日志的路径:target 字符串含裸 %(例如指向 notes/50%.xhtml 的文档内链接)时会抛 URIError,对不 catch 的调用方(window.goToHref / window.goToCFI、桌面 goToIndex)就是一个 unhandled rejection。

这与 #747 是不同的问题,触发条件也不同,所以没有纳入本 PR 以保持聚焦。如果希望一并处理,把解码连同 resolveNavigation 一起挪进 try 即可,我可以另开一个 PR。

view.goTo 无条件对 target 执行 decodeURIComponent,而该函数会先把参数
ToString:数字 5 变成 "5",对象 { index: 5 } 变成 "[object Object]"。
于是 resolveNavigation 的 typeof target === "number" 分支永远走不到,
最终落到 book.resolveHref 找不到目标,renderer.goTo 读 resolved.index
抛错又被 goTo 自己的 catch 吞掉,只在 console 留下一行 Could not go to。
用户侧表现为「点了没反应,也没有任何报错」。

受影响的是所有按索引跳章的入口:桌面端 FoliateViewer.goToIndex(TTS
章节切换、AI 引用在缺少 CFI 时的兜底、目录 page: 前缀跳转),以及移动端
经 WebView 桥接的 window.goToSection。

改为只对字符串解码,href / CFI 路径行为完全不变。数字 target 本就是这个
库的一等公民——view.js 自己就在 pushState(0) 里存数字,popstate 也是直接
把它交给 resolveNavigation,不经过解码。这一并修好了索引跳转后的前进/
后退:此前存进 history 的是解码后的 "5",回退时同样解析不出来。

移动端 window.goToSection 此前会先自行 resolveNavigation、再把结果对象
交给 goTo,但该对象同样会被解码破坏,所以这个绕过从未生效;它的 try/catch
也永远不会触发,因为 goTo 自己吞掉了异常。现直接把索引交给 goTo。

同步重建 assets/reader/reader.html。
@chy5301

chy5301 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

补充一份桌面端真机验证(Windows / Tauri)。用 Playwright 连 WebView2 的 CDP 驱动真实应用,直接调用 FoliateViewer.goToIndex 内部那一句 view.goTo(index),用 lastLocation.section.current 读取实际落点。

本 PR 分支(已修复),同一时刻三次调用:

调用 section 变化 结果
goTo(10) 数字 0 → 10
goTo("40") 字符串 0 → 0 ❌ 静默失败,停在原地
goTo(40) 数字 0 → 40

字符串形式失败、数字形式成功——decodeURIComponent(target) 正是把数字转成字符串的那一步,因果链闭合。

对照:未含本 PR 的分支goTo(0) / goTo(10) / goTo(40) 三次调用后 section 一律停在 11,纹丝不动,且不抛错、不报警——与 #747 描述的「静默失败」完全一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] view.goTo(index) 因 decodeURIComponent 静默失败,按索引跳章不生效(TTS 章节切换 / AI 引用跳转)

1 participant