31 KiB
浏览器环境对象模型补齐指南
当需要从 RuyiTrace / Node trace / fixtures 编写 env.js、runner.js 或最终 result/src/env/* 时读取本文件。本文件用于决定“补哪些浏览器对象”,但不降低对象真实性要求;真实性保护细节必须同时遵循 env-native-protection.md 和 addon-api.md。
总体原则
核心规则:
最小范围,完整真实性。只减少对象覆盖范围,不降低已补对象质量。
不要一开始伪造完整浏览器,但凡某个 WebAPI 进入补环境范围,就必须从第一版实现开始执行:
- 先加载 / 记录 addon,addon 可用时优先使用 addon API。
- 先建立构造函数、构造函数非法调用行为、原型链、实例工厂、
prototype.constructor和Symbol.toStringTag。 - 再安装属性描述符、getter / setter、方法、内部状态和真实样本值。
- getter / setter / 方法 / 构造函数默认 native-like,优先
createGetter/createSetter/createNativeFunction。 - 实例对象默认要满足
Object.prototype.toString.call(obj)、constructor.name、instanceof和 descriptor 检查。 - addon 不可用、ABI 不兼容或 API 调用失败时,才使用
NativeProtect/ JS fallback,并记录降级原因。
“根据访问路径补最小对象模型”只表示不一次性补全所有 DOM / BOM,不表示可以先用普通对象、普通赋值或普通函数跑通后再补保护。
构造函数行为采样
每个进入补环境范围的构造函数都要记录浏览器真实行为:
Ctor()是否允许直接调用。new Ctor()是否允许构造。- 失败时的
error.name、error.constructor.name、error.message、String(error)和 stack 首行。 - 成功时的实例原型、
instanceof、constructor.name和Object.prototype.toString.call(instance)。
不要只写:
throw new TypeError('Illegal constructor');
也不要把所有构造错误都写成同一个 message。EventTarget()、new Node()、new Document()、new Blob()、DOMRect() 等在不同浏览器和调用方式下可能有不同表现;必须以本 case 的取证浏览器为准。建议把结果保存到 case/fixtures/constructor-errors.fixture.json 或 case/notes/构造函数行为采样.md。addon 可用时用 addon 创建构造函数并用 addon.throwTypeError(message) 或等价 helper 抛出浏览器式错误;addon 不支持目标错误类型时才 JS fallback。
推荐补齐顺序:
Node 泄露阻断 → addon 加载记录 → 目标对象范围确认 → 构造函数 / 原型链 / 实例工厂 → 属性描述符 / 访问器 / 方法 → 样本值写入 → fixtures 验证
对象范围由 RuyiTrace、Node trace、fixtures、请求样本和目标入口决定;对象质量由 addon-first、原型链、描述符、访问器、toString 保护、Symbol.toStringTag 和构造函数行为决定。
对象补齐硬性清单
每补一个浏览器对象,先检查以下项目:
| 项目 | 要求 |
|---|---|
| addon-first | 进入补环境阶段先加载 addon,可用时优先使用 addon API |
| 构造函数 | 优先 createProtoChains(descriptors),构造函数名称、length、prototype 描述符要明确 |
| 非法构造行为 | 浏览器不可直接构造的对象要按真实浏览器采样复现错误类型、错误构造器和完整 message,不能统一写泛化 Illegal constructor |
| 原型链 | 先建 Constructor.prototype 与父级链,再创建实例 |
prototype.constructor |
通常不可枚举,指回构造函数 |
Symbol.toStringTag |
挂在正确 prototype 或实例上,按浏览器样本设置 |
| 属性描述符 | 全部关键属性用 Object.defineProperty / defineProperties |
| 访问器 | 浏览器中是 getter / setter 的属性,不得降级为 data descriptor |
| 方法 | 优先 addon.createNativeFunction,fallback 才用 NativeProtect.setNativeFunc |
| 访问器 toString | 优先 addon.createGetter / createSetter,fallback 才保护 getter / setter 函数 |
| 实例 toString | 优先 addon 原型链 / 实例工厂;addon 创建的实例通常已处理对象 toString,fallback 才用 Symbol.toStringTag / NativeProtect.setObjFunc |
| 内部状态 | 优先 addon.setPrivate/getPrivate,fallback 才用 WeakMap |
| 降级记录 | addon 不可用或用户豁免必须写入 notes、阶段报告和最终总结 |
全局对象
基础关系可按目标需要安装:
globalThis.window = globalThis;
globalThis.self = globalThis;
globalThis.top = globalThis;
globalThis.parent = globalThis;
真实浏览器的 window 不是普通对象。只要补 Window / window,就默认需要考虑:
Window构造函数和非法构造行为。Object.prototype.toString.call(window)。window instanceof Window。window.window === window、window.self === window。window.navigator、window.document、window.location的 descriptor。globalThis、self、top、parent的关系和只读程度。
探测模式可以最小化;交付模式不得把普通对象当作最终 window 真实性方案。
iframe / Worker Realm 隔离
iframe 与 Worker 不是“换一个全局对象引用”的浅拷贝。只要目标路径创建 iframe、DedicatedWorker 或 SharedWorker,就必须为每个 Realm 建立独立全局、构造器图、对象实例和生命周期状态。
iframe 最低关系:
iframe.contentWindow !== mainWindow
iframe.contentWindow.self === iframe.contentWindow
iframe.contentWindow.window === iframe.contentWindow
iframe.contentWindow.globalThis === iframe.contentWindow
iframe.contentWindow.parent === mainWindow
iframe.contentWindow.top === mainWindow.top
iframe.contentWindow.frames === iframe.contentWindow
iframe.contentDocument.defaultView === iframe.contentWindow
iframe.contentWindow.document === iframe.contentDocument
iframe.frameElement === iframe
主 Window 与同源 iframe 可以共享同源 Cookie、Storage 后端和网络 Session,但公开 wrapper、构造器与实例不能共享 identity。共享底层 origin state 不等于复用 navigator、performance、crypto、location、document、Storage wrapper、timer、EventTarget、XHR/fetch 或 URL 构造器。
必须验证:
frame.Object !== window.Object、frame.Function !== window.Function、frame.Array !== window.Array、frame.Promise !== window.Promise。frame.Event !== window.Event、frame.EventTarget !== window.EventTarget、frame.URL !== window.URL、frame.Blob !== window.Blob、frame.XMLHttpRequest !== window.XMLHttpRequest。- frame 创建的对象满足 frame-local
instanceof,且通常不满足主 Realm 对应构造器的instanceof。 srcdoc、document.open/write/close、navigation/reload 后会创建或更新正确 Document/Realm;旧 Realm 的 timer、XHR、MessagePort task 与 observer callback 不得继续写入新文档。- Worker Realm 不得暴露 Window-only API,Worker 的
self/globalThis、navigator、performance、timer、消息队列和构造器必须独立。
发现 ctx.X = mainWindow.X、frame.URL.prototype = URL.prototype、Worker 直接透传宿主或主 Realm Event/XHR/Request/Response 时,默认按 P0/P1 Realm 隔离缺陷处理,不能标为 accepted-diff。
属性定义工具与模板模块
本 Skill 随包提供可复制模板:
assets/env-modules/native-protect.js:NativeProtect与 addon-first helper。assets/env-modules/base-env.js:Window/Location/Navigator原型链、描述符和 getter 保护。assets/env-modules/storage-env.js:Storage构造函数、实例、方法和lengthgetter。assets/env-modules/document-env.js:EventTarget → Node → Document → HTMLDocument基础链路、cookie accessor、DOM 方法、document.alladdon 优先处理。
复制模板后必须按当前目标的 RuyiTrace / fixtures 修改字段值,不要把模板默认值当成真实采集值。不要随意赋值:
navigator.userAgent = 'xxx';
关键属性必须统一使用 descriptor,并优先把 getter / setter / 方法交给 addon 创建:
Object.defineProperty(Navigator.prototype, 'userAgent', {
get: addon.createGetter('userAgent', 0, function () {
return fixture.browser.userAgent;
}),
enumerable: true,
configurable: true,
});
描述符来源优先级:
- 用户真实浏览器控制台采集。
- ruyiPage / Camoufox / CloakBrowser / 真实浏览器取证样本。
- RuyiTrace 环境访问证据。
- 常见浏览器行为模板。
- 目标 JS 检测结果。
navigator
常见字段:
| 字段 | 来源 |
|---|---|
userAgent |
必须尽量来自真实请求 UA |
language / languages |
来自浏览器样本 |
platform |
来自浏览器样本 |
hardwareConcurrency |
来自浏览器样本或用户确认 |
deviceMemory |
来自浏览器样本或用户确认 |
webdriver |
普通浏览器通常应为 false 或不存在,取决于目标环境 |
plugins / mimeTypes |
优先真实采集,addon 可用时优先 getMimeTypesAndPlugins() |
userAgentData |
来自浏览器样本;brands/fullVersionList/platform/mobile/getHighEntropyValues 必须与请求头 Client Hints 一致 |
补 navigator 时不要先手写普通 function Navigator(){} 作为主路径。推荐:
- 优先用
addon.createProtoChains(descriptors)创建Navigator构造函数、Navigator.prototype和navigator实例工厂。 Navigator构造函数按浏览器行为模拟非法构造,直接调用或new Navigator()应抛出合适 TypeError。Navigator.prototype.constructor、Symbol.toStringTag = "Navigator"、实例原型链在第一版就补齐。userAgent、language、languages、platform、hardwareConcurrency、plugins、mimeTypes等优先用 addon getter。- addon 不可用时,才使用 JS 构造函数 +
Object.defineProperty+NativeProtectfallback。
不要只补返回值。getter 的 Function.prototype.toString.call(descriptor.get)、实例的 Object.prototype.toString.call(navigator)、navigator.constructor.name 都属于默认真实性基线。
navigator.userAgentData 是高强度检测重点。进入补环境范围时必须采样真实浏览器:
brands、mobile、platform、toJSON。getHighEntropyValues()的 Promise 行为、参数校验、返回字段和错误模式。architecture、bitness、model、platformVersion、uaFullVersion、fullVersionList等字段。- 与最终请求头
User-Agent、sec-ch-ua、sec-ch-ua-mobile、sec-ch-ua-platform保持一致;不一致时先修正 fixture 和请求头,不要只改 JS 层。
window.chrome 与 Chrome 专有对象
如果目标检测 window.chrome,不要只补空对象。按目标浏览器版本采样后再决定是否提供:
chrome.appchrome.csichrome.loadTimeschrome.runtime
这些属性的 key、descriptor、函数 name/length/toString、返回对象结构都可能被检测。旧 Chrome API 不要盲目全补;版本不匹配也会形成指纹。addon / xbs 可用时,相关函数仍优先 native-first;fallback 才用 NativeProtect。
location
location 经常参与签名。不要用空字符串猜测。
值应从目标页面 URL 解析:
const u = new URL(fixture.pageUrl);
补 location 时默认需要:
- 优先建立
Location构造函数和Location.prototype。 Location构造函数按浏览器行为模拟非法构造。href、origin、protocol、host、hostname、port、pathname、search、hash优先按真实浏览器 descriptor 安装。- 浏览器中是 getter / setter 的属性保持 accessor,不要降级成普通字段。
- getter / setter 优先
addon.createGetter/createSetter。 - 内部 URL 状态优先
addon.setPrivate/getPrivate,fallback 才用WeakMap。 - 安装
Symbol.toStringTag = "Location",并验证Object.prototype.toString.call(location)。
document 与 cookie
常见访问:
document.cookiedocument.referrerdocument.URLdocument.documentElementdocument.createElementdocument.querySelectordocument.all
补 document 时默认先建立:
EventTarget → Node → Document → HTMLDocument
然后再创建 document 实例。Document / HTMLDocument 构造函数、prototype.constructor、Symbol.toStringTag、实例 Object.prototype.toString 和非法构造行为都属于默认真实性基线。
document.cookie 必须作为 accessor descriptor 处理。即使当前样本只读取 cookie,也建议同时准备最小 setter,setter 可以只实现当前 case 需要的写入、覆盖和过期策略,但不得把 cookie 做成普通 data 属性。getter / setter 优先 addon;fallback 才用 NativeProtect 保护访问器函数。
DOM 方法如 createElement、querySelector、querySelectorAll、getElementById 进入补环境范围后,优先用 addon.createNativeFunction,并挂在正确 prototype 上。
document.createElement(tag) 不能统一返回普通对象。进入补环境范围的 tag 必须映射到正确构造链,例如:
| tag | 期望实例与原型链 |
|---|---|
canvas |
HTMLCanvasElement → HTMLElement → Element → Node → EventTarget → Object |
video |
HTMLVideoElement → HTMLMediaElement → HTMLElement → Element → Node → EventTarget → Object |
audio |
HTMLAudioElement → HTMLMediaElement → HTMLElement → Element → Node → EventTarget → Object |
img / image |
HTMLImageElement → HTMLElement → Element → Node → EventTarget → Object |
a |
HTMLAnchorElement → HTMLElement → Element → Node → EventTarget → Object |
同时验证 constructor.name、instanceof、Object.prototype.toString.call(element)、Symbol.toStringTag、Object.getPrototypeOf walk、跨原型方法 brand check,例如 HTMLElement.prototype.getAttribute.call(video, "src")。
document.all
document.all 是 HTMLDDA / 不可检测特殊对象,不得用普通对象、普通 Proxy 或 undefined 声称完整实现。
选择 isolated-vm 时,优先使用 xbs.dom.createDocument() 自动提供的 document.all:
const document = xbs.dom.createDocument({
url: "https://example.com/",
html: '<main id="app"></main>',
});
// 需要禁用时必须在创建前声明,不要运行时强删。
const withoutAll = xbs.dom.createDocument({ omitApis: ["document.all"] });
const withoutAll2 = xbs.dom.createDocument({ features: { documentAll: false } });
期望关键行为至少包括:
typeof document.all === 'undefined'
document.all == null
document.all !== undefined
Boolean(document.all) === false
'all' in document
Object.prototype.toString.call(document.all) === '[object HTMLAllCollection]'
typeof document.all.length === 'number'
typeof document.all.item === 'function'
typeof document.all.namedItem === 'function'
只有不使用 xbs.dom.createDocument()、而是手写 document 时,才手动使用 xbs.createUndetectable(callback, handlers):
const all = xbs.createUndetectable(function (value) {
if (arguments.length === 0 || value == null) return null;
// 中文说明:这里根据索引、id 或 name 返回元素;未命中返回 null。
return null;
}, {
getter(target, property) {
// 中文说明:索引或命名属性可在这里 materialize;未命中必须放行给原型链。
return { intercept: false };
},
query(target, property) {
return { intercept: false };
},
descriptor(target, property) {
return { intercept: false };
},
enumerator(target) {
return Object.getOwnPropertyNames(target);
},
});
Object.setPrototypeOf(all, HTMLAllCollection.prototype);
手动模式硬规则:
xbs.createUndetectable()只负责typeof all === "undefined"、Boolean(all) === false、all == null等不可检测语义。length / item / namedItem / [0] / 命名属性 / descriptor / enumerator需要由 handlers 与HTMLAllCollection.prototype配合实现。length / item / namedItem / constructor等原型链已有属性不要定义成document.all自有属性,否则会和真实浏览器不一致。- 更接近浏览器的安装方式是挂到
Document.prototypegetter:
Object.defineProperty(Document.prototype, "all", {
get: xbs.createGetter("all", 0, function () {
return all;
}),
enumerable: true,
configurable: true,
});
普通 Node + addon 模式也优先使用 addon createUndetectable(callback, handlers) 并遵循同样的 handlers / 原型链要求。addon 或 xbs 不可用时只能写明降级近似,并必须在 notes、阶段报告和最终总结中标记真实性不足;不得声称完全一致。
Storage
实现 localStorage / sessionStorage 时,不要以普通对象或普通函数作为主路径。推荐:
- 优先用
addon.createProtoChains(descriptors)创建Storage构造函数、Storage.prototype和实例工厂。 Storage构造函数按浏览器行为模拟非法构造。localStorage/sessionStorage由实例工厂创建,并设置正确Symbol.toStringTag、原型链和constructor。getItem、setItem、removeItem、clear、key优先addon.createNativeFunction。length优先addon.createGetter,保持 accessor descriptor。- 内部键值状态优先
addon.setPrivate/getPrivate,fallback 才用WeakMap/Map。
如果 addon 不可用,才使用 JS fallback:
function Storage() {
throw new TypeError("Illegal constructor");
}
const localStorage = Object.create(Storage.prototype);
fallback 仍必须显式 descriptor、原型链、Symbol.toStringTag、方法 toString 和访问器 toString,不得只用普通赋值。
crypto
crypto.getRandomValues、crypto.subtle、crypto.randomUUID 可能参与签名。
原则:
- 如果签名依赖随机数,fixtures 必须记录对应随机输入或控制随机源。
- 不能随意用真实随机数比较固定期望值。
Crypto/SubtleCrypto构造函数、crypto实例、getRandomValues、randomUUID进入补环境范围后,要按 addon-first 建立构造函数、原型链、descriptor 和 native-like 方法。getRandomValues在测试模式下可使用 fixture 中的固定字节序列,但函数形态仍要像浏览器 native API。
performance 与时间
Date.now()、new Date()、performance.now() 经常影响签名。
探测模式可以临时固定:
const fixedNow = fixture.runtime.now;
交付模式不要直接写:
Date.now = () => fixedNow;
performance.now = () => fixture.runtime.performanceNow ?? 0;
交付模式要求:
Date构造函数、Date.now、Date.parse、Date.UTC的name、length、toString和调用行为要受保护。new Date()与Date()两种调用路径都要按样本验证。Performance构造函数、Performance.prototype.now、timeOrigin、相关 descriptor 要明确。performance.now优先addon.createNativeFunction,不要暴露 Nodeperformance.nodeTiming/eventLoopUtilization/timerify。
Canvas / WebGL / WebGPU / 字体 / DOM 几何指纹
这类指纹不要优先在 Node.js 中真实模拟渲染。真实浏览器的 Skia、GPU、字体、抗锯齿、颜色管理和布局细节很难由 node-canvas / headless-gl / jsdom 精确复现。
处理原则:
- 先读取
fingerprint-value-replay.md。 - 用用户确认的取证模式采集终端 API 返回值,例如
toDataURL、getImageData、measureText、getParameter、readPixels、getBoundingClientRect。 - 在 Node.js 中用
assets/env-modules/fingerprint-env.js按调用特征回放采样值。 - 回放函数也要挂在正确 prototype 上,并保持原型链、属性描述符、native-like
toString和实例对象Object.prototype.toString。 - 缺少采样值时阻塞并提示补采样,不要静默返回空值或改用自动化浏览器作为最终方案。
示例接入:
const { installFingerprintValueReplay } = require('./fingerprint-env');
const fingerprintFixture = require('../../fixtures/fingerprint.fixture.json');
installFingerprintValueReplay(globalThis, fingerprintFixture, {
strict: true,
addon,
});
最终项目中不得包含用于采样的 Hook、Playwright、Puppeteer、CloakBrowser、ruyiPage 或其他浏览器自动化代码。
高强度补充 WebAPI 对象清单
从 Cloudflare / Turnstile / Akamai / DataDome / Kasada / Shape / F5 等高强度检测样本抽象出的通用对象范围如下。只有目标 trace / fixture / 取证证据访问到时才补,但一旦补就必须遵循 addon-first / xbs native-first、原型链、描述符、访问器、构造函数行为、Symbol.toStringTag 和 native-like 保护。
| 对象 / API | 重点行为 | 补环境要求 |
|---|---|---|
navigator.permissions / PermissionStatus |
query() Promise、state、onchange、错误类型、权限名校验 |
采样真实浏览器;方法优先 native-like;返回对象原型链和 descriptor 不得用普通对象 |
navigator.plugins / navigator.mimeTypes |
长度、索引属性、命名属性、item()、namedItem()、枚举顺序、Plugin / MimeType 原型 |
addon 可用时优先 getMimeTypesAndPlugins(config);禁止空数组或普通数组作为高强度主路径 |
speechSynthesis / SpeechSynthesisUtterance |
getVoices() 列表、异步 voiceschanged、语言、voiceURI、构造函数行为 |
只回放真实采样的 voices 摘要;构造函数、事件属性和方法 toString 需保护 |
AudioContext / OfflineAudioContext |
构造限制、采样率、startRendering() Promise、AudioBuffer 数据摘要 |
终端值走 fingerprint-value-replay.md;不要用 Node 音频模拟库猜值 |
DOMRect / layout dimensions |
getBoundingClientRect()、getClientRects()、offset/client/scroll 尺寸、对象可枚举性 |
按目标元素和调用栈采样;DOMRect 原型链、只读属性、toJSON 行为要真实 |
CSSStyleDeclaration / getComputedStyle / matchMedia |
属性名、索引、length、media query 结果、CSS.supports |
使用真实浏览器样本;不要只返回空对象或固定字符串 |
navigator.mediaDevices / WebRTC |
enumerateDevices()、getUserMedia() 错误模式、RTCPeerConnection 候选摘要 |
不暴露宿主 Node;需要权限或设备时记录降级,不伪造敏感设备信息 |
screen / viewport / DPR |
width/height/avail*、colorDepth、devicePixelRatio、窗口尺寸关系 |
必须与取证工具 viewport、最终请求 UA / Client Hints 和 fingerprint baseline 一致 |
常见高风险 WebAPI 的 addon 覆盖规则
以下对象在真实项目中很容易被临时写成普通函数或普通对象,但只要进入补环境范围,就必须从第一版实现开始执行 addon-first、原型链、descriptor、访问器、Symbol.toStringTag、非法构造行为和 native-like 保护。
禁止把以下写法作为主路径:
ctx.Blob = function Blob() {};
ctx.screen = { width: 1920, height: 1080 };
ctx.indexedDB = { open() {} };
ctx.URL.createObjectURL = function createObjectURL() {};
HTMLCanvasElement.prototype = { getContext() {} };
Object.assign(ctx, { history: { back() {} } });
ctx.TextEncoder = globalThis.TextEncoder;
正确方向:
- 构造函数优先由
addon.createProtoChains(descriptors)或addon.createNativeFunction(true, name, length, callback)创建。 - 普通方法优先由
addon.createNativeFunction(false, name, length, callback)创建,并通过Object.defineProperty挂到正确 prototype 或静态对象上。 - getter / setter 优先由
addon.createGetter/addon.createSetter创建。 - 实例对象优先由
createProtoChains的实例工厂创建,禁止直接{}。 - 内部状态优先
addon.setPrivate/getPrivate,fallback 才用WeakMap。 - 只有 addon 缺失、ABI 不兼容、API 调用失败或用户明确豁免时,才允许
NativeProtect/ JS fallback,并记录到 notes、阶段报告和最终总结。
重点对象清单:
| 对象 / API | 必须补齐的最低结构 |
|---|---|
screen / Screen / ScreenOrientation |
Screen 构造函数、Screen.prototype、screen instanceof Screen、Object.prototype.toString.call(screen)、width/height/availWidth/availHeight/colorDepth/pixelDepth descriptor;orientation 需要 ScreenOrientation 原型链和 type/angle 访问器。 |
Blob / File |
构造函数可 new,Blob.prototype / File.prototype、size/type/name/lastModified descriptor、slice/text/arrayBuffer/stream native-like 方法;不可直接返回普通对象。 |
FormData |
构造函数可 new,FormData.prototype.append/delete/get/getAll/has/set/entries/keys/values/forEach 均用 addon native-like 方法,内部 entries 用私有状态保存。 |
Event / CustomEvent / MessageEvent |
构造函数、继承链、type/bubbles/cancelable/defaultPrevented/detail/data/origin/source descriptor,方法 preventDefault/stopPropagation/stopImmediatePropagation native-like。 |
XMLHttpRequest |
EventTarget → XMLHttpRequestEventTarget → XMLHttpRequest 链路,构造函数可 new,open/send/abort/setRequestHeader/getResponseHeader/getAllResponseHeaders native-like,readyState/status/responseText/onreadystatechange descriptor。 |
indexedDB / IDBFactory / IDBOpenDBRequest / IDBKeyRange |
indexedDB 必须是 IDBFactory 实例;open/deleteDatabase/cmp/databases 用 addon native-like 方法;IDBKeyRange.only/lowerBound/upperBound/bound 为静态 native-like 方法;请求对象要有 IDBRequest / IDBOpenDBRequest 原型链。 |
URL.createObjectURL / URL.revokeObjectURL |
作为 URL 静态方法用 descriptor 安装,函数体用 createNativeFunction,不要直接赋值普通 function;URL 本体不要盲目透传 Node 宿主构造器。 |
CSS.supports / CSS.escape |
CSS 不能是普通对象字面量;静态方法必须 native-like,必要时建立 CSS 命名空间对象的 Symbol.toStringTag 与 descriptor。 |
MutationObserver / IntersectionObserver / ResizeObserver |
构造函数可 new,实例方法 observe/unobserve/disconnect/takeRecords native-like;回调与 records 可按 fixture 最小实现。 |
BroadcastChannel / MessageChannel / MessagePort |
构造函数与 MessagePort 原型链,postMessage/start/close/addEventListener/removeEventListener native-like,port1/port2 是 MessagePort 实例。 |
| Canvas / WebGL 上下文 | HTMLCanvasElement、CanvasRenderingContext2D、WebGLRenderingContext / WebGL2RenderingContext 原型链,getContext/toDataURL/getImageData/measureText/getParameter/readPixels 等终端 API 用 addon native-like 包装并按指纹 fixture 回放。 |
AudioContext / OfflineAudioContext |
构造函数、BaseAudioContext 链路、createAnalyser/createOscillator/decodeAudioData/startRendering native-like,指纹输出从采样值回放。 |
Image / HTMLImageElement / Worker |
不要用普通 function 临时返回普通对象;需要 HTMLElement → HTMLImageElement、EventTarget → Worker 链路,postMessage/terminate/addEventListener native-like。 |
直接复用 Node 宿主对象也属于风险写法。TextEncoder、TextDecoder、URL、URLSearchParams、fetch、Headers、Request、Response、WebAssembly、Streams、Events、crypto 等如果参与目标检测,不能简单写成 ctx.X = globalThis.X;必须按浏览器样本和目标调用范围建立可控实现,或明确记录不可用原因。
MutationObserver 行为最低要求
如果目标实际调用 MutationObserver,不能只补构造函数空壳。至少按 fixture 支持:
new MutationObserver(callback)的参数校验。observe(target, options)、disconnect()、takeRecords()。attributes、attributeOldValue、attributeFilter的最小行为。- DOM 属性变化后 callback 的异步触发顺序。
MutationRecord的type、target、attributeName、oldValue。- 参数错误时的错误类型、message 和 stack 首行按浏览器采样。
媒体能力最低要求
如果目标访问媒体能力,优先采样并回放:
HTMLMediaElement.prototype.canPlayType对 mp4、webm、ogg、hls 等 MIME / codecs 的返回值:""、"maybe"、"probably"。navigator.mediaSession的对象结构、descriptor、Object.prototype.toString和相关方法。AudioContext/OfflineAudioContext指纹输出仍按真实浏览器采样值回放。
交付前必须运行:
node scripts/check_webapi_addon_coverage.js --case-dir case --markdown
该检查失败时,不得交付最终项目;应先把普通 WebAPI 函数、普通对象、prototype 对象字面量和宿主透传迁移为 addon-first 实现。
fetch 与 XMLHttpRequest
补环境阶段默认不应让目标 JS 真的发网络请求。
策略:
- 如果目标 JS 只构造请求或计算签名,
fetch/XMLHttpRequest可以记录调用并返回离线 fixture。 fetch、Headers、Request、Response、XMLHttpRequest一旦进入补环境范围,仍要建立构造函数、原型链、方法、访问器、descriptor 和 native-like 行为。- 不要直接透传 Node 宿主
fetch/ undici,也不要把最终验证交给浏览器自动化。 - 如果必须访问网络,先确认用户授权和访问范围;最终真实请求应由已确认的 Node.js / Python TLS 指纹兼容客户端完成。
- 不要把补环境 runner 变成批量请求工具。
原型链
原型链不是最后补的附加项,而是每个对象进入补环境范围时的第一步。
以下内容默认要考虑:
navigator instanceof Navigator
document instanceof Document
Object.getPrototypeOf(navigator)
navigator.constructor.name
Object.prototype.toString.call(navigator)
Object.getOwnPropertyDescriptor(Navigator.prototype, 'userAgent')
Function.prototype.toString.call(Object.getOwnPropertyDescriptor(Navigator.prototype, 'userAgent').get)
基础链路示例:
EventTarget → Node → Document → HTMLDocument
EventTarget → XMLHttpRequestEventTarget → XMLHttpRequest
HTMLElement → HTMLCanvasElement
优先用 addon createProtoChains(descriptors) 一次性定义构造函数、父级、实例工厂、Symbol.toStringTag、只读 prototype 和不可变原型设置。只有 addon 不可用时,才用 JS Object.setPrototypeOf / Object.create fallback,并用 NativeProtect 做函数和实例保护。
不要为了“完整”一次性补所有 DOM。只补 RuyiTrace / Node trace / fixtures / 目标检测证明目标 JS 会访问或依赖的部分;但已补的部分必须完整真实性。