Skip to content

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

Draft
cyfung1031 wants to merge 9 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 9 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 也应如此

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.

1 participant