reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,133 @@
// 基础 env 模板:提供网页端最小对象,并按描述符、原型链和 native-like 规则固化。
// 复制到 case/result 或 case/env 后,必须根据 fixtures 和 RuyiTrace 证据调整具体字段。
'use strict';
let protect = null;
try { protect = require('./native-protect'); } catch { protect = null; }
function defineValue(obj, key, value, options = {}) {
if (protect && protect.defineValue) return protect.defineValue(obj, key, value, options);
Object.defineProperty(obj, key, {
value,
writable: options.writable ?? false,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return value;
}
function defineGetter(obj, key, getter, options = {}) {
Object.defineProperty(obj, key, {
get: getter,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return getter;
}
function markNativeFunction(fn, name) {
if (protect && protect.markNativeFunction) return protect.markNativeFunction(fn, name);
return fn;
}
function markObjectToString(obj, tag) {
if (protect && protect.markObjectToString) return protect.markObjectToString(obj, tag);
try { Object.defineProperty(obj, Symbol.toStringTag, { value: tag, enumerable: false, configurable: true }); } catch {}
return obj;
}
function defineNativeGetter(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeGetter) return protect.defineNativeGetter(obj, key, impl, options, addon);
return defineGetter(obj, key, impl, options);
}
function defineNativeValue(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeValue) return protect.defineNativeValue(obj, key, impl, options, addon);
return defineValue(obj, key, impl, { writable: true, ...options });
}
function createNativeConstructor(name, length, impl, addon) {
if (protect && protect.createNativeConstructor) return protect.createNativeConstructor(name, length, impl, addon);
function Ctor(...args) { return impl.call(this, new.target ? true : false, ...args); }
try { Object.defineProperty(Ctor, 'name', { value: name, configurable: true }); } catch {}
return markNativeFunction(Ctor, name);
}
function throwIllegalConstructor(name, addon) {
if (protect && protect.throwIllegalConstructor) return protect.throwIllegalConstructor(name, addon);
throw new TypeError(`Failed to construct '${name}': Illegal constructor`);
}
function installBaseEnv(globalObject, fixture = {}, options = {}) {
const addon = options.addon || null;
const browser = fixture.browser || {};
const pageUrl = fixture.pageUrl || fixture.apiUrl || 'https://example.com/';
const url = new URL(pageUrl);
const Window = createNativeConstructor('Window', 0, function Window(isNew) {
return throwIllegalConstructor('Window', addon);
}, addon);
const Location = createNativeConstructor('Location', 0, function Location(isNew) {
return throwIllegalConstructor('Location', addon);
}, addon);
const Navigator = createNativeConstructor('Navigator', 0, function Navigator(isNew) {
return throwIllegalConstructor('Navigator', addon);
}, addon);
defineValue(Window.prototype, 'constructor', Window, { writable: true, enumerable: false, configurable: true });
defineValue(Location.prototype, 'constructor', Location, { writable: true, enumerable: false, configurable: true });
defineValue(Navigator.prototype, 'constructor', Navigator, { writable: true, enumerable: false, configurable: true });
markObjectToString(Window.prototype, 'Window');
markObjectToString(Location.prototype, 'Location');
markObjectToString(Navigator.prototype, 'Navigator');
const location = Object.create(Location.prototype);
const locationState = {
href: url.href,
origin: url.origin,
protocol: url.protocol,
host: url.host,
hostname: url.hostname,
port: url.port,
pathname: url.pathname,
search: url.search,
hash: url.hash,
};
for (const key of ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash']) {
defineNativeGetter(Location.prototype, key, function () { return locationState[key]; }, { enumerable: true, configurable: true, name: key }, addon);
}
defineNativeValue(Location.prototype, 'toString', function toString() { return this.href; }, { enumerable: false, configurable: true, writable: true, name: 'toString', length: 0 }, addon);
markObjectToString(location, 'Location');
const navigator = Object.create(Navigator.prototype);
const navState = {
userAgent: browser.userAgent || '',
language: browser.language || 'zh-CN',
languages: browser.languages || ['zh-CN', 'zh'],
platform: browser.platform || 'Win32',
hardwareConcurrency: browser.hardwareConcurrency ?? 8,
deviceMemory: browser.deviceMemory ?? 8,
webdriver: browser.webdriver ?? false,
cookieEnabled: browser.cookieEnabled ?? true,
};
for (const key of Object.keys(navState)) {
defineNativeGetter(Navigator.prototype, key, function () { return navState[key]; }, { enumerable: true, configurable: true, name: key }, addon);
}
markObjectToString(navigator, 'Navigator');
defineValue(globalObject, 'Window', Window, { writable: true, enumerable: false, configurable: true });
defineValue(globalObject, 'Location', Location, { writable: true, enumerable: false, configurable: true });
defineValue(globalObject, 'Navigator', Navigator, { writable: true, enumerable: false, configurable: true });
defineValue(globalObject, 'window', globalObject, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'self', globalObject, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'top', globalObject, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'parent', globalObject, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'location', location, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'navigator', navigator, { writable: false, enumerable: true, configurable: true });
markObjectToString(globalObject, 'Window');
return { Window, Location, Navigator, location, navigator };
}
module.exports = { installBaseEnv };

View File

@@ -0,0 +1,177 @@
// Document 模块模板:提供最小 document / DOM 原型链,并优先用 addon.createUndetectable 处理 document.all。
'use strict';
let protect = null;
try { protect = require('./native-protect'); } catch { protect = null; }
function defineValue(obj, key, value, options = {}) {
if (protect && protect.defineValue) return protect.defineValue(obj, key, value, options);
Object.defineProperty(obj, key, {
value,
writable: options.writable ?? false,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return value;
}
function markNativeFunction(fn, name) {
if (protect && protect.markNativeFunction) return protect.markNativeFunction(fn, name);
return fn;
}
function markObjectToString(obj, tag) {
if (protect && protect.markObjectToString) return protect.markObjectToString(obj, tag);
try { Object.defineProperty(obj, Symbol.toStringTag, { value: tag, enumerable: false, configurable: true }); } catch {}
return obj;
}
function defineNativeGetter(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeGetter) return protect.defineNativeGetter(obj, key, impl, options, addon);
Object.defineProperty(obj, key, { get: impl, enumerable: options.enumerable ?? true, configurable: options.configurable ?? true });
return impl;
}
function defineNativeSetter(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeSetter) return protect.defineNativeSetter(obj, key, impl, options, addon);
Object.defineProperty(obj, key, { set: impl, enumerable: options.enumerable ?? true, configurable: options.configurable ?? true });
return impl;
}
function defineNativeValue(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeValue) return protect.defineNativeValue(obj, key, impl, options, addon);
return defineValue(obj, key, impl, { writable: true, ...options });
}
function createNativeConstructor(name, length, impl, addon) {
if (protect && protect.createNativeConstructor) return protect.createNativeConstructor(name, length, impl, addon);
function Ctor(...args) { return impl.call(this, new.target ? true : false, ...args); }
try { Object.defineProperty(Ctor, 'name', { value: name, configurable: true }); } catch {}
return markNativeFunction(Ctor, name);
}
function throwIllegalConstructor(name, addon) {
if (protect && protect.throwIllegalConstructor) return protect.throwIllegalConstructor(name, addon);
throw new TypeError(`Failed to construct '${name}': Illegal constructor`);
}
function throwConstructorRequiresNew(name, addon) {
if (protect && protect.throwConstructorRequiresNew) return protect.throwConstructorRequiresNew(name, addon);
throw new TypeError(`Failed to construct '${name}': Please use the 'new' operator, this DOM object constructor cannot be called as a function.`);
}
function installDocumentAll(document, addon) {
let value;
let exact = false;
if (protect && typeof protect.createUndetectable === 'function') {
value = protect.createUndetectable(function documentAll() { return undefined; }, addon);
exact = value !== undefined;
} else if (addon && typeof addon.createUndetectable === 'function') {
try {
value = addon.createUndetectable(function documentAll() { return undefined; });
exact = true;
} catch {}
}
if (!exact) value = undefined;
Object.defineProperty(document, 'all', {
value,
writable: false,
enumerable: false,
configurable: true,
});
return { exact, value };
}
function installDocumentEnv(globalObject, fixture = {}, options = {}) {
const addon = options.addon || null;
const pageUrl = fixture.pageUrl || fixture.apiUrl || (globalObject.location && globalObject.location.href) || 'https://example.com/';
const url = new URL(pageUrl);
const cookieJar = new Map();
const initialCookie = (fixture.request && fixture.request.cookie) || fixture.cookie || '';
if (initialCookie) {
for (const pair of String(initialCookie).split(';')) {
const idx = pair.indexOf('=');
if (idx > 0) cookieJar.set(pair.slice(0, idx).trim(), pair.slice(idx + 1).trim());
}
}
const EventTarget = createNativeConstructor('EventTarget', 0, function EventTarget(isNew) {
if (!isNew) return throwConstructorRequiresNew('EventTarget', addon);
}, addon);
const Node = createNativeConstructor('Node', 0, function Node(isNew) {
return throwIllegalConstructor('Node', addon);
}, addon);
const Document = createNativeConstructor('Document', 0, function Document(isNew) {
return throwIllegalConstructor('Document', addon);
}, addon);
const HTMLDocument = createNativeConstructor('HTMLDocument', 0, function HTMLDocument(isNew) {
return throwIllegalConstructor('HTMLDocument', addon);
}, addon);
const Element = createNativeConstructor('Element', 0, function Element(isNew) {
return throwIllegalConstructor('Element', addon);
}, addon);
const HTMLElement = createNativeConstructor('HTMLElement', 0, function HTMLElement(isNew) {
return throwIllegalConstructor('HTMLElement', addon);
}, addon);
const HTMLHtmlElement = createNativeConstructor('HTMLHtmlElement', 0, function HTMLHtmlElement(isNew) {
return throwIllegalConstructor('HTMLHtmlElement', addon);
}, addon);
for (const ctor of [EventTarget, Node, Document, HTMLDocument, Element, HTMLElement, HTMLHtmlElement]) {
defineValue(ctor.prototype, 'constructor', ctor, { writable: true, enumerable: false, configurable: true });
markObjectToString(ctor.prototype, ctor.name);
}
Object.setPrototypeOf(Node.prototype, EventTarget.prototype);
Object.setPrototypeOf(Document.prototype, Node.prototype);
Object.setPrototypeOf(HTMLDocument.prototype, Document.prototype);
Object.setPrototypeOf(Element.prototype, Node.prototype);
Object.setPrototypeOf(HTMLElement.prototype, Element.prototype);
Object.setPrototypeOf(HTMLHtmlElement.prototype, HTMLElement.prototype);
const document = Object.create(HTMLDocument.prototype);
const documentElement = Object.create(HTMLHtmlElement.prototype);
markObjectToString(document, 'HTMLDocument');
markObjectToString(documentElement, 'HTMLHtmlElement');
defineNativeGetter(Document.prototype, 'URL', function () { return url.href; }, { enumerable: true, configurable: true, name: 'URL' }, addon);
defineNativeGetter(Document.prototype, 'documentURI', function () { return url.href; }, { enumerable: true, configurable: true, name: 'documentURI' }, addon);
defineNativeGetter(Document.prototype, 'referrer', function () { return fixture.referrer || ''; }, { enumerable: true, configurable: true, name: 'referrer' }, addon);
defineNativeGetter(Document.prototype, 'hidden', function () { return false; }, { enumerable: true, configurable: true, name: 'hidden' }, addon);
defineNativeGetter(Document.prototype, 'visibilityState', function () { return 'visible'; }, { enumerable: true, configurable: true, name: 'visibilityState' }, addon);
defineNativeGetter(Document.prototype, 'documentElement', function () { return documentElement; }, { enumerable: true, configurable: true, name: 'documentElement' }, addon);
defineNativeGetter(Document.prototype, 'defaultView', function () { return globalObject.window || globalObject; }, { enumerable: true, configurable: true, name: 'defaultView' }, addon);
const getCookie = function () {
return Array.from(cookieJar.entries()).map(([k, v]) => `${k}=${v}`).join('; ');
};
const setCookie = function (value) {
const first = String(value || '').split(';')[0];
const idx = first.indexOf('=');
if (idx > 0) cookieJar.set(first.slice(0, idx).trim(), first.slice(idx + 1).trim());
};
const cookieGetter = protect && protect.createNativeGetter ? protect.createNativeGetter('cookie', getCookie, addon) : getCookie;
const cookieSetter = protect && protect.createNativeSetter ? protect.createNativeSetter('cookie', setCookie, addon) : setCookie;
Object.defineProperty(Document.prototype, 'cookie', { get: cookieGetter, set: cookieSetter, enumerable: true, configurable: true });
defineNativeValue(Document.prototype, 'querySelector', function querySelector() { return null; }, { enumerable: true, configurable: true, writable: true, name: 'querySelector', length: 1 }, addon);
defineNativeValue(Document.prototype, 'getElementById', function getElementById() { return null; }, { enumerable: true, configurable: true, writable: true, name: 'getElementById', length: 1 }, addon);
defineNativeValue(Document.prototype, 'createElement', function createElement(tagName) {
const el = Object.create(HTMLElement.prototype);
defineValue(el, 'tagName', String(tagName || '').toUpperCase(), { writable: false, enumerable: true, configurable: true });
defineValue(el, 'nodeName', String(tagName || '').toUpperCase(), { writable: false, enumerable: true, configurable: true });
markObjectToString(el, 'HTMLElement');
return el;
}, { enumerable: true, configurable: true, writable: true, name: 'createElement', length: 1 }, addon);
const allStatus = installDocumentAll(document, addon);
for (const ctor of [EventTarget, Node, Document, HTMLDocument, Element, HTMLElement, HTMLHtmlElement]) {
defineValue(globalObject, ctor.name, ctor, { writable: true, enumerable: false, configurable: true });
}
defineValue(globalObject, 'document', document, { writable: false, enumerable: true, configurable: true });
return { document, documentElement, allStatus, EventTarget, Node, Document, HTMLDocument, Element, HTMLElement, HTMLHtmlElement };
}
module.exports = { installDocumentEnv, installDocumentAll };

View File

@@ -0,0 +1,349 @@
// 指纹值回放模板:用于 Canvas / WebGL / DOM 几何等终端 API 的真实浏览器采样值回放。
// 使用前复制到 case/result/src/env/ 中,并接入 case/fixtures/fingerprint.fixture.json。
// 目标:回放真实浏览器最终返回值,而不是在 Node.js 中真实模拟渲染。
'use strict';
let native = {};
try { native = require('./native-protect'); } catch {}
const defineValue = native.defineValue || function defineValue(obj, key, value, options = {}) {
Object.defineProperty(obj, key, {
value,
writable: options.writable ?? true,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return value;
};
const defineNativeValue = native.defineNativeValue || function defineNativeValue(obj, key, impl, options = {}) {
return defineValue(obj, key, impl, options);
};
const defineNativeGetter = native.defineNativeGetter || function defineNativeGetter(obj, key, impl, options = {}) {
Object.defineProperty(obj, key, {
get: impl,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return impl;
};
const markObjectToString = native.markObjectToString || function markObjectToString(obj, tag) {
try {
Object.defineProperty(obj, Symbol.toStringTag, {
value: tag,
enumerable: false,
configurable: true,
});
} catch {}
return obj;
};
function stackText() {
try { return String(new Error().stack || ''); } catch { return ''; }
}
function b64ToBytes(value) {
if (!value) return new Uint8ClampedArray();
const buf = Buffer.from(String(value), 'base64');
return new Uint8ClampedArray(buf.buffer, buf.byteOffset, buf.byteLength);
}
function includesStack(match, stack) {
if (!match || !match.stackIncludes) return true;
const needles = Array.isArray(match.stackIncludes) ? match.stackIncludes : [match.stackIncludes];
return needles.some(s => stack.includes(String(s)));
}
function matchScalar(expected, actual) {
if (expected === undefined) return true;
return String(expected) === String(actual);
}
function findReplay(list, ctx, options = {}) {
const items = Array.isArray(list) ? list : [];
const stack = ctx.stack || stackText();
for (const item of items) {
const match = item.match || {};
let ok = includesStack(match, stack);
for (const [key, expected] of Object.entries(match)) {
if (key === 'stackIncludes') continue;
ok = ok && matchScalar(expected, ctx[key]);
}
if (ok) return item.result;
}
if (options.strict !== false) {
const brief = Object.fromEntries(Object.entries(ctx).filter(([k]) => k !== 'stack'));
throw new Error(`缺少浏览器指纹回放样本:${ctx.api || 'unknown'} ${JSON.stringify(brief)}`);
}
return undefined;
}
function createImageData(result) {
const width = Number(result && result.width) || 0;
const height = Number(result && result.height) || 0;
const data = result && result.dataBase64 ? b64ToBytes(result.dataBase64) : new Uint8ClampedArray(width * height * 4);
return markObjectToString({ width, height, data }, 'ImageData');
}
function ensureConstructors(g, options = {}) {
const addon = options.addon;
if (!g.HTMLCanvasElement) {
const HTMLCanvasElement = native.createNativeConstructor ? native.createNativeConstructor('HTMLCanvasElement', 0, function HTMLCanvasElement() {}, addon) : function HTMLCanvasElement() {};
defineValue(g, 'HTMLCanvasElement', HTMLCanvasElement, { enumerable: false, configurable: true });
}
if (!g.CanvasRenderingContext2D) {
const CanvasRenderingContext2D = native.createNativeConstructor ? native.createNativeConstructor('CanvasRenderingContext2D', 0, function CanvasRenderingContext2D() {}, addon) : function CanvasRenderingContext2D() {};
defineValue(g, 'CanvasRenderingContext2D', CanvasRenderingContext2D, { enumerable: false, configurable: true });
}
if (!g.WebGLRenderingContext) {
const WebGLRenderingContext = native.createNativeConstructor ? native.createNativeConstructor('WebGLRenderingContext', 0, function WebGLRenderingContext() {}, addon) : function WebGLRenderingContext() {};
defineValue(g, 'WebGLRenderingContext', WebGLRenderingContext, { enumerable: false, configurable: true });
}
if (!g.DOMRect) {
const DOMRect = native.createNativeConstructor ? native.createNativeConstructor('DOMRect', 4, function DOMRect(isNew, x = 0, y = 0, width = 0, height = 0) {
if (!isNew && !(this instanceof DOMRect)) return new g.DOMRect(x, y, width, height);
defineValue(this, 'x', Number(x), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'y', Number(y), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'width', Number(width), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'height', Number(height), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'top', Number(y), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'left', Number(x), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'right', Number(x) + Number(width), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'bottom', Number(y) + Number(height), { enumerable: true, writable: false, configurable: true });
markObjectToString(this, 'DOMRect');
}, addon) : function DOMRect(x = 0, y = 0, width = 0, height = 0) {
defineValue(this, 'x', Number(x), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'y', Number(y), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'width', Number(width), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'height', Number(height), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'top', Number(y), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'left', Number(x), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'right', Number(x) + Number(width), { enumerable: true, writable: false, configurable: true });
defineValue(this, 'bottom', Number(y) + Number(height), { enumerable: true, writable: false, configurable: true });
markObjectToString(this, 'DOMRect');
};
defineValue(g, 'DOMRect', DOMRect, { enumerable: false, configurable: true });
}
for (const Ctor of [g.HTMLCanvasElement, g.CanvasRenderingContext2D, g.WebGLRenderingContext, g.DOMRect]) {
try {
defineValue(Ctor.prototype, 'constructor', Ctor, { enumerable: false, writable: true, configurable: true });
} catch {}
}
try { markObjectToString(g.HTMLCanvasElement.prototype, 'HTMLCanvasElement'); } catch {}
try { markObjectToString(g.CanvasRenderingContext2D.prototype, 'CanvasRenderingContext2D'); } catch {}
try { markObjectToString(g.WebGLRenderingContext.prototype, 'WebGLRenderingContext'); } catch {}
}
function create2DContext(g, canvas, state) {
const ctx = Object.create(g.CanvasRenderingContext2D.prototype);
ctx.__canvas = canvas;
ctx.__state = state || { font: '10px sans-serif' };
markObjectToString(ctx, 'CanvasRenderingContext2D');
return ctx;
}
function createWebGLContext(g, canvas, state) {
const gl = Object.create(g.WebGLRenderingContext.prototype);
gl.__canvas = canvas;
gl.__state = state || {};
markObjectToString(gl, 'WebGLRenderingContext');
return gl;
}
function installCanvasReplay(g, fixture, options) {
const canvasFx = fixture.canvas || {};
const proto = g.HTMLCanvasElement.prototype;
const ctx2dProto = g.CanvasRenderingContext2D.prototype;
defineNativeValue(proto, 'getContext', function getContext(type) {
const kind = String(type || '').toLowerCase();
if (kind === '2d') return this.__ctx2d || (this.__ctx2d = create2DContext(g, this, { font: '10px sans-serif' }));
if (kind === 'webgl' || kind === 'experimental-webgl' || kind === 'webgl2') return this.__webgl || (this.__webgl = createWebGLContext(g, this, {}));
return null;
}, { name: 'getContext', length: 1, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'toDataURL', function toDataURL(type = 'image/png', quality) {
const result = findReplay(canvasFx.toDataURL, {
api: 'canvas.toDataURL',
width: this.width,
height: this.height,
type,
quality,
stack: stackText(),
}, options);
return result === undefined ? '' : String(result);
}, { name: 'toDataURL', length: 0, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'toBlob', function toBlob(callback, type = 'image/png', quality) {
const result = findReplay(canvasFx.toBlob, {
api: 'canvas.toBlob',
width: this.width,
height: this.height,
type,
quality,
stack: stackText(),
}, { ...options, strict: options.strictToBlob ?? options.strict });
if (typeof callback === 'function') {
const blob = markObjectToString({ type, size: result && result.size ? Number(result.size) : 0, __replay: result }, 'Blob');
callback(blob);
}
}, { name: 'toBlob', length: 1, enumerable: true, configurable: true }, options.addon);
defineNativeGetter(ctx2dProto, 'font', function getFont() {
return this.__state && this.__state.font || '10px sans-serif';
}, { name: 'font', enumerable: true, configurable: true }, options.addon);
Object.defineProperty(ctx2dProto, 'font', {
get: Object.getOwnPropertyDescriptor(ctx2dProto, 'font').get,
set: function setFont(value) { this.__state.font = String(value); },
enumerable: true,
configurable: true,
});
defineNativeValue(ctx2dProto, 'measureText', function measureText(text) {
const result = findReplay(canvasFx.measureText, {
api: 'canvas.measureText',
text: String(text),
font: this.font,
stack: stackText(),
}, options) || { width: 0 };
return markObjectToString({ ...result }, 'TextMetrics');
}, { name: 'measureText', length: 1, enumerable: true, configurable: true }, options.addon);
defineNativeValue(ctx2dProto, 'getImageData', function getImageData(sx, sy, sw, sh) {
const result = findReplay(canvasFx.getImageData, {
api: 'canvas.getImageData',
sx, sy, sw, sh,
stack: stackText(),
}, options);
return createImageData(result || { width: sw, height: sh });
}, { name: 'getImageData', length: 4, enumerable: true, configurable: true }, options.addon);
for (const name of ['fillText', 'strokeText', 'drawImage', 'fillRect', 'clearRect', 'rect', 'arc', 'beginPath', 'closePath', 'stroke', 'fill', 'putImageData']) {
if (!ctx2dProto[name]) {
defineNativeValue(ctx2dProto, name, function noop() {}, { name, length: 0, enumerable: true, configurable: true }, options.addon);
}
}
}
function installWebGLReplay(g, fixture, options) {
const webglFx = fixture.webgl || {};
const proto = g.WebGLRenderingContext.prototype;
defineNativeValue(proto, 'getParameter', function getParameter(pname) {
return findReplay(webglFx.getParameter, { api: 'webgl.getParameter', pname, stack: stackText() }, options);
}, { name: 'getParameter', length: 1, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'getSupportedExtensions', function getSupportedExtensions() {
const configured = webglFx.getSupportedExtensions;
if (configured && Array.isArray(configured.result)) return configured.result.slice();
return findReplay(webglFx.getSupportedExtensions, { api: 'webgl.getSupportedExtensions', stack: stackText() }, options) || [];
}, { name: 'getSupportedExtensions', length: 0, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'getExtension', function getExtension(name) {
const configured = webglFx.getExtension;
const result = findReplay(configured, { api: 'webgl.getExtension', name: String(name), stack: stackText() }, { ...options, strict: false });
return result === undefined ? null : markObjectToString({ name: String(name), ...result }, String(name));
}, { name: 'getExtension', length: 1, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'getShaderPrecisionFormat', function getShaderPrecisionFormat(shaderType, precisionType) {
const result = findReplay(webglFx.getShaderPrecisionFormat, {
api: 'webgl.getShaderPrecisionFormat',
shaderType,
precisionType,
stack: stackText(),
}, options) || { rangeMin: 0, rangeMax: 0, precision: 0 };
return markObjectToString({ ...result }, 'WebGLShaderPrecisionFormat');
}, { name: 'getShaderPrecisionFormat', length: 2, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'readPixels', function readPixels(x, y, width, height, format, type, pixels) {
const result = findReplay(webglFx.readPixels, {
api: 'webgl.readPixels',
x, y, width, height, format, type,
stack: stackText(),
}, options);
if (pixels && result && result.dataBase64) {
const bytes = b64ToBytes(result.dataBase64);
for (let i = 0; i < Math.min(pixels.length, bytes.length); i++) pixels[i] = bytes[i];
}
}, { name: 'readPixels', length: 7, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'getContextAttributes', function getContextAttributes() {
return { alpha: true, antialias: true, depth: true, desynchronized: false, failIfMajorPerformanceCaveat: false, powerPreference: 'default', premultipliedAlpha: true, preserveDrawingBuffer: false, stencil: false, xrCompatible: false };
}, { name: 'getContextAttributes', length: 0, enumerable: true, configurable: true }, options.addon);
}
function installDomGeometryReplay(g, fixture, options) {
const domFx = fixture.domGeometry || {};
if (!g.Element) return;
const proto = g.Element.prototype;
function selectorOf(el) {
return el.__fingerprintSelector || el.id && `#${el.id}` || el.className && `.${String(el.className).split(/\s+/)[0]}` || String(el.tagName || '').toLowerCase();
}
defineNativeValue(proto, 'getBoundingClientRect', function getBoundingClientRect() {
const result = findReplay(domFx.getBoundingClientRect, {
api: 'dom.getBoundingClientRect',
selector: selectorOf(this),
stack: stackText(),
}, options) || {};
return new g.DOMRect(result.x || result.left || 0, result.y || result.top || 0, result.width || 0, result.height || 0);
}, { name: 'getBoundingClientRect', length: 0, enumerable: true, configurable: true }, options.addon);
defineNativeValue(proto, 'getClientRects', function getClientRects() {
const rect = this.getBoundingClientRect();
return markObjectToString({ 0: rect, length: 1, item(i) { return i === 0 ? rect : null; } }, 'DOMRectList');
}, { name: 'getClientRects', length: 0, enumerable: true, configurable: true }, options.addon);
for (const key of ['offsetWidth', 'offsetHeight', 'scrollWidth', 'scrollHeight', 'clientWidth', 'clientHeight']) {
defineNativeGetter(proto, key, function geometryGetter() {
const result = findReplay(domFx.offset, {
api: `dom.${key}`,
selector: selectorOf(this),
stack: stackText(),
}, { ...options, strict: false });
if (result && result[key] !== undefined) return Number(result[key]);
const rect = this.getBoundingClientRect();
return /height/i.test(key) ? Math.round(rect.height || 0) : Math.round(rect.width || 0);
}, { name: key, enumerable: true, configurable: true }, options.addon);
}
}
function createCanvasElement(globalObj, width = 300, height = 150) {
const g = globalObj || globalThis;
ensureConstructors(g, options);
const canvas = Object.create(g.HTMLCanvasElement.prototype);
defineValue(canvas, 'width', Number(width), { enumerable: true, writable: true, configurable: true });
defineValue(canvas, 'height', Number(height), { enumerable: true, writable: true, configurable: true });
markObjectToString(canvas, 'HTMLCanvasElement');
return canvas;
}
function installFingerprintValueReplay(globalObj, fixture = {}, options = {}) {
const g = globalObj || globalThis;
const replayOptions = { strict: true, ...options };
ensureConstructors(g, replayOptions);
installCanvasReplay(g, fixture, replayOptions);
installWebGLReplay(g, fixture, replayOptions);
installDomGeometryReplay(g, fixture, replayOptions);
if (!g.document) return { createCanvasElement: (w, h) => createCanvasElement(g, w, h) };
if (g.document && typeof g.document.createElement === 'function' && !g.document.__fingerprintCreateElementPatched) {
const rawCreateElement = g.document.createElement;
defineNativeValue(g.document, 'createElement', function createElement(tagName, optionsArg) {
const tag = String(tagName || '').toLowerCase();
if (tag === 'canvas') return createCanvasElement(g);
return rawCreateElement.call(this, tagName, optionsArg);
}, { name: 'createElement', length: 1, enumerable: true, configurable: true }, replayOptions.addon);
defineValue(g.document, '__fingerprintCreateElementPatched', true, { enumerable: false, configurable: true });
}
return { createCanvasElement: (w, h) => createCanvasElement(g, w, h) };
}
module.exports = {
installFingerprintValueReplay,
createCanvasElement,
findReplay,
};

View File

@@ -0,0 +1,92 @@
// Storage 模块模板:按 fixture 注入 localStorage / sessionStorage,并固化原型链、描述符和 native-like 方法。
'use strict';
let protect = null;
try { protect = require('./native-protect'); } catch { protect = null; }
function defineValue(obj, key, value, options = {}) {
if (protect && protect.defineValue) return protect.defineValue(obj, key, value, options);
Object.defineProperty(obj, key, {
value,
writable: options.writable ?? false,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return value;
}
function markNativeFunction(fn, name) {
if (protect && protect.markNativeFunction) return protect.markNativeFunction(fn, name);
return fn;
}
function markObjectToString(obj, tag) {
if (protect && protect.markObjectToString) return protect.markObjectToString(obj, tag);
try { Object.defineProperty(obj, Symbol.toStringTag, { value: tag, enumerable: false, configurable: true }); } catch {}
return obj;
}
function defineNativeGetter(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeGetter) return protect.defineNativeGetter(obj, key, impl, options, addon);
Object.defineProperty(obj, key, { get: impl, enumerable: options.enumerable ?? true, configurable: options.configurable ?? true });
return impl;
}
function defineNativeValue(obj, key, impl, options = {}, addon) {
if (protect && protect.defineNativeValue) return protect.defineNativeValue(obj, key, impl, options, addon);
return defineValue(obj, key, impl, { writable: true, ...options });
}
function createNativeConstructor(name, length, impl, addon) {
if (protect && protect.createNativeConstructor) return protect.createNativeConstructor(name, length, impl, addon);
function Ctor(...args) { return impl.call(this, new.target ? true : false, ...args); }
try { Object.defineProperty(Ctor, 'name', { value: name, configurable: true }); } catch {}
return markNativeFunction(Ctor, name);
}
function throwIllegalConstructor(name, addon) {
if (protect && protect.throwIllegalConstructor) return protect.throwIllegalConstructor(name, addon);
throw new TypeError(`Failed to construct '${name}': Illegal constructor`);
}
function installStorage(globalObject, fixture = {}, options = {}) {
const addon = options.addon || null;
const request = fixture.request || {};
const Storage = createNativeConstructor('Storage', 0, function Storage(isNew) {
return throwIllegalConstructor('Storage', addon);
}, addon);
defineValue(Storage.prototype, 'constructor', Storage, { writable: true, enumerable: false, configurable: true });
markObjectToString(Storage.prototype, 'Storage');
defineNativeGetter(Storage.prototype, 'length', function () { return this.__data.size; }, { enumerable: true, configurable: true, name: 'length' }, addon);
defineNativeValue(Storage.prototype, 'key', function key(index) { return Array.from(this.__data.keys())[Number(index)] ?? null; }, { enumerable: true, configurable: true, writable: true, name: 'key', length: 1 }, addon);
defineNativeValue(Storage.prototype, 'getItem', function getItem(key) { key = String(key); return this.__data.has(key) ? this.__data.get(key) : null; }, { enumerable: true, configurable: true, writable: true, name: 'getItem', length: 1 }, addon);
defineNativeValue(Storage.prototype, 'setItem', function setItem(key, value) { this.__data.set(String(key), String(value)); }, { enumerable: true, configurable: true, writable: true, name: 'setItem', length: 2 }, addon);
defineNativeValue(Storage.prototype, 'removeItem', function removeItem(key) { this.__data.delete(String(key)); }, { enumerable: true, configurable: true, writable: true, name: 'removeItem', length: 1 }, addon);
defineNativeValue(Storage.prototype, 'clear', function clear() { this.__data.clear(); }, { enumerable: true, configurable: true, writable: true, name: 'clear', length: 0 }, addon);
function createStorage(initial = {}) {
const data = new Map(Object.entries(initial).map(([k, v]) => [String(k), String(v)]));
const storage = Object.create(Storage.prototype);
defineValue(storage, '__data', data, { enumerable: false, configurable: false, writable: false });
markObjectToString(storage, 'Storage');
return storage;
}
const localStorage = createStorage(request.localStorage || fixture.localStorage || {});
const sessionStorage = createStorage(request.sessionStorage || fixture.sessionStorage || {});
defineValue(globalObject, 'Storage', Storage, { writable: true, enumerable: false, configurable: true });
defineValue(globalObject, 'localStorage', localStorage, { writable: false, enumerable: true, configurable: true });
defineValue(globalObject, 'sessionStorage', sessionStorage, { writable: false, enumerable: true, configurable: true });
return { Storage, localStorage, sessionStorage, createStorage };
}
function createStorage(initial = {}, options = {}) {
const context = {};
return installStorage(context, { localStorage: initial }, options).localStorage;
}
module.exports = { createStorage, installStorage };