Files

46 KiB
Raw Permalink Blame History

native addon API 使用规范

本文件用于网页端 Node.js 补环境阶段。每次进入补环境阶段、修改 native-protect.js / addon helper、创建或修复 WebAPI 对象模型、处理 document.all、navigator.plugins / navigator.mimeTypes、集合对象、私有状态或构造函数行为时必须读取。

选择 isolated-vm 框架时的 xbs 规则

如果用户选择 isolated-vm 补环境框架,必须使用随包魔改 xbs isolated-vm,并读取 references/xbs-isolated-vm-api.md。此时 WebAPI native-first 能力来自 Context 内的 window.xbs / globalThis.xbs,不要把本文件所述旧 addon.node 桥接进 isolated-vm。xbs API 名称与本文件主要 API 基本一致:createNativeFunction、createGetter、createSetter、createProtoChains、createNativeCollection、getMimeTypesAndPlugins、createUndetectable 等;补环境约束仍然是原型链、描述符、访问器、toString、集合对象和特殊对象优先走 native API。

Skill 硬性规则

  1. addon-first 是默认硬性基线:进入补环境阶段先加载 / 记录 addon,可用时优先调用 addon API。ABI 不兼容时不得直接降级;先读取 node-version-recovery.md,提示 addon 兼容 Node.js v25.8.2,检测 nvm 并征得用户同意是否安装 / 切换兼容 Node。只有用户拒绝切换、当前平台缺失、切换后仍失败、明确要求不使用 addon 或 API 调用失败时,才降级为 NativeProtect / JS fallback。
  2. 新代码优先新版 API:构造函数、原型链、别名、实例工厂、原型方法和静态方法优先使用 createProtoChains(descriptors);禁止把旧式 createProtoChains(name, chain) 或 createNativeObject(tag, proto, properties) 写成新代码主路径。
  3. WebAPI 方法必须 native-like:普通方法优先 createNativeFunction(false, name, length, callback);构造函数优先 createProtoChains 或必要时 createNativeFunction(true, ...);getter / setter 优先 createGetter / createSetter。
  4. 集合对象优先 addon 集合 API:HTMLCollection、NodeList、PluginArray、MimeTypeArray、DOMTokenList、StyleSheetList 等浏览器集合对象,addon 可用时优先使用 createNativeCollection 或 getMimeTypesAndPlugins,不要手写普通数组 / 普通对象作为主路径。
  5. plugins / mimeTypes 是强约束:补 navigator.plugins 和 navigator.mimeTypes 时,addon 可用必须优先使用 getMimeTypesAndPlugins([config]);如果真实浏览器样本与内置 PDF 插件数据不同,传入 config 生成样本一致的插件和 MIME 类型。只有 addon 不可用或用户明确禁用 addon 时,才允许最小 JS fallback,并记录差异。
  6. 特殊对象优先 native 能力:document.all 优先 createUndetectable;需要 named / indexed property interceptor 的高级对象可用 createInterceptor;最终能表达为真实构造函数和集合结构时,仍应迁移到真实对象模型。
  7. 内部状态优先 V8 Private:对象内部状态优先使用 setPrivate / getPrivate / hasPrivate / deletePrivate,不要把状态直接挂成可枚举属性;addon 不可用时才用 WeakMap fallback。
  8. 注册表管理只用于隔离和重置:getProtoChainRegistry、deleteProtoChainRegistryEntry、clearProtoChainRegistry 主要用于测试隔离、环境重置或分阶段初始化管理;正式补环境应尽量一次性规划对象模型,避免运行中反复删除注册表。
  9. 错误行为必须采样:constructorBehavior、callBehavior、constructorErrorMessage、callErrorMessage、illegalConstructor 等配置要以目标浏览器采样为准,不要泛化写死 Illegal constructor。
  10. 禁止本机强依赖:Skill 文档和最终产物不得写入本机绝对路径;addon 默认从 assets/native-addon/<platform>-<arch>/addon.node 加载,用户自定义路径只能作为运行参数或环境变量。

WebAPI 默认映射

场景 addon-first API
构造函数、原型链、别名、实例工厂 createProtoChains(descriptors)
普通 WebAPI 方法 createNativeFunction(false, name, length, callback)
getter / setter createGetter(name, 0, callback) / createSetter(name, 1, callback)
document.all / HTMLDDA 近似 createUndetectable(callback, handlers)
navigator.plugins / navigator.mimeTypes getMimeTypesAndPlugins(config)
HTMLCollection / NodeList / 类数组集合 createNativeCollection(options)
named / indexed property interceptor createInterceptor(options)
内部状态 setPrivate / getPrivate / hasPrivate / deletePrivate
构造函数注册表隔离 getProtoChainRegistry / deleteProtoChainRegistryEntry / clearProtoChainRegistry
浏览器 TypeError throwTypeError(message)

降级记录要求

如果发生 NativeProtect / JS fallback,必须在 notes、阶段报告和最终总结写明:

  • addon 加载路径或随包加载结果。
  • 失败 API、错误信息、Node 版本、平台和 ABI。
  • fallback 覆盖范围和已知差异。
  • 是否为用户明确豁免。
  • 如果是 ABI 不兼容,记录是否已提示 nvm + Node.js v25.8.2 恢复流程、用户选择、重新检测结果,以及为何最终仍需 fallback。

native 能力缺口处理

当 addon 可加载但当前 API 无法表达目标浏览器行为时,不得把问题简单降级为 NativeProtect / JS fallback。先确认是否属于 API 用法错误:已有 createProtoChains、createNativeFunction、createGetter、createSetter、createNativeCollection、getMimeTypesAndPlugins、createUndetectable、createInterceptor、private slot、注册表管理或 throwTypeError 可以覆盖时,必须先用这些 API 修复。

确认当前 addon API 仍无法覆盖时,按 references/native-capability-gap.md 输出能力缺口报告。报告中必须给出:

  • 真实浏览器采样行为。
  • 纯 JS fallback 的失败点。
  • 当前 addon API 的失败点或缺失能力。
  • 如果用户选择 isolated-vm,同时给出当前 xbs API 的失败点或缺失能力。
  • 建议新增或增强的 addon API 名称、参数、返回值、浏览器语义和保护点。
  • 最小行为测试用例。用户更新 addon.node 后必须先让该测试通过,才能继续把该点写成已解决。

示例:如果 document.all 需要同时满足 typeof document.all === "undefined"、document.all.length、document.all.item()、Object.prototype.toString.call(document.all) === "[object HTMLAllCollection]",而当前 createUndetectable 只能覆盖部分行为,则输出建议新增 createHTMLDDACollection(options) 或增强 createUndetectable() 的能力,而不是继续用状态化 getter 或普通对象硬凑。


项目对外 API 使用说明

本文档说明当前 Node.js native addon 对外可用 API、常用配置项、返回值、示例和注意事项。文档基于当前项目源码 main.cc、undetect.cc、test.js 与当前构建产物的可见导出整理。

加载方式示例:

const addon = require("./build/Release/addon");

1. API 总览

API 推荐状态 主要用途
createNativeFunction 推荐 创建 native-like 函数或构造函数,Function.prototype.toString 显示为 [native code]。
createGetter 推荐 创建 native-like getter 函数,例如 function get userAgent() { [native code] }。
createSetter 推荐 创建 native-like setter 函数,例如 function set value() { [native code] }。
createNativeObject 兼容 / 旧式 创建单个构造函数、实例对象和嵌套父级原型链。新代码更建议使用 createProtoChains。
createProtoChains 推荐 批量创建构造函数、原型链、构造函数继承、别名、实例工厂、原型方法和静态方法。
getProtoChainRegistry 推荐 查看 createProtoChains 的全局构造函数 / 别名注册表。
deleteProtoChainRegistryEntry 推荐 删除注册表中的构造函数或别名,并清理指向该构造函数的别名。
clearProtoChainRegistry 推荐 清空 createProtoChains 注册表,适合测试隔离或环境重新初始化。
getPrivate 推荐 读取 V8 Private 私有字段。
setPrivate 推荐 写入 V8 Private 私有字段。
hasPrivate 推荐 判断对象上是否存在指定 V8 Private 私有字段。
deletePrivate 推荐 删除对象上的指定 V8 Private 私有字段。
createInterceptor 高级 创建带 V8 named / indexed property interceptor 的对象。
createNativeCollection 推荐 通用浏览器集合对象工厂,可创建 HTMLCollection、NodeList、PluginArray 类似对象。
getMimeTypesAndPlugins 可用 创建浏览器风格 navigator.mimeTypes / navigator.plugins;支持默认数据和参数化配置。
createUndetectable 高级 创建 V8 undetectable 对象,常用于近似 document.all / HTMLDDA 行为。
throwTypeError 辅助 从 native addon 抛出 TypeError。
hello 历史调试 当前源码未导出,正式补环境逻辑不要依赖。

2. 通用注意事项

  1. API 都是 native 函数:导出函数自身的 Function.prototype.toString.call(api) 通常为 function xxx() { [native code] }。
  2. 参数类型要严格:多数 API 在参数类型不满足时会直接不返回结果或抛错,调用前建议自行校验。
  3. 构造回调参数差异:
    • createProtoChains / createNativeObject 的构造回调总会把 isNew 作为第一个参数传入。
    • createNativeFunction(true, ...) 只有在 new 调用时才把 isNew 作为第一个参数传入;普通调用不会自动补这个布尔值。
  4. 回调参数已按实际长度传递:createProtoChains / createNativeFunction 内部会按调用参数数量动态构造参数数组;仍建议 WebAPI 形参保持和真实浏览器 .length 一致。
  5. 命名全局注册:createProtoChains 内部维护构造函数模板与别名注册表,同名构造函数会跨调用复用。请使用稳定且不冲突的名称;测试或重新初始化时可用注册表管理 API 清理。

3. createNativeFunction(isConstructor, name, length, callback)

创建一个 native-like 函数或构造函数。

参数

参数 类型 必填 说明
isConstructor boolean 是 true 表示允许 new,false 表示不可构造。
name string 是 函数名 / 构造函数名。
length number 是 函数形参长度,会影响 .length。
callback function 是 实际执行逻辑。

行为

  • isConstructor === true:
    • 允许 new Fn()。
    • 构造调用时,callback 的第一个参数是 isNew === true。
    • 构造调用最终返回 this,即使 callback 返回其他值。
    • 普通调用 Fn() 也允许,但不会自动传入 isNew。
  • isConstructor === false:
    • 只能普通调用。
    • new fn() 会抛出不可构造相关错误。

示例

const XBS = addon.createNativeFunction(true, "XBS", 0, function (isNew) {
  if (isNew) this.value = 111;
});

console.log(new XBS().value); // 111
console.log(XBS.toString());  // function XBS() { [native code] }

const add = addon.createNativeFunction(false, "add", 2, function (a, b) {
  return a + b;
});

console.log(add(1, 2));       // 3
console.log(add.toString());  // function add() { [native code] }

4. createGetter(name, length, callback)

创建 native-like getter 函数。

参数

参数 类型 必填 说明
name string 是 getter 对应属性名。
length number 是 当前实现会校验该参数,但实际 getter .length 固定为 0。
callback function 是 getter 实际逻辑。this 为访问该属性的对象。

示例

class Navigator {}

Object.defineProperty(Navigator.prototype, "userAgent", {
  get: addon.createGetter("userAgent", 0, function () {
    if (!(this instanceof Navigator)) {
      addon.throwTypeError("Illegal invocation");
    }
    return "Mozilla/5.0";
  }),
  enumerable: true,
  configurable: true,
});

const getter = Object.getOwnPropertyDescriptor(Navigator.prototype, "userAgent").get;
console.log(getter.toString()); // function get userAgent() { [native code] }

5. createSetter(name, length, callback)

创建 native-like setter 函数。

参数

参数 类型 必填 说明
name string 是 setter 对应属性名。
length number 是 当前实现会校验该参数,但实际 setter .length 固定为 1。
callback function 是 setter 实际逻辑。this 为访问该属性的对象。

示例

const state = new WeakMap();
class Location {}

Object.defineProperty(Location.prototype, "href", {
  get: addon.createGetter("href", 0, function () {
    return state.get(this) || "";
  }),
  set: addon.createSetter("href", 1, function (value) {
    state.set(this, String(value));
  }),
  enumerable: true,
  configurable: true,
});

6. createNativeObject(options)

旧式单对象创建 API。它会创建一个构造函数、一个实例对象,并可通过嵌套 parent 创建父级构造函数和原型链。

配置项

字段 类型 默认值 说明
name string 无 构造函数名,必填。
length number 无 构造函数 .length,必填。
constructor function 无 构造回调,必填。第一个参数固定为 isNew。
isReadOnlyPrototype boolean false 使构造函数 .prototype 属性只读。
isImmutableProto boolean false 使构造函数的 prototype 对象原型不可更改。
isImmutableInstanceProto boolean false 使创建出的实例对象原型不可更改。
parent object 无 父级描述符,结构与当前 options 类似,可继续嵌套。

返回值

{
  instance: object,
  constructor: Function,
  prototypeChains?: Function[]
}

示例

const result = addon.createNativeObject({
  name: "Window",
  length: 0,
  constructor(isNew) {
    if (isNew) throw new TypeError("Failed to construct 'Window': Illegal constructor");
    throw new TypeError("Illegal constructor");
  },
  isReadOnlyPrototype: true,
  isImmutableProto: true,
  isImmutableInstanceProto: true,
  parent: {
    name: "EventTarget",
    length: 0,
    constructor(isNew) {
      if (!isNew) throw new TypeError("Please use the 'new' operator");
    },
    isReadOnlyPrototype: true,
    isImmutableProto: true,
  },
});

const window = result.instance;
const Window = result.constructor;
console.log(window instanceof Window); // true

建议

新功能优先使用 createProtoChains,因为它支持字符串父级引用、构造函数父级、跨调用复用、别名和实例工厂。

7. createProtoChains(descriptors)

批量创建构造函数、原型链和实例相关对象,是当前推荐的构造函数 / 原型链 API。

基本调用

const chain = addon.createProtoChains([
  {
    name: "EventTarget",
    length: 0,
    constructor(isNew) {
      if (!isNew) {
        throw new TypeError(
          "Failed to construct 'EventTarget': Please use the 'new' operator"
        );
      }
    },
  },
  {
    name: "Node",
    length: 0,
    constructor(isNew) {},
    prototypeParent: "EventTarget",
  },
]);

const { EventTarget, Node } = chain;
console.log(new Node() instanceof EventTarget); // true

普通构造函数描述符

字段 类型 默认值 说明
name string 无 构造函数名,必填。
constructor function 无 构造回调,必填。第一个参数固定为 isNew。
length number 0 构造函数 .length。
prototypeParent string 无 原型父级。设置后会使 Child.prototype -> Parent.prototype。
parent string 无 prototypeParent 的旧字段别名。
constructorParent string / null 默认同 prototypeParent 构造函数对象自身的父级。设置后会使 Child -> Parent,用于静态属性继承。传 null 可显式禁用默认继承。
readOnlyPrototypeProperty boolean false 使构造函数 .prototype 属性只读。
isReadOnlyPrototype boolean false readOnlyPrototypeProperty 的旧字段别名。
immutablePrototypeObject boolean false 使 Constructor.prototype 的 [[Prototype]] 不可更改。
isImmutableProto boolean false immutablePrototypeObject 的旧字段别名。
immutableInstancePrototype boolean false 使通过模板创建的实例对象 [[Prototype]] 不可更改。
isImmutableInstanceProto boolean false immutableInstancePrototype 的旧字段别名。
hasToStringTag boolean true 是否在 Constructor.prototype 上设置 Symbol.toStringTag。
toStringTag string name Symbol.toStringTag 的值。
internalClassName string 无 写入 addon 内部类型名。即使没有 Symbol.toStringTag,也能让 addon 创建的实例在 Object.prototype.toString.call(instance) 中显示指定标签。
constructorBehavior "allow" / "illegal" / "throw" "allow" 控制 new Constructor() 的 native 层行为。illegal 与 throw 都会在进入 JS 回调前抛出 TypeError。
callBehavior "allow" / "illegal" / "throw" "allow" 控制 Constructor() 直接调用的 native 层行为。
constructorErrorMessage string 自动生成 constructorBehavior 抛错时使用的错误信息。
callErrorMessage string "Illegal constructor" callBehavior 抛错时使用的错误信息。
illegalConstructor boolean false 便捷配置;为 true 时同时把 constructorBehavior 和 callBehavior 设置为 "illegal"。
prototypeMethods NativeMember[] [] 批量定义到 Constructor.prototype 的 native-like 方法。
staticMethods NativeMember[] [] 批量定义到构造函数自身的 native-like 静态方法。
aliases string[] [] 为当前构造函数注册一个或多个别名,别名导出的是同一个函数对象。
instanceFactoryName string 无 创建实例工厂函数的导出名。用于公开构造函数不能 new,但内部仍要创建实例的场景。
instanceInitializer function 无 factory 创建实例后调用,this 指向实例,factory 参数会原样传入。必须配合 instanceFactoryName 使用。
isCreateInstance boolean false 旧式立即创建实例开关。
instanceName string 无 旧式实例导出名,配合 isCreateInstance: true 使用。

alias-only 描述符

用于给已有构造函数注册别名。

字段 类型 必填 说明
name string 是 别名导出名。
aliasOf string 是 指向的真实构造函数名或已有别名。

alias-only 描述符不能定义 constructor、length、父级、不可变选项、toStringTag、aliases、实例或 factory 相关字段。

返回值

返回一个普通对象,包含:

  1. 所有本次创建或解析到的构造函数:result[name]。
  2. 所有可导出的别名:result[alias],值与真实构造函数严格相等。
  3. 使用旧式 isCreateInstance + instanceName 创建的实例。
  4. 使用 instanceFactoryName 创建的 factory 函数。

prototypeParent 与 constructorParent

const { Base, Child } = addon.createProtoChains([
  { name: "Base", length: 0, constructor() {} },
  { name: "Child", length: 0, constructor() {}, prototypeParent: "Base" },
]);

Object.getPrototypeOf(Child.prototype) === Base.prototype; // true
Object.getPrototypeOf(Child) === Base;                     // true,因为 constructorParent 默认同 prototypeParent
  • prototypeParent 控制实例链:new Child() instanceof Base。
  • constructorParent 控制构造函数对象链:Child.__proto__ === Base,用于静态属性继承。
  • 如果只想继承实例链,不想继承构造函数对象链:
addon.createProtoChains([
  { name: "Base", length: 0, constructor() {} },
  {
    name: "Child",
    length: 0,
    constructor() {},
    prototypeParent: "Base",
    constructorParent: null,
  },
]);

只读与不可变选项区别

选项 影响对象 效果
readOnlyPrototypeProperty 构造函数的 .prototype 属性 .prototype 属性不可写。
immutablePrototypeObject Constructor.prototype 对象 不能通过 Object.setPrototypeOf(Constructor.prototype, ...) 改它的原型。
immutableInstancePrototype 创建出的实例对象 不能通过 Object.setPrototypeOf(instance, ...) 改实例原型。

Symbol.toStringTag

默认会设置:

Constructor.prototype[Symbol.toStringTag] === name;
Object.prototype.toString.call(new Constructor()); // [object name]

关闭:

{ name: "NoTag", constructor() {}, hasToStringTag: false }

自定义:

{ name: "URL", constructor() {}, toStringTag: "URL" }

内部 [[Class]] / 无 Symbol.toStringTag 标签

有些浏览器对象没有暴露 Symbol.toStringTag,但 Object.prototype.toString.call(obj) 仍然需要返回指定标签。此时可以关闭 hasToStringTag,并使用 internalClassName:

const { createDocument } = addon.createProtoChains([
  {
    name: "Document",
    constructor() {},
    hasToStringTag: false,
    internalClassName: "Document",
    instanceFactoryName: "createDocument",
  },
]);

const document = createDocument();
console.log(document[Symbol.toStringTag]);              // undefined
console.log(Object.prototype.toString.call(document));  // [object Document]

实现注意:

  • internalClassName 使用 V8 Private 保存,不会成为普通 JS 可枚举属性。
  • addon 会安装 native-like 的 Object.prototype.toString 包装函数;普通对象、null、undefined 会回退原始行为。
  • 如果同时设置了 Symbol.toStringTag 和 internalClassName,实际检测结果应以目标浏览器样本为准,建议二选一保持结构清晰。

构造函数行为模式

constructorBehavior 和 callBehavior 用于在 native 层提前控制公开构造函数能否被 new 或直接调用。适合“公开构造函数不可用,但内部 factory 可以创建实例”的 DOM 场景。

const { Document, createDocument } = addon.createProtoChains([
  {
    name: "Document",
    constructor() {
      throw new Error("不会执行到这里");
    },
    constructorBehavior: "throw",
    constructorErrorMessage: "Failed to construct 'Document': Illegal constructor",
    callBehavior: "throw",
    callErrorMessage: "Illegal constructor",
    instanceFactoryName: "createDocument",
  },
]);

try { new Document(); } catch (err) { console.log(err.message); }
try { Document(); } catch (err) { console.log(err.message); }

const document = createDocument();
console.log(document instanceof Document); // true

行为说明:

  • allow:允许调用并进入 constructor JS 回调。
  • illegal / throw:在 native 层抛 TypeError,不会执行 constructor JS 回调。
  • illegalConstructor: true 是快捷写法,等价于公开构造和直接调用都走非法构造路径。
  • 错误信息建议来自目标浏览器采样,不要随意泛化。

批量定义原型方法和静态方法

prototypeMethods 与 staticMethods 的元素结构如下:

字段 类型 默认值 说明
name string 无 方法名。
callback function 无 实际执行逻辑。也兼容字段名 value。
length number 0 方法 .length。
writable boolean true descriptor 的 writable。
enumerable boolean true descriptor 的 enumerable。
configurable boolean true descriptor 的 configurable。
const { URL } = addon.createProtoChains([
  {
    name: "URL",
    constructor(isNew, href) {
      if (isNew) this.href = String(href);
    },
    prototypeMethods: [
      {
        name: "toJSON",
        length: 0,
        enumerable: false,
        callback() {
          return this.href;
        },
      },
    ],
    staticMethods: [
      {
        name: "canParse",
        length: 1,
        callback(value) {
          return typeof value === "string" && value.length > 0;
        },
      },
    ],
  },
]);

const url = new URL("https://example.com/");
console.log(url.toJSON());       // https://example.com/
console.log(URL.canParse("/"));  // true
console.log(url.toJSON.toString()); // function toJSON() { [native code] }

别名:aliases 与 aliasOf

适合模拟 URL / webkitURL 这类同一构造函数多个名字的场景。

const { URL, webkitURL } = addon.createProtoChains([
  {
    name: "URL",
    aliases: ["webkitURL"],
    length: 0,
    constructor() {},
  },
]);

console.log(webkitURL === URL); // true
console.log(webkitURL.name);    // URL

也可以单独注册:

const first = addon.createProtoChains([
  { name: "URL", length: 0, constructor() {} },
]);

const second = addon.createProtoChains([
  { name: "webkitURL", aliasOf: "URL" },
]);

console.log(second.webkitURL === first.URL); // true

实例工厂:instanceFactoryName / instanceInitializer

用于“公开构造函数不能被 new,但内部仍需要创建一个合法实例”的场景。

const { Document, createDocument } = addon.createProtoChains([
  {
    name: "Document",
    length: 0,
    constructor(isNew) {
      if (isNew) {
        throw new TypeError("Failed to construct 'Document': Illegal constructor");
      }
      throw new TypeError("Illegal constructor");
    },
    instanceFactoryName: "createDocument",
    instanceInitializer(url) {
      this.url = url;
    },
  },
]);

try {
  new Document();
} catch (err) {
  console.log(err.message); // Failed to construct 'Document': Illegal constructor
}

const doc = createDocument("https://example.com/");
console.log(doc instanceof Document); // true
console.log(doc.url);                 // https://example.com/

特点:

  • factory 通过 native InstanceTemplate 创建实例,不调用公开构造函数回调。
  • factory 自身不可 new。
  • instanceInitializer 的 this 是新实例。
  • factory 名不能和构造函数名、别名、旧式 instanceName 或其他 factory 重名。

旧式立即创建实例

仍然可用,但新代码更推荐使用实例工厂。

const { Window, window } = addon.createProtoChains([
  {
    name: "Window",
    length: 0,
    constructor(isNew) {
      if (isNew) throw new TypeError("Illegal constructor");
    },
    isCreateInstance: true,
    instanceName: "window",
  },
]);

console.log(window instanceof Window); // true

跨调用复用与冲突规则

  • 同名构造函数会复用已注册的 FunctionTemplate / Function。
  • 已注册别名也会参与后续父级解析。
  • 别名不能和真实构造函数名冲突。
  • aliasOf 必须指向当前调用中存在或之前已注册的构造函数 / 别名。
  • 对已经创建并发布过的构造函数,不建议后续再尝试改变它自身的父级或原型继承关系;请尽量一次性完成该构造函数的结构定义。

注册表管理 API

createProtoChains 会把构造函数模板、构造函数对象和别名写入内部注册表,便于后续跨调用复用父级和别名。新增了三个管理 API:

API 参数 返回值 说明
getProtoChainRegistry() 无 { constructors: string[], aliases: object } 查看当前已注册构造函数名和别名映射。
deleteProtoChainRegistryEntry(name) name: string boolean 删除指定构造函数或别名。删除构造函数时,也会清理指向它的别名。
clearProtoChainRegistry() 无 boolean 清空全部构造函数和别名注册。
addon.createProtoChains([
  {
    name: "URL",
    aliases: ["webkitURL"],
    constructor() {},
  },
]);

console.log(addon.getProtoChainRegistry());
// { constructors: ["URL"], aliases: { webkitURL: "URL" } }

addon.deleteProtoChainRegistryEntry("URL");
addon.clearProtoChainRegistry();

注意:

  • 这些 API 主要用于测试隔离、环境重置或分阶段初始化管理。
  • 删除注册表不会主动销毁 JS 中已经拿到的构造函数引用,只是不再让后续 createProtoChains 通过注册表复用它。
  • 正式补环境中建议一次性规划好对象模型,减少运行中清理注册表。

8. V8 Private 私有槽 API

基于 V8 Private::ForApi 给对象保存不可通过普通 JS 属性枚举访问的私有值。

参数

API 参数 返回值
setPrivate object: object, key: string, value: any boolean,写入是否成功。
getPrivate object: object, key: string 保存的值;不存在时通常为 undefined。
hasPrivate object: object, key: string boolean,指定私有槽是否存在。
deletePrivate object: object, key: string boolean,删除是否成功。

示例

const obj = {};

addon.setPrivate(obj, "internalState", { ok: true });
console.log(obj.internalState); // undefined
console.log(addon.hasPrivate(obj, "internalState")); // true
console.log(addon.getPrivate(obj, "internalState")); // { ok: true }

addon.deletePrivate(obj, "internalState");
console.log(addon.hasPrivate(obj, "internalState")); // false
console.log(addon.getPrivate(obj, "internalState")); // undefined

9. getMimeTypesAndPlugins([config])

创建一组浏览器风格插件对象,通常用于补 navigator.mimeTypes 和 navigator.plugins。不传参数时使用项目内置 PDF 插件数据;传入 config 时按配置生成插件和 MIME 类型。

返回值

{
  mimeTypes: MimeTypeArrayLike,
  plugins: PluginArrayLike,
  PluginArray: Function,
  MimeTypeArray: Function,
  MimeType: Function,
  Plugin: Function
}

当前内置数据

  • mimeTypes.length === 2
    • application/pdf
    • text/pdf
  • plugins.length === 5
    • PDF Viewer
    • Chrome PDF Viewer
    • Chromium PDF Viewer
    • Microsoft Edge PDF Viewer
    • WebKit built-in PDF

参数化配置

type MimePluginConfig = {
  plugins?: Array<{
    name: string;
    filename?: string;
    description?: string;
    mimeTypes?: Array<{
      type: string;
      suffixes?: string;
      description?: string;
    }>;
  }>;
};

配置规则:

  • plugins 不是数组时按空数组处理。
  • 每个 plugin 会生成一个 Plugin 实例,并写入 name、filename、description、length。
  • 每个 MIME 配置会生成一个 MimeType 实例,并写入 type、suffixes、description、enabledPlugin。
  • 返回的 plugins 与 mimeTypes 都由 createNativeCollection 生成,支持 length、数字索引、item()、namedItem()、迭代和 Symbol.toStringTag。
const result = addon.getMimeTypesAndPlugins({
  plugins: [
    {
      name: "Custom PDF Viewer",
      filename: "custom-pdf-viewer",
      description: "Custom Portable Document Format",
      mimeTypes: [
        {
          type: "application/x-custom-pdf",
          suffixes: "cpdf",
          description: "Custom PDF",
        },
      ],
    },
  ],
});

console.log(result.plugins.length);       // 1
console.log(result.mimeTypes.length);     // 1
console.log(result.plugins[0].name);      // Custom PDF Viewer
console.log(result.plugins.namedItem("Custom PDF Viewer") === result.plugins[0]); // true
console.log(result.mimeTypes[0].enabledPlugin === result.plugins[0]);             // true

支持行为

  • mimeTypes.item(index) / plugins.item(index)。
  • mimeTypes.namedItem(name) / plugins.namedItem(name)。
  • 数字索引访问和名称访问。
  • Plugin.prototype[Symbol.iterator] 近似使用数组迭代器。
  • PluginArray、MimeTypeArray、MimeType、Plugin 构造函数都会模拟非法构造:直接调用或 new 都会抛出 TypeError。

示例

const {
  mimeTypes,
  plugins,
  PluginArray,
  MimeTypeArray,
  MimeType,
  Plugin,
} = addon.getMimeTypesAndPlugins();

Object.defineProperty(navigator, "mimeTypes", {
  value: mimeTypes,
  enumerable: true,
  configurable: true,
});

Object.defineProperty(navigator, "plugins", {
  value: plugins,
  enumerable: true,
  configurable: true,
});

console.log(mimeTypes.item(0) === mimeTypes[0]);
console.log(mimeTypes.namedItem("application/pdf"));

try { new Plugin(); } catch (err) { console.log(err.message); }

注意

当前源码中 MimeType 的部分 className / Symbol.toStringTag 拼写为 MimeTpye,如果目标检测非常严格,需要按实际输出决定是否修正源码或在 JS 层额外处理。

10. createInterceptor(options)

创建一个带 V8 named / indexed property interceptor 的对象。它适合做受控的“虚拟属性 + backing target”对象,能拦截读取、写入、查询、删除、枚举和属性描述符。

参数

type InterceptorOptions = {
  target?: object;
  handlers?: {
    getter?: (target: object, property: string | number | symbol) => any;
    setter?: (target: object, property: string | number | symbol, value: any) => any;
    query?: (target: object, property: string | number | symbol) => number;
    deleter?: (target: object, property: string | number | symbol) => boolean;
    enumerator?: (target: object) => Array<string | number>;
    definer?: (target: object, property: string | number | symbol, descriptor: object) => any;
    descriptor?: (target: object, property: string | number | symbol) => PropertyDescriptor;
  };
  immutablePrototype?: boolean;
  internalClassName?: string;
};

handler 返回约定

handler 返回值约定
getter 返回 { value } 表示拦截并返回该值;返回 { intercept: false } 表示放弃拦截并回落到 target。
setter 返回 { intercept: true, value } 可改写写入 target 的值;返回 { intercept: false } 可放弃 native 拦截。
descriptor 返回标准属性描述符对象。
enumerator 返回枚举属性名数组;indexed enumerator 内部只接受可转成 uint32 的索引。
query 可返回 V8 属性特性数字;未返回时会尝试从 descriptor 或 target 推导。
deleter 返回布尔值可作为删除结果。
definer 返回 false 或 { intercept: false } 可放弃拦截,否则 descriptor 会定义到 target。

示例

const target = { existing: 1 };
const obj = addon.createInterceptor({
  target,
  internalClassName: "MagicInterceptor",
  handlers: {
    getter(targetObject, property) {
      if (property === "virtual") return { value: 42 };
      return { intercept: false };
    },
    setter(targetObject, property, value) {
      return { intercept: true, value: String(value) };
    },
    descriptor(targetObject, property) {
      if (property === "virtual") {
        return {
          value: 42,
          writable: true,
          enumerable: true,
          configurable: true,
        };
      }
    },
    enumerator() {
      return ["virtual"];
    },
  },
});

console.log(obj.virtual);                         // 42
console.log(obj.existing);                        // 1
obj.created = 123;
console.log(target.created);                      // "123"
console.log(Object.keys(obj));                    // ["virtual"]
console.log(Object.prototype.toString.call(obj)); // [object MagicInterceptor]

注意:createInterceptor 适合高级探测和特殊对象;最终 WebAPI 若能用真实对象、描述符和 createProtoChains 表达,仍优先使用真实结构。

11. createNativeCollection(options)

通用浏览器集合对象工厂,用于创建 HTMLCollection、NodeList、PluginArray、MimeTypeArray 等“有 length、数字索引、名称索引、item、namedItem、可迭代”的集合对象。

参数

字段 类型 默认值 说明
name string "NativeCollection" 集合构造函数名。
items `Array<any { name?: string, value: any }>` []
itemMethod boolean true 是否在 prototype 上安装 item(index)。
namedItemMethod boolean true 是否在 prototype 上安装 namedItem(name)。
indexedAccess boolean true 是否定义数字索引属性。
namedAccess boolean true 是否定义名称属性。
namedEnumerable boolean false 名称属性是否可枚举。
iterable boolean true 是否安装 Symbol.iterator。
hasToStringTag boolean true 是否设置 Symbol.toStringTag。
toStringTag string 同 name Symbol.toStringTag 的值。
internalClassName string 无 不暴露 Symbol.toStringTag 时使用的内部对象标签。
immutableInstancePrototype boolean false 是否使集合实例原型不可改。

返回值

{
  collection: object;
  constructor: Function;
  [name: string]: Function;
}

示例

const first = { id: 1 };
const second = { id: 2 };

const result = addon.createNativeCollection({
  name: "HTMLCollection",
  items: [
    { name: "first", value: first },
    { name: "second", value: second },
  ],
  hasToStringTag: false,
  internalClassName: "HTMLCollection",
});

const collection = result.collection;
const HTMLCollection = result.HTMLCollection;

console.log(result.constructor === HTMLCollection); // true
console.log(collection.length);                     // 2
console.log(collection[0] === first);               // true
console.log(collection.first === first);            // true
console.log(collection.item(1) === second);         // true
console.log(collection.namedItem("second") === second);
console.log([...collection]);                       // [first, second]
console.log(Object.prototype.toString.call(collection)); // [object HTMLCollection]

try { new HTMLCollection(); } catch (err) { console.log(err.message); }

12. createUndetectable(callback[, handlers])

创建一个 V8 MarkAsUndetectable() 对象,适合模拟 document.all 这类特殊对象。

参数

参数 类型 必填 说明
callback function 是 对象被当作函数调用时执行。this 为内部 target。
handlers object 否 属性拦截处理器集合。

handler 列表

handler 调用签名 说明
getter (target, property) 属性读取前调用。返回普通对象(例如 {})时会继续从内部 target 读取同名属性;返回 { intercept: false } 表示放弃 native 拦截并回落到外层对象默认读取。当前 addon 中如果只定义 setter 而不定义 getter,读取刚写入的属性可能得到 undefined,因此需要读写内部 target 属性时必须显式提供 getter() { return {}; }。
setter (target, property, value) 属性写入时调用。可返回 { intercept: true, value } 改写保存值,或 { intercept: false, value } 放弃拦截。
query (target, property) 用于属性存在性 / 属性特性查询。
deleter (target, property) 删除属性时调用。返回布尔值可直接作为删除结果。
enumerator (target) 枚举属性名。返回数组时使用该数组,否则使用 target 自有属性。
definer (target, property, descriptor) Object.defineProperty 时调用。返回 false 或 { intercept: false } 可放弃拦截。
descriptor (target, property) Object.getOwnPropertyDescriptor 时调用。返回 descriptor 可覆盖默认结果。

示例:近似 document.all

const all = addon.createUndetectable(function () {
  return undefined;
});

Object.defineProperty(document, "all", {
  value: all,
  enumerable: false,
  configurable: true,
});

示例:带属性拦截

const special = addon.createUndetectable(
  function (...args) {
    return undefined;
  },
  {
    getter(target, property) {
      return {};
    },
    setter(target, property, value) {
      return { intercept: true, value: String(value) };
    },
    descriptor(target, property) {
      if (property === "hidden") {
        return { value: 1, writable: true, enumerable: false, configurable: true };
      }
    },
  }
);

special.foo = 123;
console.log(special.foo); // "123"

13. throwTypeError(message)

从 native addon 抛出 TypeError。

参数

参数 类型 必填 说明
message string 是 错误消息。

示例

function assertIllegalInvocation(thisValue, Ctor) {
  if (!(thisValue instanceof Ctor)) {
    addon.throwTypeError("Illegal invocation");
  }
}

如果没有传参,会抛出:the number of parameters must be at least one.

如果第一个参数不是字符串,会抛出:the first parameter require is a string.

14. hello()

历史调试 API。当前源码中 NODE_SET_METHOD(exports, "hello", Method); 已注释,当前重新构建后的导出列表不包含 hello。请不要在正式补环境逻辑中依赖它。

15. 推荐组合用法

创建普通 native-like 方法

Storage.prototype.getItem = addon.createNativeFunction(false, "getItem", 1, function (key) {
  return storageMap.get(String(key)) ?? null;
});

更推荐配合 Object.defineProperty:

Object.defineProperty(Storage.prototype, "getItem", {
  value: addon.createNativeFunction(false, "getItem", 1, function (key) {
    return storageMap.get(String(key)) ?? null;
  }),
  writable: true,
  enumerable: true,
  configurable: true,
});

创建不可直接构造但可内部实例化的 DOM 构造函数

const { Document, createDocument } = addon.createProtoChains([
  {
    name: "Document",
    length: 0,
    constructor(isNew) {
      if (isNew) throw new TypeError("Failed to construct 'Document': Illegal constructor");
      throw new TypeError("Illegal constructor");
    },
    readOnlyPrototypeProperty: true,
    immutablePrototypeObject: true,
    immutableInstancePrototype: true,
    instanceFactoryName: "createDocument",
    instanceInitializer(url) {
      Object.defineProperty(this, "URL", {
        value: String(url),
        enumerable: true,
        configurable: true,
      });
    },
  },
]);

const document = createDocument("https://example.com/");

创建带别名的构造函数

const { URL, webkitURL } = addon.createProtoChains([
  {
    name: "URL",
    aliases: ["webkitURL"],
    length: 1,
    constructor(isNew, url) {
      if (!isNew) throw new TypeError("Class constructor URL cannot be invoked without 'new'");
      this.href = String(url);
    },
  },
]);

console.log(URL === webkitURL); // true

16. 建议实践

  1. 优先使用 createProtoChains 管理构造函数、原型链、别名、实例工厂、原型方法和静态方法。
  2. 对 WebAPI 关键属性统一使用 Object.defineProperty / Object.defineProperties 明确描述符。
  3. 对不能被脚本直接构造的 DOM 类型,使用 constructor 抛错 + instanceFactoryName 内部创建实例。
  4. 需要 URL / webkitURL 这类别名时,使用 aliases 或 aliasOf,不要复制两个函数。
  5. 避免在多个阶段重复定义同一个构造函数名称;如果必须跨调用复用,请确保第一次创建时已经设置好继承与不可变选项。
  6. createNativeObject 保留给旧代码;新代码尽量迁移到 createProtoChains。
  7. 对 document.all 优先用 createUndetectable,JS fallback 很难完整模拟 HTMLDDA 行为。
  8. 对浏览器集合对象优先用 createNativeCollection,避免手写普通数组或普通对象导致描述符、迭代器和 toStringTag 不一致。
  9. 对需要临时拦截访问的高级对象可用 createInterceptor,但最终可表达为真实结构时仍应迁移到真实对象模型。
  10. 测试之间需要隔离构造函数注册状态时,使用 clearProtoChainRegistry() 或 deleteProtoChainRegistryEntry(name)。

17. 快速自检代码

const addon = require("./build/Release/addon");

console.log(Object.keys(addon).sort());

const f = addon.createNativeFunction(false, "demo", 0, () => 1);
console.log(f());
console.log(f.toString());

const { A, createA } = addon.createProtoChains([
  {
    name: "A",
    constructor(isNew) {
      if (isNew) throw new TypeError("Illegal constructor");
    },
    instanceFactoryName: "createA",
  },
]);

console.log(createA() instanceof A);