// 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, };