Skip to content

✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM)#1565

Draft
cyfung1031 wants to merge 12 commits into
scriptscat:mainfrom
cyfung1031:pr/gm-cookies-firstPartyDomain
Draft

✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM)#1565
cyfung1031 wants to merge 12 commits into
scriptscat:mainfrom
cyfung1031:pr/gm-cookies-firstPartyDomain

Conversation

@cyfung1031

@cyfung1031 cyfung1031 commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Checklist / 检查清单

  • Fixes API Compatibility Issue (TM/VM)
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

背景

Firefox 的 First-Party Isolation(FPI,privacy.firstparty.isolate)会使用 firstPartyDomain 区分 Cookie 所属的第一方分区。

Tampermonkey 和 Violentmonkey 的 GM_cookie / GM.cookie 接口支持传入该字段,但 ScriptCat 目前既未公开对应类型,也不会将其传递给 Firefox Cookies API。

这会导致启用 FPI 的 Firefox 环境中,用户脚本无法准确列出、创建或删除指定第一方分区内的 Cookie;其中 set / delete 在缺少该字段时还可能直接被 Firefox 拒绝。

同时,该字段属于 Firefox 特有扩展。如果在其他浏览器中直接传递,可能因为 Cookies API 不认识该参数而导致整个调用失败,因此需要在浏览器适配层明确处理能力差异。

本次改动

  • GM_cookie / GM.cookie 参数增加可选的 firstPartyDomain 字符串支持。

  • 内容脚本侧将 firstPartyDomain 纳入参数类型校验,非字符串值会返回 Invalid Argument Type

  • Service Worker 侧仅在 Firefox 环境向 chrome.cookies.getAllsetremove 传递该字段;其他浏览器会从底层调用参数中完全省略,避免未知字段导致 Cookies API 拒绝请求。

  • 用户脚本显式使用 firstPartyDomain 时输出浏览器兼容性警告:

    • Firefox:提示该字段属于 Firefox 特有能力,在其他浏览器中的行为可能不同;
    • 非 Firefox:提示当前浏览器不支持该字段,参数将被忽略。
  • 警告按照用户脚本 UUID 去重,同一运行时内每个脚本只提示一次,不因重复调用刷屏;不同脚本分别提示。

  • 对传入值执行 trim(),同时保留显式空字符串语义。

  • 按 Firefox Cookies API 的实际行为分别处理不同操作:

    • list:未提供时显式传入字面量 null
    • set / delete:未提供时省略字段,由 Firefox 在 FPI 开启时按原生规则报错。
  • 扩展 chrome.cookies.GetAllDetailsSetDetailsCookieDetailsCookie 的环境类型声明。

  • 更新 GMTypes.CookieDetailsGMTypes.Cookie,并同步模板、英文及简体中文用户脚本类型声明。

  • 更新开发文档中的浏览器特有 API 处理原则:能力差异应在适配层处理,并通过可操作、按脚本去重的 DevTools 诊断明确告知用户,而不是静默伪装成等价行为。

  • 新增内容脚本和 Service Worker 测试,覆盖参数校验、浏览器差异、警告内容和去重边界、空白裁剪、空字符串、各操作的缺省行为及错误传播。

实现考虑

1. 只在 Firefox 中传递 firstPartyDomain

firstPartyDomain 是 Firefox Cookies API 为 First-Party Isolation 提供的扩展字段。

Chrome 等其他浏览器不支持该参数,并可能因为出现未知属性而拒绝整个 Cookies API 调用。

因此实现使用 isFirefox() 作为运行时开关:

  • Firefox:按照各操作的语义传递 firstPartyDomain
  • 非 Firefox:从实际传给 Cookies API 的参数中省略该字段。

这样可以提供 Firefox 兼容能力,同时不破坏其他浏览器现有的 Cookie 操作。

2. 浏览器能力差异必须提供明确诊断

虽然非 Firefox 环境不能把 firstPartyDomain 传递给底层 API,但完全静默忽略会让用户脚本误以为 Cookie 操作已经按照指定分区执行。

因此,脚本首次显式使用该字段时会输出 DevTools 警告:

  • Firefox 环境说明该字段是 Firefox-specific;
  • 其他浏览器说明该字段不受支持并会被忽略。

警告包含脚本 UUID、脚本名称和 GM_cookie 组件信息,便于开发者定位来源。

去重边界是用户脚本 UUID,而不是整个扩展全局:同一脚本重复调用不会刷屏,不同脚本仍会分别收到诊断。

3. 区分 listset / delete 的缺省语义

Firefox 对 cookies.getAll()cookies.set() / cookies.remove() 的处理并不对称:

  • getAll() 会区分“完全没有 firstPartyDomain key”和“key 存在但值为 null”;
  • FPI 开启时,前者会触发必填校验;
  • 后者会跳过该必填校验,并且不按照第一方域过滤。
  • set() / remove()null 与未提供的处理等价;FPI 开启时仍会因为缺少有效值而拒绝请求。

因此:

  • list 在未提供字段时补字面量 null
  • set / delete 不补默认值;
  • 实现不会伪造 Cookie 所属的第一方分区。

4. 区分“未提供”和“显式空字符串”

Firefox 使用空字符串表示 Cookie 创建时 First-Party Isolation 处于关闭状态,因此 "" 是合法值,不能与未提供字段混为一谈。

实现先判断原始值是否为字符串,再执行 trim()

  • 未提供:按照对应操作的缺省规则处理;
  • "" 或仅包含空白的字符串:保留为 ""
  • 非空字符串:裁剪首尾空白后传递。

5. 不自动推导当前 firstPartyDomain

WebExtension API 无法可靠读取 privacy.firstparty.isolateprivacy.firstparty.isolate.use_site 等内部偏好,也没有官方接口可以直接取得与 Firefox 内部算法完全一致的当前第一方域。

从标签页 URL 自行推导 eTLD+1 也无法完整覆盖:

  • use_site 配置;
  • 特殊 scheme;
  • 容器上下文;
  • 隐私浏览上下文;
  • Firefox 内部的分区计算规则。

因此本 PR 不尝试猜测默认值。需要精确操作分区时,由用户脚本显式提供 firstPartyDomain

已知限制

1. 仅 Firefox 支持实际分区操作

Chrome 及其他不支持该 Cookies API 扩展字段的浏览器不会向底层 API 传递 firstPartyDomain

参数会被忽略,但脚本首次使用时会收到明确的 DevTools 警告,避免把实际行为误认为跨浏览器等价。

2. 警告仅在当前运行时内去重

去重集合由当前 Service Worker 的 GMApi 实例维护。

同一运行时内每个用户脚本只会警告一次;Service Worker 或扩展运行时重启后,脚本再次使用该字段时可能重新出现警告。

3. list 缺省时会跨第一方分区查询

Firefox 下调用 GM_cookie("list", ...) / GM.cookie.list(...) 而未提供 firstPartyDomain 时,实现会传入 null,从而绕过 FPI 必填校验并匹配所有 first-party domains。

这与现有兼容实现一致,但查询范围不会自动限制在调用脚本所在标签页的当前第一方分区。

4. set / delete 在 FPI 开启时需要显式提供

Firefox 开启 FPI 后,若 set / delete 未提供有效的 firstPartyDomain,浏览器的原生拒绝会直接传播给调用方。

实现不会吞掉错误,也不会伪造默认值。

5. 兼容目标

本实现依据 Firefox Cookies API 的实际参数语义,并提供与常见用户脚本管理器相近的 firstPartyDomain 接口。

不在类型声明或文档中承诺与某个特定版本的 Tampermonkey 或 Violentmonkey 在所有边缘行为上完全一致。

建议审查重点

  • Firefox 与非 Firefox 环境的底层参数隔离是否足够严格;
  • 非 Firefox 环境是否仍能正常执行原有 Cookie 操作;
  • Firefox 与非 Firefox 的警告内容是否清晰、可操作;
  • 警告是否正确按照用户脚本 UUID 去重,同时保留不同脚本之间的边界;
  • listnullset / delete 省略字段的差异是否符合 Firefox 实际行为;
  • 空字符串与未提供字段的区分是否完整;
  • 跨 first-party 分区查询的已知取舍是否可以接受;
  • 浏览器特有能力的开发文档原则是否符合项目整体兼容策略;
  • 新增公开类型及中英文注释是否符合项目现有 API 约定。

参考

https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/cookies#storage_partitioning

violentmonkey/violentmonkey#1467

violentmonkey/violentmonkey#746

Chrome 不支持该参数(chrome.cookies 会拒绝未知属性),因此仅在 Firefox 下透传给 chrome.cookies.getAll/set/remove,其余浏览器直接忽略该字段。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cyfung1031
cyfung1031 force-pushed the pr/gm-cookies-firstPartyDomain branch from 7a20d26 to 48248a5 Compare July 11, 2026 10:06
@cyfung1031 cyfung1031 changed the title GM_cookie: support firstPartyDomain (Firefox First-Party Isolation) GM_cookie:支持 firstPartyDomain(Firefox 第一方隔离) Jul 11, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cyfung1031 cyfung1031 changed the title GM_cookie:支持 firstPartyDomain(Firefox 第一方隔离) GM_cookie:支持 Firefox firstPartyDomain (TM/VM) Jul 11, 2026
@cyfung1031 cyfung1031 changed the title GM_cookie:支持 Firefox firstPartyDomain (TM/VM) GM_cookie:支持 Firefox First-Party Isolation: firstPartyDomain (TM/VM) Jul 11, 2026
@cyfung1031 cyfung1031 changed the title GM_cookie:支持 Firefox First-Party Isolation: firstPartyDomain (TM/VM) GM_cookie 支持 Firefox First-Party Isolation: firstPartyDomain (TM/VM) Jul 11, 2026
@cyfung1031 cyfung1031 changed the title GM_cookie 支持 Firefox First-Party Isolation: firstPartyDomain (TM/VM) ✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM) Jul 11, 2026
cyfung1031 and others added 5 commits July 11, 2026 20:13
- list 分支误判 cookieAction === "getAll"(实际值为 "list"),导致 Firefox 下 firstPartyDomain 从未真正传给 chrome.cookies.getAll;改为区分「是否提供」而非真值判断,未提供时按 MDN 语义传 null(getAll)或省略(set/remove),提供空字符串时如实保留(代表 FPI 关闭时创建的 cookie)。
- 补齐 GMTypes.Cookie.firstPartyDomain(英/中文类型声明)及内置编辑器默认类型定义 src/template/scriptcat.d.tpl 的 CookieDetails/Cookie,避免与独立声明文件不同步。
- 新增空字符串/纯空白、list 未提供时传 null、set/delete 未提供时省略字段的单元测试。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chrome.cookies.getAll({firstPartyDomain: null}) 会跨越所有 first-party 分区查询,等同绕过 Firefox 的 First-Party Isolation。
GM_cookie 的权限校验只验证目标 hostname,未考虑调用脚本所在的 first-party 上下文,之前默认给未提供的 firstPartyDomain 补 null,
会让脚本读取到与自己运行上下文无关的第三方分区下的同名 cookie(见 violentmonkey/violentmonkey#1467)。

现在未提供该字段时一律省略(不再区分 list 与 set/delete),只有脚本显式提供时才透传给 chrome.cookies.getAll/set/remove;
相应移除 chrome.cookies.GetAllDetails.firstPartyDomain 的 null 联合类型标注与失效测试。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
上一版为规避 null 跨分区查询的隐患,把未提供时一律省略该字段,但这重新引入了 violentmonkey/violentmonkey#746
描述的问题:FPI(含 Tor Browser 的默认设置)开启时该参数为必填,缺省会导致 chrome.cookies.getAll/remove 的
promise 被拒绝,即 GM_cookie 的 list/delete 在这些环境下报错。

对齐 Violentmonkey 的实际实现(其 checkCookieOpts 对 CookieList/CookieDelete 均补 null,仅 CookieSet 不补):
list/delete 未提供时补 null,set 未提供时仍省略(无法用 null 表达新 cookie 的归属)。

"按调用标签页的 first-party 上下文收紧 firstPartyDomain"(而非放宽为 null)目前没有可靠算法可从 WebExtension
API 计算(需感知 privacy.firstparty.isolate.use_site 等无法读取的偏好),Violentmonkey 自身对此有长期开放但
未实现的讨论(violentmonkey/violentmonkey#1467),本次不在此 PR 范围内引入。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
上一版把 list 与 delete 一并按 Violentmonkey 的写法补 null,但直接查阅 Firefox 源码
(toolkit/components/extensions/parent/ext-cookies.js,而非 MDN 文档)后确认两者处理并不对称:

- getAll():`if (!("firstPartyDomain" in details)) validateFirstPartyDomain(details)` ——
  只有「完全没有这个 key」才会在 FPI 开启时报错;key 存在但值为 null/undefined 会跳过校验,
  且 oaFromDetails 在 allowPattern=true(仅 getAll)时会因此丢弃 firstPartyDomain 过滤条件。
  因此 list 必须显式传字面量 null(而非省略整个字段,stripUndefined 只会剔除 undefined)。
- set() / remove():`validateFirstPartyDomain(details)` 无条件执行,且该函数用 `!= null`
  判断,对 null 与 undefined 一视同仁;oaFromDetails 在 allowPattern=false(set/remove)时
  用 `details.firstPartyDomain ?? ""`,null 与未提供得到完全相同的结果。也就是说给
  set/remove 补 null 是无效动作:既不能避免 FPI 开启时的报错,也没有「跨分区」的含义。

因此改为:
- list 未提供时补字面量 null(对 getAll 才有效,避免 violentmonkey/violentmonkey#746 描述的
  FPI/Tor Browser 报错)。
- set/delete 未提供时直接省略该字段;FPI 开启且脚本未提供时,让 Firefox 自身抛出
  "... required 'firstPartyDomain' attribute was not set." 并原样传播给调用方,不去伪造
  一个值掩盖这个限制。

「list 的 null 会跨越所有 first-party 分区、GM_cookie 权限校验只验证 hostname 未限定调用脚本
自身分区」这一取舍依旧存在且注释里已说明:正确收紧到调用标签页当前分区需要复刻 Firefox 内部
eTLD+1 算法并感知无法从 WebExtension API 读取的偏好,Violentmonkey 自身对此有长期开放但未
实现的讨论(violentmonkey/violentmonkey#1467),不在本 PR 范围内引入。

同步修正 chrome.cookies.CookieDetails(remove 使用)类型标注,去掉不再需要的 null 联合;
补充 GMTypes.CookieDetails.firstPartyDomain 的文档说明 set/delete 在 FPI 下必须显式提供;
测试更新为验证 list 补 null、delete 省略字段、以及 set/delete 在 FPI 拒绝时原样传播错误。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cyfung1031 cyfung1031 added the GM API 支持一下TM/VM/FM的API label Jul 11, 2026
@cyfung1031

cyfung1031 commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator Author

945cdaa 7295396

我认为这些 browser 限定的设定,违背了 ScriptCat 的设计
(像 Tampermonkey 倾向一大堆设定一样)
如果 Firefox 跟 Chrome 不一样的话,应该要显示到 DevTools.

往后的其他 browser specific config option 也应如此

@CodFrm

CodFrm commented Jul 20, 2026

Copy link
Copy Markdown
Member

👀 还没处理/没准备好处理的,不用merge得这么频繁吧

@cyfung1031

cyfung1031 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

👀 还没处理/没准备好处理的,不用merge得这么频繁吧

现在不merge 一下之后merge不了

反正测试都要用最新代码去测

这个 draft PR 是因为未测试好

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

Labels

GM API 支持一下TM/VM/FM的API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants