✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM)#1565
Draft
cyfung1031 wants to merge 9 commits into
Draft
✨ 为 GM.cookie / GM_cookie 添加 Firefox First-Party Isolation: firstPartyDomain (TM/VM)#1565cyfung1031 wants to merge 9 commits into
cyfung1031 wants to merge 9 commits into
Conversation
Chrome 不支持该参数(chrome.cookies 会拒绝未知属性),因此仅在 Firefox 下透传给 chrome.cookies.getAll/set/remove,其余浏览器直接忽略该字段。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7a20d26 to
48248a5
Compare
- 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>
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist / 检查清单
背景
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.getAll、set、remove传递该字段;其他浏览器会从底层调用参数中完全省略,避免未知字段导致 Cookies API 拒绝请求。用户脚本显式使用
firstPartyDomain时输出浏览器兼容性警告:警告按照用户脚本 UUID 去重,同一运行时内每个脚本只提示一次,不因重复调用刷屏;不同脚本分别提示。
对传入值执行
trim(),同时保留显式空字符串语义。按 Firefox Cookies API 的实际行为分别处理不同操作:
list:未提供时显式传入字面量null;set/delete:未提供时省略字段,由 Firefox 在 FPI 开启时按原生规则报错。扩展
chrome.cookies.GetAllDetails、SetDetails、CookieDetails、Cookie的环境类型声明。更新
GMTypes.CookieDetails与GMTypes.Cookie,并同步模板、英文及简体中文用户脚本类型声明。更新开发文档中的浏览器特有 API 处理原则:能力差异应在适配层处理,并通过可操作、按脚本去重的 DevTools 诊断明确告知用户,而不是静默伪装成等价行为。
新增内容脚本和 Service Worker 测试,覆盖参数校验、浏览器差异、警告内容和去重边界、空白裁剪、空字符串、各操作的缺省行为及错误传播。
实现考虑
1. 只在 Firefox 中传递
firstPartyDomainfirstPartyDomain是 Firefox Cookies API 为 First-Party Isolation 提供的扩展字段。Chrome 等其他浏览器不支持该参数,并可能因为出现未知属性而拒绝整个 Cookies API 调用。
因此实现使用
isFirefox()作为运行时开关:firstPartyDomain;这样可以提供 Firefox 兼容能力,同时不破坏其他浏览器现有的 Cookie 操作。
2. 浏览器能力差异必须提供明确诊断
虽然非 Firefox 环境不能把
firstPartyDomain传递给底层 API,但完全静默忽略会让用户脚本误以为 Cookie 操作已经按照指定分区执行。因此,脚本首次显式使用该字段时会输出 DevTools 警告:
警告包含脚本 UUID、脚本名称和
GM_cookie组件信息,便于开发者定位来源。去重边界是用户脚本 UUID,而不是整个扩展全局:同一脚本重复调用不会刷屏,不同脚本仍会分别收到诊断。
3. 区分
list与set/delete的缺省语义Firefox 对
cookies.getAll()与cookies.set()/cookies.remove()的处理并不对称:getAll()会区分“完全没有firstPartyDomainkey”和“key 存在但值为null”;set()/remove()对null与未提供的处理等价;FPI 开启时仍会因为缺少有效值而拒绝请求。因此:
list在未提供字段时补字面量null;set/delete不补默认值;4. 区分“未提供”和“显式空字符串”
Firefox 使用空字符串表示 Cookie 创建时 First-Party Isolation 处于关闭状态,因此
""是合法值,不能与未提供字段混为一谈。实现先判断原始值是否为字符串,再执行
trim():""或仅包含空白的字符串:保留为"";5. 不自动推导当前
firstPartyDomainWebExtension API 无法可靠读取
privacy.firstparty.isolate、privacy.firstparty.isolate.use_site等内部偏好,也没有官方接口可以直接取得与 Firefox 内部算法完全一致的当前第一方域。从标签页 URL 自行推导 eTLD+1 也无法完整覆盖:
use_site配置;因此本 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 在所有边缘行为上完全一致。
建议审查重点
list补null、set/delete省略字段的差异是否符合 Firefox 实际行为;参考
https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/cookies#storage_partitioning
violentmonkey/violentmonkey#1467
violentmonkey/violentmonkey#746