Skip to content

feat: 支持 allow 例外规则,实现父路径拦截与子路径放行 #2

@ooooo-create

Description

@ooooo-create

背景

当前 Nolo 只支持基于 urlFilter 的拦截规则,并统一生成 redirect -> blocked.html 的 DNR 动态规则。它还不能表达“在一个更大的路径范围内拦截,但对其中某个子路径放行”的需求。

典型场景:

  • 想拦截 github.com/xxx/ 下的大部分内容
  • 但允许访问 github.com/xxx/yyy 及其下级路径

当前代码现状:

  • rule-utils.jsbuildDynamicRules() 只生成 redirect 规则
  • background.js 的状态模型只围绕 blockedSites 工作
  • popup.js 只支持新增 / 启停 / 删除“拦截规则”,没有“放行例外”概念

目标

  • 支持“父路径拦截、子路径放行”的规则表达能力
  • 保持当前基于 DNR 的实现路线,不引入请求拦截型后台代理
  • 尽量复用现有 popup / background / rule-utils 架构
  • 不新增超出当前需求的新高风险权限

非目标

  • 这一期不做复杂的可视化规则树编辑器
  • 这一期不做同步到云端、多设备同步
  • 这一期不做站点模板市场或预设规则仓库

技术调研结论

1. DNR 原生支持 allow 规则

Chrome 官方 DNR 文档明确支持以下动作:

  • block
  • redirect
  • allow
  • allowAllRequests
  • upgradeScheme
  • modifyHeaders

其中 allow 的语义是:如果请求命中 allow 规则,则该请求不会被当前扩展拦截。

2. 同一扩展内的规则优先级满足“高优先级 allow 覆盖低优先级 redirect”

官方文档说明:

  • 先按开发者设置的 priority 排序
  • 同优先级下按动作优先级排序:
    • allow
    • allowAllRequests
    • block
    • upgradeScheme
    • redirect

因此,实现路径例外的核心方式是:

  • 用较高优先级的 allow 规则放行子路径
  • 用较低优先级的 redirect 规则拦截父路径

3. 这个需求大多数情况下不需要 regexFilter,urlFilter 就够用

GitHub 这类路径场景可以先用 urlFilter 完成:

  • block: ||github.com/xxx/
  • allow: ||github.com/xxx/yyy^

这里的 ^ 是 DNR urlFilter 的分隔符/结尾匹配符,可以避免把 github.com/xxx/yyyzzz 误判为放行目标。

这意味着首期实现可以不引入 regex 编辑复杂度,就先满足主要需求。

4. regexFilter 仍然值得保留为后续高级能力

虽然首期不必依赖 regex,但更复杂的路径约束、排除规则、精确匹配仍然会受益于 regexFilter。Chrome 官方也支持 regexFilter,并提供:

  • RE2 语法
  • isRegexSupported() 校验能力
  • 每类 regex 规则最多 1000 条的限制

结论:

  • MVP 可先做 urlFilter + allow
  • regexFilter 作为第二阶段高级能力更合适

5. 当前权限足够,不需要为 allow 规则额外加权限

当前 manifest 已包含:

  • declarativeNetRequest
  • storage
  • webNavigation
  • host_permissions: ["<all_urls>"]

基于官方文档,allow 规则可以直接在现有 DNR 能力内使用,因此本需求不需要新增新的核心权限。

6. 开发期验证可以结合 DNR 调试 API

Chrome 官方提供:

  • testMatchOutcome()
  • getMatchedRules()
  • onRuleMatchedDebug

这些能力主要用于 unpacked extension 调试,适合开发阶段验证“allow 是否真的压过 redirect”。

需求定义

用户故事

  1. 用户可以新增一条“拦截规则”
  2. 用户可以新增一条“放行规则”
  3. 当某个请求同时命中“放行规则”和“拦截规则”时,应优先放行
  4. 用户可以通过规则组合表达:
    • 拦截 github.com/xxx/ 下的大多数页面
    • 放行 github.com/xxx/yyy 与其下级路径
  5. popup 中可以清楚区分规则类型,避免误操作

验收示例

给定以下两条规则:

  • allow: ||github.com/xxx/yyy^
  • block/redirect: ||github.com/xxx/

预期行为:

  • https://github.com/xxx/aaa -> 被拦截
  • https://github.com/xxx/yyy -> 放行
  • https://github.com/xxx/yyy/issues -> 放行
  • https://github.com/xxx/yyyzzz -> 不应因为 allow 规则被误放行
  • https://github.com/other/repo -> 不受影响

推荐技术方案

方案选型

推荐先做“通用规则动作扩展”,而不是直接做“嵌套例外 UI”。

原因:

  • 更贴近 DNR 原生模型
  • 与当前代码结构更兼容
  • 后续要支持 regexFilter / block / allow 时更容易扩展
  • 首期实现成本更低

规则模型升级

建议将当前单一拦截规则模型升级为通用规则模型,建议字段如下:

{
  id: number;
  action: "redirect" | "allow";
  patternType: "urlFilter" | "regexFilter";
  pattern: string;
  enabled: boolean;
}

说明:

  • redirect 继续表示“拦截并跳转到 blocked.html”
  • allow 表示“放行,不拦截”
  • patternType 让首期与后续阶段共用同一数据模型
  • 首期 UI 可以只开放 urlFilter,但底层数据结构提前兼容 regexFilter

状态与存储

建议把当前 blockedSites 演进为更语义化的 rules

如果要最小改动,也可以短期保留原存储键名,但对象结构需扩展出 actionpatternType。长期更推荐统一重命名为 rules,减少概念歧义。

DNR 生成策略

建议在 buildDynamicRules() 中按动作类型生成不同 DNR action:

  • redirect -> blocked.html?ruleId=<id>
  • allow -> { type: "allow" }

建议显式设置优先级:

  • allow 规则:priority = 100
  • redirect 规则:priority = 10

这样即使未来出现更多规则组合,也能稳定表达“放行优先于拦截”。

匹配策略

首期推荐:

  • 默认使用 urlFilter
  • 路径例外通过更具体的 allow 规则表达

例如:

  • allow: ||github.com/xxx/yyy^
  • redirect: ||github.com/xxx/

第二阶段再考虑开放 regexFilter

  • 在保存前通过 chrome.declarativeNetRequest.isRegexSupported() 校验
  • 对不支持的 regex 给出明确报错

UI / UX 方案

popup 需要新增“规则类型”概念,建议最小实现为:

  • 新增一个动作选择控件:拦截 / 放行
  • 规则列表中展示动作标签,例如:
    • 拦截
    • 放行
  • 保留当前启停、删除交互
  • 保持输入框仍面向高级用户,继续接受 DNR 规则表达式

建议不要在第一期就做可视化“父规则 + 子例外”树状编辑器,先让底层能力上线。

blocked 页面影响

blocked 页面只服务于 redirect 规则。

因此:

  • allow 规则不需要 blocked 页展示
  • GET_SITE_BY_RULE_ID / CONSUME_BLOCKED_REQUEST 逻辑可以继续服务于 redirect 规则
  • 只需保证 blocked 页在读取规则时能处理“当前 ruleId 对应的是 redirect 规则”这一前提

风险与注意事项

  1. 存储迁移风险

    • 如果要把 blockedSites 重命名为 rules,需要决定是否做迁移
    • 如果当前 main 已有用户,建议设计一次性迁移逻辑
  2. 规则冲突风险

    • 用户可以创建过宽的 allow 规则,导致部分拦截失效
    • 需要在 UI 文案和排序逻辑上让优先级模型足够清晰
  3. regex 复杂度风险

    • regexFilter 有 1000 条上限和复杂度限制
    • 所以不建议把 regex 当作首期主路径
  4. 测试误判风险

    • 仅靠单元测试不够,仍需浏览器内手动验证真实 DNR 行为

具体实施规划

Phase 1: 规则模型与 DNR 基础能力

  • 扩展规则数据结构,支持 actionpatternType
  • 调整 normalizeState() / serializeState() / 去重逻辑
  • 调整 buildDynamicRules(),支持输出 allowredirect
  • 为 allow / redirect 设置显式优先级
  • 扩展 background.js 的新增 / 更新 / 删除消息处理

Phase 2: Popup 交互

  • 在 popup 中增加规则动作选择器(拦截 / 放行)
  • 在规则列表中展示规则类型标签
  • 优化状态提示文案,使“放行优先级高于拦截”可理解
  • 保持当前启停 / 删除交互不退化

Phase 3: 验证与测试

  • 扩展 rule-utils.test.js,覆盖 mixed allow + redirect 规则生成
  • 补充状态规范化 / 去重 / 相等性测试
  • 增加手动验证用例:GitHub 父路径拦截 + 子路径放行
  • 在 unpacked extension 下用 testMatchOutcome() / getMatchedRules() 辅助验证

Phase 4: 高级能力(可选后续 issue)

  • 开放 regexFilter 作为高级匹配方式
  • 在 background 中接入 isRegexSupported() 保存前校验
  • 评估是否提供“例外规则模板”或“嵌套例外 UI”

建议的首批落地范围(MVP)

建议本 issue 先完成以下闭环:

  • 支持 allow + redirect
  • 仅要求 urlFilter
  • popup 可创建 / 管理两类规则
  • 用明确优先级满足“父路径拦截、子路径放行”

这已经足以满足最核心的 GitHub 场景,而且能以最低复杂度验证产品方向。

参考资料

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions