Skip to content

perf(onboarding,settings): 权限/热键状态纯事件驱动,去掉高频轮询#673

Merged
appergb merged 2 commits into
betafrom
fix/470-frontend-permission-eventdriven
Jun 16, 2026
Merged

perf(onboarding,settings): 权限/热键状态纯事件驱动,去掉高频轮询#673
appergb merged 2 commits into
betafrom
fix/470-frontend-permission-eventdriven

Conversation

@appergb

@appergb appergb commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

User description

源自 #470 Cloud 性能审查 #3/#4/#5。引导页桌面(1s)/Android(3s)、设置页热键(1s) 的高频权限/热键 setInterval 轮询全部改为 focus/visibilitychange 事件驱动,连低频兜底也去掉(纯事件驱动)。

覆盖论证:麦克风前台原生弹窗授予由申请结果回调 + 800ms 兜底覆盖;辅助功能授予必经系统设置 App、切回必触发 focus/visibilitychange。并给 onGrantAccessibility 补对称的 void refresh() + 800ms 兜底。refreshPermissions(10s)/refreshNetwork(30s) 保留不动。

验证npm run build 通过 + 对抗式复审(已修复其指出的 floating-promise)。


PR Type

Enhancement


Description

  • 移除高频轮询,改用 focus/visibilitychange 事件驱动

  • 添加 visibilitychange 事件监听,确保从系统设置返回后立即刷新

  • 对称处理麦克风和辅助功能授权后的刷新逻辑

  • 统一代码风格(void refresh())消除 floating promise


File Walkthrough

Relevant files
Performance improvement
Onboarding.tsx
事件驱动替代轮询 + 授权刷新对称处理                                                                           

openless-all/app/src/components/Onboarding.tsx

  • AndroidMicrophoneStep: 移除 3 秒轮询,改为 focus + visibilitychange 事件
  • DesktopOnboarding: 移除 1 秒轮询,添加同类事件监听
  • onGrantAccessibility: 添加同步刷新 + 800ms 超时兜底
  • 所有 refresh() 调用加 void(修复 floating promise)
+14/-6   
PermissionsSection.tsx
Settings 页热键状态改为纯事件驱动                                                                       

openless-all/app/src/pages/settings/PermissionsSection.tsx

  • 移除热键 1 秒轮询(retain 10s/30s 低频轮询)
  • 添加 focus + visibilitychange 事件驱动刷新所有状态
  • 统一焦点事件处理逻辑
+6/-5     

issue #470:引导页桌面(1s)/Android(3s)、设置页热键(1s) 的 setInterval 高频权限/热键轮询
全部改为 window focus / visibilitychange 事件驱动,连低频兜底也去掉(纯事件驱动)。
覆盖论证:麦克风前台原生弹窗授予由申请结果回调 + 800ms 兜底覆盖;辅助功能授予必经系统
设置 App、切回 OpenLess 必触发 focus/visibilitychange。并给 onGrantAccessibility 补对称的
void refresh() + 800ms 兜底(与 onRequestMicrophone 对齐)。PermissionsSection 的
refreshPermissions(10s)/refreshNetwork(30s) 保留不动。
@appergb

appergb commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author

@claude 请审查本 PR(#470 #3/#4/#5:权限/热键状态纯事件驱动)。重点核查删掉所有高频/低频定时器后授予路径是否仍全覆盖(麦克风申请结果回调、辅助功能 focus/visibilitychange、onGrantAccessibility 补的 800ms 兜底),listener 清理是否到位无泄漏,10s/30s 两个定时器是否保留。

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit d221ee2)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ❌

4 - Not compliant

Non-compliant requirements:

  • 未添加翻译目标语言设置项
  • 未实现 Shift 组合键触发
  • 未添加 translate prompt 模板
  • 未更新胶囊 UI 翻译模式标签

5 - Not compliant

Non-compliant requirements:

  • 未添加 F5 或双击快捷键支持
  • 未优化组件描述 prompt 注入

470 - Not compliant

Non-compliant requirements:

  • PR 未修改 Windows 胶囊渲染相关代码,未直接修复胶囊不显示问题
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown

Claude finished @appergb's task in 2m 24s —— View job


PR #673 代码审查

  • 获取上下文,读取变更文件
  • 核查麦克风申请结果回调覆盖
  • 核查辅助功能 focus/visibilitychange 覆盖 + onGrantAccessibility 800ms 兜底
  • 核查 listener 清理(无泄漏)
  • 核查 10s/30s 定时器是否保留
  • 汇总审查结论

总体结论

逻辑设计正确,授予路径全覆盖,cleanup 无泄漏,两个长周期定时器保留完好。发现 1 个需修复的 floating-promise(DesktopOnboarding 的 refresh() 调用未加 void)和 2 个较小建议。


各项核查

1. 麦克风申请结果回调覆盖 ✅

AndroidMicrophoneStepOnboarding.tsx:234-246):

  • request() 内部:setStatus(await requestAndroidMicrophoneAccess()) 直接从回调结果更新状态,后接 await refresh() 做双重确认。授予结果即时体现,无需等待 focus。
  • focus/visibilitychange 兜底用户从系统设置返回的场景,逻辑完整。

DesktopOnboarding onRequestMicrophoneOnboarding.tsx:331-348):

  • requestMicrophonePermission() 返回值直接 setMicrophone(status),状态即时更新。
  • 追加 800ms setTimeout(refresh, 800) 作为二次确认。✅

2. 辅助功能 focus/visibilitychange + onGrantAccessibility 800ms 兜底 ✅

onGrantAccessibilityOnboarding.tsx:317-329):

void refresh();                                      // 立即刷新
if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current);
refreshTimeoutRef.current = window.setTimeout(refresh, 800);  // 800ms 兜底
  • requestAccessibilityPermission() + openSystemSettings('accessibility') 之后立即 void refresh(),再接 800ms 定时兜底。
  • 用户必须离开 app 进入系统设置 → 切回时必触发 focus/visibilitychange,两条路径均覆盖。✅

3. Listener 清理(无泄漏)✅

AndroidMicrophoneStepOnboarding.tsx:228-231):

return () => {
  window.removeEventListener('focus', onFocus);
  document.removeEventListener('visibilitychange', onVisibility);
};

DesktopOnboardingOnboarding.tsx:310-314):

return () => {
  window.removeEventListener('focus', onFocus);
  document.removeEventListener('visibilitychange', onVisibility);
  if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current);
};
  • refreshTimeoutRef 在 cleanup 中也已清除,防止卸载后 refresh() 被调用。✅

PermissionsSectionPermissionsSection.tsx:101-106):

return () => {
  window.clearInterval(permissionId);
  window.clearInterval(networkId);
  window.removeEventListener('focus', onFocus);
  document.removeEventListener('visibilitychange', onVisibility);
};

全部清理,无泄漏。✅


4. 10s/30s 定时器保留 ✅

PermissionsSection.tsx:85-86

const permissionId = window.setInterval(refreshPermissions, 10000);  // ✅ 保留
const networkId    = window.setInterval(refreshNetwork,     30000);  // ✅ 保留

问题

🔴 Floating Promise(需修复)— DesktopOnboarding

PR 描述声称已修复 floating-promise,但 DesktopOnboardingrefresh() 的三处调用未加 void。对比 AndroidMicrophoneStepvoid refresh() 写法不一致。

Onboarding.tsx 第 304、306、307 行:

// 当前(浮动 Promise)
useEffect(() => {
  refresh();                                                              // 行 304
  const onFocus      = () => refresh();                                   // 行 306
  const onVisibility = () => { if (...) refresh(); };                     // 行 307
  ...
}, [requiresAccessibility]);

// 建议
useEffect(() => {
  void refresh();
  const onFocus      = () => { void refresh(); };
  const onVisibility = () => { if (document.visibilityState === 'visible') void refresh(); };
  ...
}, [requiresAccessibility]);

Fix this →


🟡 PermissionsSection refreshAll() 中的浮动 Promise(非本 PR 引入,建议跟进)

PermissionsSection.tsx:87-96refreshAll() 内部调用 refreshPermissions()refreshHotkey() 等均为 async 函数,无 voidawait。这个模式在文件其它位置(如 refreshPermissions() 直接调用行 75)已存在,非本 PR 引入,可在后续统一修复。


🟡 refreshAll() 在每次 focus 时也触发 refreshNetwork()

PermissionsSection.tsx:94refreshNetwork() 出现在 refreshAll() 中,因此每次用户切回窗口都会触发网络检查(除了 30s 周期外)。对网络延迟较高的环境影响可忽略,但若 focus 触发非常频繁(如快速 cmd+tab)会产生多次并发网络请求。若要更保守,可考虑将网络检查仅保留在 30s 定时器中,不放入 refreshAll()。优先级低,供参考。


@HKLHaoBin

Copy link
Copy Markdown
Contributor

https://github.com/HKLHaoBin/openless/actions/runs/27556216066
我跑一下真机检查。

@claude 审查 #673 指出:DesktopOnboarding 的 useEffect 里 refresh()/onFocus/onVisibility
三处 refresh() 是 floating promise(与上方 Android 段的 void refresh() 不一致,IPC 失败会
静默)。统一加 void 明确 fire-and-forget。issue #470
@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit d221ee2

@appergb appergb merged commit ee42d1a into beta Jun 16, 2026
5 checks passed
@appergb appergb deleted the fix/470-frontend-permission-eventdriven branch June 16, 2026 03:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants