Files

1070 lines
40 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// addon-first native-like 保护工具。
// 1) 创建函数、getter、setter、document.all、构造函数、原型链、集合对象和插件列表时,优先尝试 addon API。
// 2) addon.node 缺失、ABI 不兼容或调用失败时,才降级到 NativeProtect / JS fallback。
// 3) 本模块自动尝试加载随 Skill 携带的 addon.node,并记录 usedApis / fallbacks,便于写入 notes 和最终总结。
// 注意:复制到 case/result 时不要写死本机绝对路径,优先使用相对 assets/native-addon 或 WEB_JS_ENV_PATCHER_ADDON。
'use strict';
let fs = null;
let path = null;
try { fs = require('fs'); path = require('path'); } catch {}
class NativeProtect {
#map = new Map();
#objMap = new Map();
static #instance = null;
static getInstance() {
if (!NativeProtect.#instance) {
NativeProtect.#instance = new NativeProtect();
const rawFunctionToString = Function.prototype.toString;
const patchedFunctionToString = {
toString() {
if (NativeProtect.#instance.#map.has(this)) {
const name = NativeProtect.#instance.#map.get(this);
return `function ${name || this.name}() { [native code] }`;
}
return rawFunctionToString.call(this);
}
}.toString;
Object.defineProperty(Function.prototype, 'toString', {
value: patchedFunctionToString,
writable: true,
enumerable: false,
configurable: true,
});
NativeProtect.#instance.#map.set(Function.prototype.toString, 'toString');
const rawObjectToString = Object.prototype.toString;
const patchedObjectToString = {
toString() {
if (NativeProtect.#instance.#objMap.has(this)) {
const name = NativeProtect.#instance.#objMap.get(this);
return `[object ${name}]`;
}
return rawObjectToString.call(this);
}
}.toString;
Object.defineProperty(Object.prototype, 'toString', {
value: patchedObjectToString,
writable: true,
enumerable: false,
configurable: true,
});
NativeProtect.#instance.#map.set(Object.prototype.toString, 'toString');
}
return NativeProtect.#instance;
}
constructor() {
if (NativeProtect.#instance) throw new Error('NativeProtect 类只能实例化一次');
}
setNativeFunc(func, name = '') { this.#map.set(func, name); }
setObjFunc(obj, name = '') { this.#objMap.set(obj, name); }
}
function getNativeProtect() {
return NativeProtect.getInstance();
}
function markNativeFunction(func, name = '') {
if (typeof func === 'function') getNativeProtect().setNativeFunc(func, name || func.name || '');
return func;
}
function markObjectToString(obj, tag = '') {
if (!obj || !tag) return obj;
try {
Object.defineProperty(obj, Symbol.toStringTag, {
value: tag,
writable: false,
enumerable: false,
configurable: true,
});
} catch {}
getNativeProtect().setObjFunc(obj, tag);
return obj;
}
const nativeAddonUsage = {
available: false,
path: '',
autoLoadAttempted: false,
usedApis: [],
fallbacks: [],
attempts: [],
};
let cachedNativeAddon = null;
let nativeAddonResolved = false;
const privateStore = new WeakMap();
function addCandidate(out, p) {
if (!p || !path) return;
try { out.push(path.resolve(p)); } catch { out.push(String(p)); }
}
function addAddonFileCandidates(out, base, platformArch) {
if (!base) return;
if (platformArch) {
addCandidate(out, path.join(base, platformArch, 'addon.node'));
addCandidate(out, path.join(base, platformArch, `addon-${platformArch}.node`));
}
addCandidate(out, path.join(base, 'addon.node'));
}
function nativeAddonCandidates(extra) {
const out = [];
if (Array.isArray(extra)) for (const p of extra) addCandidate(out, p);
else if (extra) addCandidate(out, extra);
const hasProcess = typeof process !== 'undefined' && process && process.versions;
const platformArch = hasProcess ? `${process.platform}-${process.arch}` : '';
if (hasProcess && process.env && process.env.WEB_JS_ENV_PATCHER_ADDON) addCandidate(out, process.env.WEB_JS_ENV_PATCHER_ADDON);
if (path && hasProcess && process.cwd) {
addAddonFileCandidates(out, path.join(process.cwd(), 'assets', 'native-addon'), platformArch);
addAddonFileCandidates(out, path.join(process.cwd(), 'native-addon'), platformArch);
}
if (path && typeof __dirname !== 'undefined') {
const bases = [
path.join(__dirname, 'native-addon'),
path.join(__dirname, 'assets', 'native-addon'),
path.join(__dirname, '..', 'native-addon'),
path.join(__dirname, '..', 'assets', 'native-addon'),
path.join(__dirname, '..', '..', 'native-addon'),
path.join(__dirname, '..', '..', 'assets', 'native-addon'),
path.join(__dirname, '..', '..', '..', 'native-addon'),
path.join(__dirname, '..', '..', '..', 'assets', 'native-addon'),
];
for (const base of bases) addAddonFileCandidates(out, base, platformArch);
}
return [...new Set(out)];
}
function tryRequireAddon(p) {
try {
if (fs && !fs.existsSync(p)) {
nativeAddonUsage.attempts.push({ path: p, ok: false, reason: '文件不存在' });
return null;
}
const addon = require(p);
nativeAddonUsage.available = true;
nativeAddonUsage.path = p;
nativeAddonUsage.attempts.push({ path: p, ok: true, reason: '' });
return addon;
} catch (err) {
nativeAddonUsage.attempts.push({ path: p, ok: false, reason: err && err.message ? err.message : String(err) });
return null;
}
}
function loadNativeAddon(options = {}) {
const hasExplicit = !!(options.addon || options.path || options.candidates);
if (nativeAddonResolved && !options.force && !hasExplicit) return cachedNativeAddon;
nativeAddonUsage.autoLoadAttempted = true;
for (const p of nativeAddonCandidates(options.addon || options.path || options.candidates)) {
const addon = tryRequireAddon(p);
if (addon) {
cachedNativeAddon = addon;
nativeAddonResolved = true;
return addon;
}
}
if (!hasExplicit || !cachedNativeAddon) nativeAddonResolved = true;
return cachedNativeAddon;
}
function normalizeAddon(addonLike, allowAutoLoad = true) {
if (addonLike && typeof addonLike === 'object' && Object.prototype.hasOwnProperty.call(addonLike, 'available') && addonLike.available === false) return null;
if (addonLike && typeof addonLike === 'object' && Object.prototype.hasOwnProperty.call(addonLike, 'addon')) return normalizeAddon(addonLike.addon, false);
if (addonLike && (typeof addonLike === 'object' || typeof addonLike === 'function')) return addonLike;
if (allowAutoLoad) return cachedNativeAddon || loadNativeAddon();
return null;
}
function setNativeAddon(addonLike) {
const addon = normalizeAddon(addonLike, false);
cachedNativeAddon = addon;
nativeAddonResolved = true;
nativeAddonUsage.available = !!addon;
nativeAddonUsage.path = addonLike && addonLike.path || nativeAddonUsage.path || '';
if (addonLike && Array.isArray(addonLike.attempts)) nativeAddonUsage.attempts.push(...addonLike.attempts);
return addon;
}
function getNativeAddon() {
return normalizeAddon(null, true);
}
function getAddonApi(addonLike, apiName) {
const addon = normalizeAddon(addonLike, true);
if (addon && typeof addon[apiName] === 'function') {
nativeAddonUsage.available = true;
return addon[apiName].bind(addon);
}
return null;
}
function isAddonLike(value) {
return !!(
value &&
(typeof value === 'object' || typeof value === 'function') &&
(
typeof value.createNativeFunction === 'function' ||
typeof value.createProtoChains === 'function' ||
typeof value.getMimeTypesAndPlugins === 'function' ||
Object.prototype.hasOwnProperty.call(value, 'addon')
)
);
}
function recordAddonUse(apiName) {
if (!nativeAddonUsage.usedApis.includes(apiName)) nativeAddonUsage.usedApis.push(apiName);
}
function recordAddonFallback(apiName, reason) {
nativeAddonUsage.fallbacks.push({ api: apiName, reason: reason || 'addon 不可用,使用 JS fallback' });
}
function getNativeAddonUsage() {
return {
available: nativeAddonUsage.available,
path: nativeAddonUsage.path,
autoLoadAttempted: nativeAddonUsage.autoLoadAttempted,
usedApis: nativeAddonUsage.usedApis.slice(),
fallbacks: nativeAddonUsage.fallbacks.slice(),
attempts: nativeAddonUsage.attempts.slice(),
};
}
function 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 defineSetter(obj, key, setter, options = {}) {
Object.defineProperty(obj, key, {
set: setter,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
return setter;
}
function setFunctionMeta(fn, name, length) {
try { if (name) Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
try { if (Number.isFinite(length)) Object.defineProperty(fn, 'length', { value: length, configurable: true }); } catch {}
return fn;
}
function createNativeFunction(name, length, impl, addon) {
const api = getAddonApi(addon, 'createNativeFunction');
if (api) {
try {
const fn = api(false, name, length ?? impl.length ?? 0, impl);
recordAddonUse('createNativeFunction');
return fn;
} catch (err) {
recordAddonFallback('createNativeFunction', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createNativeFunction', 'addon 不可用,使用 NativeProtect fallback');
}
return markNativeFunction(setFunctionMeta(impl, name, length ?? impl.length ?? 0), name);
}
function createNativeConstructor(name, length, impl, addon) {
const api = getAddonApi(addon, 'createNativeFunction');
if (api) {
try {
const ctor = api(true, name, length ?? 0, impl);
recordAddonUse('createNativeFunction');
return ctor;
} catch (err) {
recordAddonFallback('createNativeFunction', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createNativeFunction', 'addon 不可用,构造函数使用 NativeProtect fallback');
}
const ctor = function (...args) {
return impl.call(this, new.target ? true : false, ...args);
};
return markNativeFunction(setFunctionMeta(ctor, name, length ?? 0), name);
}
function createNativeGetter(name, impl, addon) {
const api = getAddonApi(addon, 'createGetter');
if (api) {
try {
const getter = api(name, 0, impl);
recordAddonUse('createGetter');
return getter;
} catch (err) {
recordAddonFallback('createGetter', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createGetter', 'addon 不可用,getter 使用 NativeProtect fallback');
}
return markNativeFunction(setFunctionMeta(impl, `get ${name}`, 0), `get ${name}`);
}
function createNativeSetter(name, impl, addon) {
const api = getAddonApi(addon, 'createSetter');
if (api) {
try {
const setter = api(name, 1, impl);
recordAddonUse('createSetter');
return setter;
} catch (err) {
recordAddonFallback('createSetter', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createSetter', 'addon 不可用,setter 使用 NativeProtect fallback');
}
return markNativeFunction(setFunctionMeta(impl, `set ${name}`, 1), `set ${name}`);
}
const UNDETECTABLE_HANDLER_NAMES = new Set(['getter', 'setter', 'query', 'deleter', 'enumerator', 'definer', 'descriptor']);
function splitAddonAndHandlers(second, third) {
let addon = null;
let handlers = null;
if (second && typeof second === 'object' && (second.addon || second.handlers)) {
addon = second.addon || null;
handlers = second.handlers || third || null;
} else if (second && typeof second === 'object' && [...UNDETECTABLE_HANDLER_NAMES].some(k => typeof second[k] === 'function')) {
handlers = second;
addon = third || null;
} else if (second && typeof second === 'object' && isAddonLike(third)) {
handlers = second;
addon = third;
} else {
addon = second || null;
handlers = third || null;
}
return { addon, handlers };
}
function createUndetectable(impl, addonOrHandlers, maybeHandlers) {
const { addon, handlers } = splitAddonAndHandlers(addonOrHandlers, maybeHandlers);
const api = getAddonApi(addon, 'createUndetectable');
if (api) {
try {
const value = handlers ? api(impl, handlers) : api(impl);
recordAddonUse('createUndetectable');
return value;
} catch (err) {
recordAddonFallback('createUndetectable', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createUndetectable', 'addon 不可用,document.all 只能使用近似 fallback');
}
return undefined;
}
function createInterceptor(options = {}, addon) {
const api = getAddonApi(addon || options.addon, 'createInterceptor');
if (api) {
try {
const value = api(options);
recordAddonUse('createInterceptor');
return value;
} catch (err) {
recordAddonFallback('createInterceptor', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createInterceptor', 'addon 不可用,使用 Proxy 近似 fallback');
}
const target = options.target && typeof options.target === 'object' ? options.target : {};
const handlers = options.handlers || {};
const proxy = new Proxy(target, {
get(obj, property, receiver) {
if (typeof handlers.getter === 'function') {
const got = handlers.getter(obj, property);
if (!got || got.intercept !== false) return got && Object.prototype.hasOwnProperty.call(got, 'value') ? got.value : got;
}
return Reflect.get(obj, property, receiver);
},
set(obj, property, value, receiver) {
if (typeof handlers.setter === 'function') {
const got = handlers.setter(obj, property, value);
if (got && got.intercept === true) return Reflect.set(obj, property, got.value, receiver);
if (got && got.intercept === false) return Reflect.set(obj, property, value, receiver);
}
return Reflect.set(obj, property, value, receiver);
},
getOwnPropertyDescriptor(obj, property) {
if (typeof handlers.descriptor === 'function') {
const desc = handlers.descriptor(obj, property);
if (desc) return desc;
}
return Reflect.getOwnPropertyDescriptor(obj, property);
},
ownKeys(obj) {
if (typeof handlers.enumerator === 'function') {
const keys = handlers.enumerator(obj);
if (Array.isArray(keys)) return keys;
}
return Reflect.ownKeys(obj);
},
deleteProperty(obj, property) {
if (typeof handlers.deleter === 'function') return !!handlers.deleter(obj, property);
return Reflect.deleteProperty(obj, property);
},
defineProperty(obj, property, descriptor) {
if (typeof handlers.definer === 'function') {
const got = handlers.definer(obj, property, descriptor);
if (got === false || (got && got.intercept === false)) return false;
}
return Reflect.defineProperty(obj, property, descriptor);
},
});
if (options.internalClassName) markObjectToString(proxy, options.internalClassName);
return proxy;
}
function isDescriptorObject(value) {
return value && typeof value === 'object' && ('value' in value || 'get' in value || 'set' in value);
}
function createNativeObjectFallback(tag, proto, properties = {}) {
const obj = Object.create(proto || Object.prototype);
for (const [key, desc] of Object.entries(properties || {})) {
try {
Object.defineProperty(obj, key, isDescriptorObject(desc) ? desc : {
value: desc,
writable: true,
enumerable: true,
configurable: true,
});
} catch {}
}
if (tag) markObjectToString(obj, tag);
return obj;
}
function createNativeObjectFromOptionsFallback(options = {}) {
const name = options.name || 'NativeObject';
const ctor = createNativeConstructor(name, options.length ?? 0, options.constructor || function () {}, null);
if (options.parent && typeof options.parent === 'object') {
const parent = createNativeObjectFromOptionsFallback(options.parent);
try { Object.setPrototypeOf(ctor.prototype, parent.constructor.prototype); } catch {}
try { Object.setPrototypeOf(ctor, parent.constructor); } catch {}
}
if (options.isReadOnlyPrototype) {
try { Object.defineProperty(ctor, 'prototype', { writable: false }); } catch {}
}
if (options.isImmutableProto) {
try { Object.preventExtensions(ctor.prototype); } catch {}
}
const instance = Object.create(ctor.prototype);
if (typeof options.constructor === 'function') {
try { options.constructor.call(instance, false); } catch {}
}
if (options.isImmutableInstanceProto) {
try { Object.preventExtensions(instance); } catch {}
}
markObjectToString(instance, name);
return { instance, constructor: ctor, prototypeChains: [] };
}
function createNativeObject(arg1, arg2, arg3, arg4) {
const isOptions = arg1 && typeof arg1 === 'object' && !Array.isArray(arg1) && typeof arg1.name === 'string';
const addon = isOptions ? arg2 : arg4;
const api = getAddonApi(addon, 'createNativeObject');
if (isOptions && api) {
try {
const result = api(arg1);
recordAddonUse('createNativeObject');
return result;
} catch (err) {
recordAddonFallback('createNativeObject', err && err.message ? err.message : String(err));
}
} else if (!isOptions) {
recordAddonFallback('createNativeObject', '检测到旧式 createNativeObject(tag, proto, properties) 调用,新代码应迁移为 createNativeObject(options) 或 createProtoChains(descriptors)');
} else {
recordAddonFallback('createNativeObject', 'addon 不可用,使用 JS options fallback');
}
if (isOptions) return createNativeObjectFromOptionsFallback(arg1);
return createNativeObjectFallback(arg1, arg2, arg3 || {});
}
function normalizeProtoChainArgs(arg1, arg2, arg3) {
if (Array.isArray(arg1)) return { descriptors: arg1, addon: arg2, legacy: false };
if (arg1 && typeof arg1 === 'object' && Array.isArray(arg1.descriptors)) return { descriptors: arg1.descriptors, addon: arg1.addon || arg2, legacy: false };
return { descriptors: Array.isArray(arg2) ? arg2 : [], addon: arg3, legacy: true, name: arg1 };
}
function callConstructorForFallback(desc, thisValue, isNew, args) {
if (typeof desc.constructor !== 'function') return undefined;
return desc.constructor.apply(thisValue, [isNew, ...args]);
}
function createProtoChainsFallback(descriptors = []) {
const registry = new Map();
const result = {};
for (const desc of descriptors) {
if (!desc || typeof desc !== 'object' || !desc.name) continue;
if (desc.aliasOf) {
const aliased = registry.get(desc.aliasOf) || result[desc.aliasOf];
if (aliased) {
registry.set(desc.name, aliased);
result[desc.name] = aliased;
}
continue;
}
const name = desc.name;
const Ctor = function (...args) {
const isNew = !!new.target;
const behavior = desc.illegalConstructor
? 'throw'
: (isNew ? desc.constructorBehavior : desc.callBehavior) || 'allow';
if (behavior === 'illegal' || behavior === 'throw') {
const message = isNew
? (desc.constructorErrorMessage || illegalConstructorMessage(name))
: (desc.callErrorMessage || 'Illegal constructor');
return throwBrowserTypeError(message, null);
}
return callConstructorForFallback(desc, this, isNew, args);
};
setFunctionMeta(Ctor, name, desc.length ?? 0);
markNativeFunction(Ctor, name);
try {
Object.defineProperty(Ctor.prototype, 'constructor', {
value: Ctor,
writable: true,
enumerable: false,
configurable: true,
});
} catch {}
const parentName = desc.prototypeParent || desc.parent;
const parentCtor = parentName ? registry.get(parentName) : null;
if (parentCtor) {
try { Object.setPrototypeOf(Ctor.prototype, parentCtor.prototype); } catch {}
}
const constructorParentName = Object.prototype.hasOwnProperty.call(desc, 'constructorParent') ? desc.constructorParent : parentName;
if (constructorParentName) {
const parent = registry.get(constructorParentName);
if (parent) try { Object.setPrototypeOf(Ctor, parent); } catch {}
}
if (desc.hasToStringTag !== false) {
try {
Object.defineProperty(Ctor.prototype, Symbol.toStringTag, {
value: desc.toStringTag || name,
writable: false,
enumerable: false,
configurable: true,
});
} catch {}
}
for (const method of Array.isArray(desc.prototypeMethods) ? desc.prototypeMethods : []) {
if (!method || !method.name || typeof (method.callback || method.value) !== 'function') continue;
defineValue(
Ctor.prototype,
method.name,
createNativeFunction(method.name, method.length ?? 0, method.callback || method.value, null),
{
writable: method.writable ?? true,
enumerable: method.enumerable ?? true,
configurable: method.configurable ?? true,
}
);
}
for (const method of Array.isArray(desc.staticMethods) ? desc.staticMethods : []) {
if (!method || !method.name || typeof (method.callback || method.value) !== 'function') continue;
defineValue(
Ctor,
method.name,
createNativeFunction(method.name, method.length ?? 0, method.callback || method.value, null),
{
writable: method.writable ?? true,
enumerable: method.enumerable ?? true,
configurable: method.configurable ?? true,
}
);
}
if (desc.readOnlyPrototypeProperty || desc.isReadOnlyPrototype) {
try { Object.defineProperty(Ctor, 'prototype', { writable: false }); } catch {}
}
if (desc.immutablePrototypeObject || desc.isImmutableProto) {
try { Object.preventExtensions(Ctor.prototype); } catch {}
}
registry.set(name, Ctor);
result[name] = Ctor;
for (const alias of Array.isArray(desc.aliases) ? desc.aliases : []) {
registry.set(alias, Ctor);
result[alias] = Ctor;
}
if (desc.instanceFactoryName) {
const factory = function (...args) {
const instance = Object.create(Ctor.prototype);
if (typeof desc.instanceInitializer === 'function') desc.instanceInitializer.apply(instance, args);
if (desc.immutableInstancePrototype || desc.isImmutableInstanceProto) {
try { Object.preventExtensions(instance); } catch {}
}
markObjectToString(instance, desc.internalClassName || desc.toStringTag || name);
return instance;
};
setFunctionMeta(factory, desc.instanceFactoryName, 0);
markNativeFunction(factory, desc.instanceFactoryName);
result[desc.instanceFactoryName] = factory;
}
if (desc.isCreateInstance && desc.instanceName) {
const instance = Object.create(Ctor.prototype);
if (typeof desc.instanceInitializer === 'function') desc.instanceInitializer.call(instance);
markObjectToString(instance, desc.internalClassName || desc.toStringTag || name);
result[desc.instanceName] = instance;
}
}
return result;
}
function createProtoChains(arg1, arg2, arg3) {
const { descriptors, addon, legacy } = normalizeProtoChainArgs(arg1, arg2, arg3);
const api = getAddonApi(addon, 'createProtoChains');
if (!legacy && api) {
try {
const result = api(descriptors);
recordAddonUse('createProtoChains');
return result;
} catch (err) {
recordAddonFallback('createProtoChains', err && err.message ? err.message : String(err));
}
} else if (legacy) {
recordAddonFallback('createProtoChains', '检测到旧式 createProtoChains(name, chain) 调用,新代码应迁移为 createProtoChains(descriptors)');
} else {
recordAddonFallback('createProtoChains', 'addon 不可用,使用 JS 构造函数 / 原型链 fallback');
}
if (legacy) {
let current = null;
const created = [];
for (const item of descriptors) {
const proto = Object.create(current || Object.prototype);
if (item && item.name) markObjectToString(proto, item.name);
created.push(proto);
current = proto;
}
return created;
}
return createProtoChainsFallback(descriptors);
}
function getProtoChainRegistry(addon) {
const api = getAddonApi(addon, 'getProtoChainRegistry');
if (api) {
try {
const value = api();
recordAddonUse('getProtoChainRegistry');
return value;
} catch (err) {
recordAddonFallback('getProtoChainRegistry', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('getProtoChainRegistry', 'addon 不可用,无法读取 native 注册表');
}
return { constructors: [], aliases: {} };
}
function deleteProtoChainRegistryEntry(name, addon) {
const api = getAddonApi(addon, 'deleteProtoChainRegistryEntry');
if (api) {
try {
const ok = api(String(name));
recordAddonUse('deleteProtoChainRegistryEntry');
return ok;
} catch (err) {
recordAddonFallback('deleteProtoChainRegistryEntry', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('deleteProtoChainRegistryEntry', 'addon 不可用,无法删除 native 注册表项');
}
return false;
}
function clearProtoChainRegistry(addon) {
const api = getAddonApi(addon, 'clearProtoChainRegistry');
if (api) {
try {
const ok = api();
recordAddonUse('clearProtoChainRegistry');
return ok;
} catch (err) {
recordAddonFallback('clearProtoChainRegistry', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('clearProtoChainRegistry', 'addon 不可用,无法清空 native 注册表');
}
return false;
}
function normalizeCollectionItems(items) {
if (!Array.isArray(items)) return [];
return items.map((item, index) => {
if (item && typeof item === 'object' && Object.prototype.hasOwnProperty.call(item, 'value')) {
return { name: item.name == null ? '' : String(item.name), value: item.value };
}
return { name: item && item.name ? String(item.name) : String(index), value: item };
});
}
function createNativeCollection(options = {}, addon) {
const api = getAddonApi(addon || options.addon, 'createNativeCollection');
if (api) {
try {
const value = api(options);
recordAddonUse('createNativeCollection');
return value;
} catch (err) {
recordAddonFallback('createNativeCollection', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('createNativeCollection', 'addon 不可用,使用最小集合 JS fallback');
}
const name = options.name || 'NativeCollection';
const Ctor = createNativeConstructor(name, 0, function () {
throwTypeError(`Failed to construct '${name}': Illegal constructor`, null);
}, null);
const collection = Object.create(Ctor.prototype);
const items = normalizeCollectionItems(options.items);
const values = items.map(item => item.value);
defineValue(collection, 'length', values.length, { writable: false, enumerable: false, configurable: true });
if (options.indexedAccess !== false) {
values.forEach((value, index) => {
defineValue(collection, String(index), value, { writable: false, enumerable: true, configurable: true });
});
}
if (options.namedAccess !== false) {
for (const item of items) {
if (!item.name) continue;
defineValue(collection, item.name, item.value, {
writable: false,
enumerable: !!options.namedEnumerable,
configurable: true,
});
}
}
if (options.itemMethod !== false) {
defineValue(Ctor.prototype, 'item', createNativeFunction('item', 1, function (index) {
const n = Number(index);
return Number.isInteger(n) && n >= 0 ? (this[String(n)] || null) : null;
}, null), { writable: true, enumerable: false, configurable: true });
}
if (options.namedItemMethod !== false) {
defineValue(Ctor.prototype, 'namedItem', createNativeFunction('namedItem', 1, function (key) {
return this[String(key)] || null;
}, null), { writable: true, enumerable: false, configurable: true });
}
if (options.iterable !== false) {
defineValue(Ctor.prototype, Symbol.iterator, createNativeFunction('values', 0, function () {
return values[Symbol.iterator]();
}, null), { writable: true, enumerable: false, configurable: true });
}
if (options.hasToStringTag !== false) {
try {
Object.defineProperty(Ctor.prototype, Symbol.toStringTag, {
value: options.toStringTag || name,
writable: false,
enumerable: false,
configurable: true,
});
} catch {}
}
markObjectToString(collection, options.internalClassName || options.toStringTag || name);
if (options.immutableInstancePrototype) {
try { Object.preventExtensions(collection); } catch {}
}
return { collection, constructor: Ctor, [name]: Ctor };
}
function normalizeMimePluginArgs(configOrAddon, maybeAddon) {
if (configOrAddon && typeof configOrAddon === 'object' && Object.prototype.hasOwnProperty.call(configOrAddon, 'config')) {
return { config: configOrAddon.config, addon: configOrAddon.addon || maybeAddon };
}
if (isAddonLike(configOrAddon)) return { config: undefined, addon: configOrAddon };
return { config: configOrAddon, addon: maybeAddon };
}
function getMimeTypesAndPlugins(configOrAddon, maybeAddon) {
const { config, addon } = normalizeMimePluginArgs(configOrAddon, maybeAddon);
const api = getAddonApi(addon, 'getMimeTypesAndPlugins');
if (api) {
try {
const result = config === undefined ? api() : api(config);
recordAddonUse('getMimeTypesAndPlugins');
return result;
} catch (err) {
recordAddonFallback('getMimeTypesAndPlugins', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('getMimeTypesAndPlugins', 'addon 不可用,使用最小 PluginArray / MimeTypeArray fallback');
}
const Plugin = createNativeConstructor('Plugin', 0, function () {
throwTypeError("Failed to construct 'Plugin': Illegal constructor", null);
}, null);
const MimeType = createNativeConstructor('MimeType', 0, function () {
throwTypeError("Failed to construct 'MimeType': Illegal constructor", null);
}, null);
const pluginConfigs = config && Array.isArray(config.plugins)
? config.plugins
: [
{ name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', mimeTypes: [{ type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' }] },
{ name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format', mimeTypes: [{ type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format' }] },
];
const pluginItems = [];
const mimeItems = [];
for (const pluginConfig of pluginConfigs) {
const plugin = Object.create(Plugin.prototype);
defineValue(plugin, 'name', String(pluginConfig.name || ''), { writable: false, enumerable: true, configurable: true });
defineValue(plugin, 'filename', String(pluginConfig.filename || ''), { writable: false, enumerable: true, configurable: true });
defineValue(plugin, 'description', String(pluginConfig.description || ''), { writable: false, enumerable: true, configurable: true });
markObjectToString(plugin, 'Plugin');
const localMimes = [];
for (const mimeConfig of Array.isArray(pluginConfig.mimeTypes) ? pluginConfig.mimeTypes : []) {
const mime = Object.create(MimeType.prototype);
defineValue(mime, 'type', String(mimeConfig.type || ''), { writable: false, enumerable: true, configurable: true });
defineValue(mime, 'suffixes', String(mimeConfig.suffixes || ''), { writable: false, enumerable: true, configurable: true });
defineValue(mime, 'description', String(mimeConfig.description || ''), { writable: false, enumerable: true, configurable: true });
defineValue(mime, 'enabledPlugin', plugin, { writable: false, enumerable: true, configurable: true });
markObjectToString(mime, 'MimeType');
localMimes.push(mime);
mimeItems.push({ name: mime.type, value: mime });
}
defineValue(plugin, 'length', localMimes.length, { writable: false, enumerable: false, configurable: true });
localMimes.forEach((mime, index) => defineValue(plugin, String(index), mime, { writable: false, enumerable: true, configurable: true }));
defineValue(plugin, Symbol.iterator, createNativeFunction('values', 0, function () {
return localMimes[Symbol.iterator]();
}, null), { writable: true, enumerable: false, configurable: true });
pluginItems.push({ name: plugin.name, value: plugin });
}
const pluginCollection = createNativeCollection({ name: 'PluginArray', items: pluginItems, toStringTag: 'PluginArray' }, null);
const mimeCollection = createNativeCollection({ name: 'MimeTypeArray', items: mimeItems, toStringTag: 'MimeTypeArray' }, null);
const plugins = pluginCollection.collection;
const mimeTypes = mimeCollection.collection;
const PluginArray = pluginCollection.PluginArray || pluginCollection.constructor;
const MimeTypeArray = mimeCollection.MimeTypeArray || mimeCollection.constructor;
return { mimeTypes, plugins, PluginArray, MimeTypeArray, MimeType, Plugin };
}
function setPrivate(object, key, value, addon) {
const api = getAddonApi(addon, 'setPrivate');
if (api) {
try {
const ok = api(object, String(key), value);
recordAddonUse('setPrivate');
return ok;
} catch (err) {
recordAddonFallback('setPrivate', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('setPrivate', 'addon 不可用,使用 WeakMap fallback');
}
let map = privateStore.get(object);
if (!map) {
map = new Map();
privateStore.set(object, map);
}
map.set(String(key), value);
return true;
}
function getPrivate(object, key, addon) {
const api = getAddonApi(addon, 'getPrivate');
if (api) {
try {
const value = api(object, String(key));
recordAddonUse('getPrivate');
return value;
} catch (err) {
recordAddonFallback('getPrivate', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('getPrivate', 'addon 不可用,使用 WeakMap fallback');
}
const map = privateStore.get(object);
return map ? map.get(String(key)) : undefined;
}
function hasPrivate(object, key, addon) {
const api = getAddonApi(addon, 'hasPrivate');
if (api) {
try {
const ok = api(object, String(key));
recordAddonUse('hasPrivate');
return ok;
} catch (err) {
recordAddonFallback('hasPrivate', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('hasPrivate', 'addon 不可用,使用 WeakMap fallback');
}
const map = privateStore.get(object);
return !!(map && map.has(String(key)));
}
function deletePrivate(object, key, addon) {
const api = getAddonApi(addon, 'deletePrivate');
if (api) {
try {
const ok = api(object, String(key));
recordAddonUse('deletePrivate');
return ok;
} catch (err) {
recordAddonFallback('deletePrivate', err && err.message ? err.message : String(err));
}
} else {
recordAddonFallback('deletePrivate', 'addon 不可用,使用 WeakMap fallback');
}
const map = privateStore.get(object);
return map ? map.delete(String(key)) : false;
}
function throwTypeError(message, addon) {
const api = getAddonApi(addon, 'throwTypeError');
if (api) {
recordAddonUse('throwTypeError');
return api(String(message));
}
throw new TypeError(String(message));
}
function throwBrowserTypeError(message, addon) {
return throwTypeError(String(message), addon);
}
function illegalConstructorMessage(name) {
return `Failed to construct '${String(name)}': Illegal constructor`;
}
function constructorRequiresNewMessage(name) {
return `Failed to construct '${String(name)}': Please use the 'new' operator, this DOM object constructor cannot be called as a function.`;
}
function throwIllegalConstructor(name, addon) {
return throwBrowserTypeError(illegalConstructorMessage(name), addon);
}
function throwConstructorRequiresNew(name, addon) {
return throwBrowserTypeError(constructorRequiresNewMessage(name), addon);
}
function defineNativeValue(obj, key, impl, options = {}, addon) {
const fn = createNativeFunction(options.name || String(key), options.length ?? impl.length, impl, addon);
return defineValue(obj, key, fn, {
writable: options.writable ?? true,
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
});
}
function defineNativeGetter(obj, key, impl, options = {}, addon) {
const getter = createNativeGetter(options.name || String(key), impl, addon);
return defineGetter(obj, key, getter, options);
}
function defineNativeSetter(obj, key, impl, options = {}, addon) {
const setter = createNativeSetter(options.name || String(key), impl, addon);
return defineSetter(obj, key, setter, options);
}
function defineNativeAccessor(obj, key, accessors = {}, options = {}, addon) {
const descriptor = {
enumerable: options.enumerable ?? true,
configurable: options.configurable ?? true,
};
if (typeof accessors.get === 'function') descriptor.get = createNativeGetter(options.getName || String(key), accessors.get, addon);
if (typeof accessors.set === 'function') descriptor.set = createNativeSetter(options.setName || String(key), accessors.set, addon);
Object.defineProperty(obj, key, descriptor);
return descriptor;
}
module.exports = {
NativeProtect,
getNativeProtect,
markNativeFunction,
markObjectToString,
nativeAddonCandidates,
loadNativeAddon,
setNativeAddon,
getNativeAddon,
normalizeAddon,
getAddonApi,
getNativeAddonUsage,
defineValue,
defineGetter,
defineSetter,
createNativeFunction,
createNativeConstructor,
createNativeGetter,
createNativeSetter,
createUndetectable,
createInterceptor,
createNativeObject,
createProtoChains,
getProtoChainRegistry,
deleteProtoChainRegistryEntry,
clearProtoChainRegistry,
createNativeCollection,
getMimeTypesAndPlugins,
setPrivate,
getPrivate,
hasPrivate,
deletePrivate,
throwTypeError,
throwBrowserTypeError,
illegalConstructorMessage,
constructorRequiresNewMessage,
throwIllegalConstructor,
throwConstructorRequiresNew,
defineNativeValue,
defineNativeGetter,
defineNativeSetter,
defineNativeAccessor,
};