# xbs isolated-vm API 使用说明 > 本文档已根据当前随包 xbsVm API 文档同步更新。原始文档也随包保存于 `assets/runtime-frameworks/xbs-isolated-vm/XBS_API_USAGE.md` 与 `assets/runtime-frameworks/xbs-isolated-vm/XBS_DOM_API_USAGE.md`。 ## Skill 使用硬约束 - 只有用户明确选择 `isolated-vm` 补环境框架时,才复制并使用随包魔改 `xbs-isolated-vm/-/isolated_vm.node`。 - `window.xbs` / `globalThis.xbs` 只存在于 isolated-vm Context 内;不得桥接旧 `addon.node` 进入 isolated-vm。 - `xbs.dom.createDocument(options)` 是当前 xbs DOM 的唯一公开入口;已删除旧 API 不得继续使用。 - 需要基础 DOM 时优先通过 `xbs.dom.createDocument({ url, html, skeleton, omitApis, features })` 创建 document,再由 runtime 或项目代码决定是否安装到 `window.document`。 - `document.all` 优先使用 `xbs.dom.createDocument()` 自动提供的 `HTMLAllCollection` / HTMLDDA 行为;只有不使用 `createDocument()`、而是手写 document 时,才使用 `xbs.createUndetectable(callback, handlers)` 手动创建,并且必须配合 `HTMLAllCollection.prototype`、handlers 和原型链 fallback。 - 手动创建 `document.all` 时,`length / item / namedItem / constructor` 等已有原型链属性不要定义成 `document.all` 自有属性;更接近浏览器的安装方式是挂到 `Document.prototype` getter,而不是直接作为 document 自有属性。 - 如果当前 xbs API 已能覆盖目标行为但代码没有优先使用或参数错误,应先修正用法,不得标记为 native 能力缺口。 --- # XBS API 使用说明 本文档说明当前魔改版 `isolated-vm` 中 `window.xbs` 的用法。 `xbs` 只会注入到 isolated-vm 创建出的浏览器式 Context 内,即 `window.xbs` / `globalThis.xbs`,不会污染宿主侧 `require("isolated-vm")` 的原始 API。 ## 1. 基本使用方式 ```js const ivm = require("./isolated-vm"); const isolate = new ivm.Isolate(); const context = isolate.createContextSync(); const result = isolate.compileScriptSync(` ({ windowIsGlobal: window === globalThis, hasXbs: !!window.xbs, apiNames: Object.keys(window.xbs).sort(), }) `).runSync(context, { copy: true }); ``` 在 Context 内可以直接使用: ```js const fn = xbs.createNativeFunction(false, "demo", 0, function () { return "ok"; }); fn(); // "ok" ``` 当前 Context 默认具备以下基础关系: ```js window === globalThis; // true self === window; // true top === window; // true parent === window; // true window instanceof Window; // true Object.getPrototypeOf(window) === Window.prototype; // true Object.getPrototypeOf(Window.prototype) === WindowProperties.prototype; // true Object.getPrototypeOf(WindowProperties.prototype) === EventTarget.prototype; // true ``` ## 2. API 总览 当前 `window.xbs` 暴露 17 个 API: | API | 说明 | | --- | --- | | `createNativeObject` | 创建带 native-like 构造函数、实例与原型链的对象。 | | `createNativeFunction` | 创建 native-like 函数或构造函数。 | | `createProtoChains` | 批量创建浏览器风格构造函数、原型链、别名、实例工厂与注册表。 | | `createGetter` | 创建 native-like getter。 | | `createSetter` | 创建 native-like setter。 | | `createInterceptor` | 创建带 V8 属性拦截器的对象。 | | `createNativeCollection` | 创建 `HTMLCollection` / `NodeList` 等浏览器集合风格对象。 | | `getMimeTypesAndPlugins` | 创建浏览器风格 `navigator.mimeTypes` 与 `navigator.plugins`。 | | `createUndetectable` | 创建 V8 undetectable 对象,常用于近似 `document.all`。 | | `getPrivate` | 读取 V8 Private slot。 | | `setPrivate` | 写入 V8 Private slot。 | | `hasPrivate` | 判断 V8 Private slot 是否存在。 | | `deletePrivate` | 删除 V8 Private slot。 | | `getProtoChainRegistry` | 查看当前 isolate 的原型链注册表。 | | `clearProtoChainRegistry` | 清空当前 isolate 的原型链注册表。 | | `deleteProtoChainRegistryEntry` | 删除当前 isolate 注册表中的指定构造函数或别名。 | | `throwTypeError` | 从 native 层抛出 `TypeError`。 | ## 3. `createNativeFunction(isConstructor, name, length, callback)` 创建 `Function.prototype.toString` 结果为 native-like 的函数。 ### 参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | `isConstructor` | `boolean` | 是否允许 `new` 调用。 | | `name` | `string` | 函数名。 | | `length` | `number` | 函数的 `length`。 | | `callback` | `function` | 实际 JS 回调。 | ### 普通函数示例 ```js const add = xbs.createNativeFunction(false, "add", 2, function (a, b) { return a + b; }); add(1, 2); // 3 Function.prototype.toString.call(add); // "function add() { [native code] }" ``` ### 构造函数示例 构造调用时,`callback` 的第一个参数是 `isNew`,后面才是实际传入参数。构造调用最终返回 `this`。 ```js const Storage = xbs.createNativeFunction(true, "Storage", 0, function (isNew) { this.createdByNew = isNew; }); const storage = new Storage(); storage.createdByNew; // true ``` ## 4. `createGetter(name, length, callback)` 创建 native-like getter,常配合 `Object.defineProperty` 使用。 ```js const navigator = {}; Object.defineProperty(navigator, "userAgent", { get: xbs.createGetter("userAgent", 0, function () { return "Mozilla/5.0"; }), enumerable: true, configurable: true, }); navigator.userAgent; // "Mozilla/5.0" Function.prototype.toString.call(Object.getOwnPropertyDescriptor(navigator, "userAgent").get); // "function get userAgent() { [native code] }" ``` ## 5. `createSetter(name, length, callback)` 创建 native-like setter。 ```js const location = {}; let href = "https://example.com/"; Object.defineProperty(location, "href", { get: xbs.createGetter("href", 0, function () { return href; }), set: xbs.createSetter("href", 1, function (value) { href = String(value); }), enumerable: true, configurable: true, }); location.href = "https://example.org/"; location.href; // "https://example.org/" ``` ## 6. `createProtoChains(descriptors)` 批量创建构造函数、原型对象、继承关系、实例工厂和别名。该 API 是补浏览器对象模型时最常用的基础能力。 ### 常用 descriptor 字段 | 字段 | 类型 | 说明 | | --- | --- | --- | | `name` | `string` | 构造函数名称。必填。 | | `length` | `number` | 构造函数 `length`。 | | `constructor` | `function` | 构造函数回调。普通构造函数必填。 | | `prototypeParent` | `string` | 原型父级构造函数名称。 | | `constructorParent` | `string` / `null` | 构造函数对象父级。未设置时默认跟随 `prototypeParent`;设为 `null` 可禁用默认继承。 | | `readOnlyPrototypeProperty` | `boolean` | 是否让构造函数的 `.prototype` 属性只读。 | | `immutablePrototypeObject` | `boolean` | 是否禁止修改构造函数 `.prototype` 对象的原型。 | | `immutableInstancePrototype` | `boolean` | 是否禁止修改实例对象的原型。 | | `hasToStringTag` | `boolean` | 是否在 prototype 上设置 `Symbol.toStringTag`,默认 `true`。 | | `toStringTag` | `string` | 自定义 `Symbol.toStringTag` 值。 | | `aliases` | `string[]` | 当前构造函数的别名列表。 | | `aliasOf` | `string` | 创建指向已有构造函数的别名。 | | `internalClassName` | `string` | 设置内部类名,用于影响 `Object.prototype.toString`。 | | `constructorBehavior` | `"allow" \| "throw" \| "illegal"` | 控制 `new Constructor()` 行为。 | | `callBehavior` | `"allow" \| "throw" \| "illegal"` | 控制 `Constructor()` 直接调用行为。 | | `constructorErrorMessage` | `string` | 构造调用报错信息。 | | `callErrorMessage` | `string` | 直接调用报错信息。 | | `prototypeMethods` | `Array` | 安装到 prototype 的 native-like 方法。 | | `staticMethods` | `Array` | 安装到构造函数对象上的 native-like 静态方法。 | | `instanceFactoryName` | `string` | 额外导出一个内部实例工厂。 | | `instanceInitializer` | `function` | 实例工厂创建对象后的初始化函数。 | ### 创建继承链 ```js const chain = xbs.createProtoChains([ { name: "EventTarget", length: 0, constructor: function () {}, readOnlyPrototypeProperty: true, immutablePrototypeObject: true, }, { name: "Node", length: 0, constructor: function () {}, prototypeParent: "EventTarget", readOnlyPrototypeProperty: true, immutablePrototypeObject: true, }, ]); const EventTarget = chain.EventTarget; const Node = chain.Node; Object.getPrototypeOf(Node.prototype) === EventTarget.prototype; // true Object.getPrototypeOf(Node) === EventTarget; // true new Node() instanceof EventTarget; // true ``` ### 非法构造 + 内部实例工厂 适合模拟 `Document`、`Location` 等不能直接 `new`,但框架内部需要创建实例的对象。 ```js const chain = xbs.createProtoChains([ { name: "Document", length: 0, constructor() {}, constructorBehavior: "throw", constructorErrorMessage: "Illegal constructor", callBehavior: "throw", callErrorMessage: "Illegal constructor", instanceFactoryName: "createDocument", instanceInitializer(url) { this.URL = url; }, }, ]); const Document = chain.Document; const document = chain.createDocument("https://example.com/"); document instanceof Document; // true new Document(); // TypeError: Illegal constructor ``` ## 7. 原型链注册表 API `createProtoChains` 创建的构造函数会登记到当前 isolate 的注册表中。注册表按 isolate 隔离,不同 `new ivm.Isolate()` 之间不会互相污染。 ```js const registry = xbs.getProtoChainRegistry(); registry.constructors; // 构造函数名称数组 registry.aliases; // 别名映射对象 xbs.deleteProtoChainRegistryEntry("URL"); // 删除指定构造函数或别名 xbs.clearProtoChainRegistry(); // 清空当前 isolate 的注册表 ``` ## 8. `createNativeObject(options)` 创建带 native-like 构造函数、实例对象与可选父级原型链的对象。新代码更推荐使用 `createProtoChains`,但该 API 仍可用于兼容旧逻辑。 ```js const result = xbs.createNativeObject({ name: "Navigator", length: 0, constructor: function () {}, isReadOnlyPrototype: true, isImmutableProto: true, }); result.constructor; // Navigator 构造函数 result.instance; // Navigator 实例 ``` ## 9. `createInterceptor(options)` 创建带 V8 属性拦截器的对象,用于模拟需要自定义读取、写入、枚举、描述符等行为的宿主对象。 ### handlers | handler | 签名 | 说明 | | --- | --- | --- | | `getter` | `(target, property)` | 读取属性时调用。返回 `{ intercept: false }` 可放行默认读取。 | | `setter` | `(target, property, value)` | 写属性时调用。可返回 `{ intercept: true, value }` 改写保存值。 | | `query` | `(target, property)` | 属性存在性查询。 | | `deleter` | `(target, property)` | 删除属性时调用。 | | `enumerator` | `(target)` | 枚举属性名时调用。 | | `definer` | `(target, property, descriptor)` | `Object.defineProperty` 时调用。 | | `descriptor` | `(target, property)` | `Object.getOwnPropertyDescriptor` 时调用。 | ### 示例 ```js const target = { existing: 1 }; const obj = xbs.createInterceptor({ target, internalClassName: "MagicObject", 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"]; }, }, }); obj.virtual; // 42 obj.existing; // 1 obj.created = 123; target.created; // "123" Object.keys(obj); // ["virtual"] Object.prototype.toString.call(obj); // "[object MagicObject]" ``` ## 10. `createNativeCollection(options)` 创建浏览器集合对象,例如 `HTMLCollection`、`NodeList`、`PluginArray` 类似对象。 ```js const first = { id: 1 }; const second = { id: 2 }; const result = xbs.createNativeCollection({ name: "HTMLCollection", items: [ { name: "first", value: first }, { name: "second", value: second }, ], hasToStringTag: false, internalClassName: "HTMLCollection", }); const collection = result.collection; collection.length; // 2 collection[0] === first; // true collection.first === first; // true collection.item(1) === second; // true collection.namedItem("second") === second; // true collection.namedItem("missing"); // null [...collection]; // [first, second] Object.prototype.toString.call(collection); // "[object HTMLCollection]" ``` 返回对象通常包含: - `collection`:集合实例。 - `constructor`:集合构造函数。 - `[name]`:以集合名称命名的构造函数,例如 `result.HTMLCollection`。 ## 11. `getMimeTypesAndPlugins([config])` 创建浏览器风格 `PluginArray`、`MimeTypeArray`、`Plugin`、`MimeType`,适合补 `navigator.plugins` 和 `navigator.mimeTypes`。 ### 默认用法 ```js const { mimeTypes, plugins, PluginArray, MimeTypeArray, MimeType, Plugin, } = xbs.getMimeTypesAndPlugins(); navigator.plugins = plugins; navigator.mimeTypes = mimeTypes; ``` ### 参数化配置 ```js const result = xbs.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", }, ], }, ], }); result.plugins.length; // 1 result.mimeTypes.length; // 1 result.plugins[0].name; // "Custom PDF Viewer" result.mimeTypes[0].type; // "application/x-custom-pdf" result.mimeTypes[0].enabledPlugin === result.plugins[0]; // true ``` ## 12. `createUndetectable(callback[, handlers])` 创建 V8 `MarkAsUndetectable()` 对象,常用于近似 `document.all` / HTMLDDA 行为。 ```js const all = xbs.createUndetectable(function () { return undefined; }); typeof all; // "undefined" Boolean(all); // false all == null; // true all === undefined; // false all(); // undefined ``` ### handler 行为 `createUndetectable` 支持与 `createInterceptor` 类似的 `handlers`,常用字段如下: | handler | 签名 | 说明 | | --- | --- | --- | | `getter` | `(target, property)` | 读取属性时调用。返回 `{ value }` 可直接返回虚拟值;返回 `{ intercept: false }` 会放行,让 V8 继续查找 backing target 或原型链。 | | `query` | `(target, property)` | 属性存在性查询。可返回属性 attribute 数值;也可配合 `descriptor` 生成属性描述。 | | `descriptor` | `(target, property)` | `Object.getOwnPropertyDescriptor` 时调用。可返回标准 descriptor;返回 `{ intercept: false }` 表示放行。 | | `enumerator` | `(target)` | 枚举属性名时调用。 | | `setter` / `definer` / `deleter` | 同 `createInterceptor` | 用于写入、`Object.defineProperty` 和删除。 | 注意:当前实现已经支持原型链 fallback。也就是说,如果 handler 不存在,或 handler 返回 `{ intercept: false }`,并且 backing target 没有该自有属性,读取会继续查找对象原型链。这对手动模拟 `document.all.length`、`document.all.item()`、`Symbol.toStringTag` 很重要。 模拟 `document.all` 时,建议 **all 创建都带 handlers**,并把 `length / item / namedItem / Symbol.toStringTag` 放到 `HTMLAllCollection.prototype` 上,避免 `length` 变成 `document.all` 的自有属性: ```js const document = { _elements: [] }; const chain = xbs.createProtoChains([{ name: "HTMLAllCollection", length: 0, constructor() {}, constructorBehavior: "throw", callBehavior: "throw", readOnlyPrototypeProperty: true, hasToStringTag: true, toStringTag: "HTMLAllCollection", }]); const HTMLAllCollection = chain.HTMLAllCollection; Object.defineProperty(HTMLAllCollection.prototype, "length", { get: xbs.createGetter("length", 0, function () { return document._elements.length; }), enumerable: true, configurable: true, }); Object.defineProperty(HTMLAllCollection.prototype, "item", { value: xbs.createNativeFunction(false, "item", 1, function (index) { index = Number(index) >>> 0; return index < document._elements.length ? document._elements[index] : null; }), enumerable: true, configurable: true, }); Object.defineProperty(HTMLAllCollection.prototype, "namedItem", { value: xbs.createNativeFunction(false, "namedItem", 1, function (name) { name = String(name); return document._elements.find(el => el.id === name || el.name === name) || null; }), enumerable: true, configurable: true, }); function isIndex(property) { return typeof property === "number" || /^(0|[1-9]\d*)$/.test(String(property)); } function resolveAll(property) { if (isIndex(property)) { const index = Number(property) >>> 0; return index < document._elements.length ? { value: document._elements[index], enumerable: true } : null; } const name = String(property); if (name === "length" || name === "item" || name === "namedItem") { return null; // 放行给 HTMLAllCollection.prototype } const element = document._elements.find(el => el.id === name || el.name === name); return element ? { value: element, enumerable: false } : null; } function defineAllProperty(target, property) { const hit = resolveAll(property); if (!hit) return false; Object.defineProperty(target, String(property), { value: hit.value, writable: false, enumerable: hit.enumerable, configurable: true, }); return true; } const all = xbs.createUndetectable(function (value) { if (arguments.length === 0 || value == null) return null; const hit = resolveAll(value); return hit ? hit.value : null; }, { getter(target, property) { if (typeof property === "symbol" || !defineAllProperty(target, property)) { return { intercept: false }; } }, query(target, property) { if (typeof property === "symbol" || !defineAllProperty(target, property)) { return { intercept: false }; } }, descriptor(target, property) { if (typeof property === "symbol" || !defineAllProperty(target, property)) { return { intercept: false }; } }, enumerator(target) { for (let i = 0; i < document._elements.length; i++) { defineAllProperty(target, String(i)); } return Object.getOwnPropertyNames(target); }, }); Object.setPrototypeOf(all, HTMLAllCollection.prototype); Object.defineProperty(document, "all", { value: all, enumerable: true, configurable: true, }); Object.hasOwn(document.all, "length"); // false Object.getOwnPropertyDescriptor(document.all, "length"); // undefined document.all.length; // 来自 HTMLAllCollection.prototype.length ``` 如果要更贴近浏览器,`all` 最好安装在 `Document.prototype` 的 getter 上,而不是直接作为 `document` 自有属性: ```js Object.defineProperty(Document.prototype, "all", { get: xbs.createGetter("all", 0, function () { return all; }), enumerable: true, configurable: true, }); ``` ## 13. Private slot API 基于 V8 `Private::ForApi` 实现,适合把内部状态存放到 JS 对象上,同时避免普通属性枚举或直接访问。 ```js const obj = {}; xbs.hasPrivate(obj, "slot"); // false xbs.setPrivate(obj, "slot", 123); // true xbs.hasPrivate(obj, "slot"); // true xbs.getPrivate(obj, "slot"); // 123 xbs.deletePrivate(obj, "slot"); // true xbs.hasPrivate(obj, "slot"); // false ``` ## 14. `throwTypeError(message)` 从 native 层抛出 `TypeError`。 ```js function assertIllegalInvocation(thisValue, Ctor) { if (!(thisValue instanceof Ctor)) { xbs.throwTypeError("Illegal invocation"); } } ``` 注意: - 不传参数会抛出参数数量错误。 - 第一个参数不是字符串会抛出类型错误。 ## 15. 推荐实践 1. 所有非 WebAPI 的辅助能力都放在 `window.xbs` 下,不要直接污染 `window`。 2. 对外可见的浏览器对象优先使用 `createProtoChains` 创建构造函数与原型链。 3. 对手写 `document.all` 这类特殊对象才优先使用 `createUndetectable(callback, handlers)`;如果需要基础 DOM,应优先使用 `xbs.dom.createDocument()` 自动提供的 `document.all`。 4. 对 `navigator.plugins` / `navigator.mimeTypes` 优先使用 `getMimeTypesAndPlugins`。 5. 对集合对象优先使用 `createNativeCollection`,不要直接用普通数组代替。 6. 对内部状态优先使用 private slot,减少普通属性泄露。 7. 多 isolate 场景下,原型链注册表会按 isolate 隔离;清理 isolate 时会同步清理对应 XBS 注册表。 8. 宿主侧 `isolated-vm` API 保持原样,`xbs` 只存在于创建出的 Context 内。 ## 16. 快速自检 ```js const expected = [ "clearProtoChainRegistry", "createGetter", "createInterceptor", "createNativeCollection", "createNativeFunction", "createNativeObject", "createProtoChains", "createSetter", "createUndetectable", "deletePrivate", "deleteProtoChainRegistryEntry", "getMimeTypesAndPlugins", "getPrivate", "getProtoChainRegistry", "hasPrivate", "setPrivate", "throwTypeError", ]; Object.keys(window.xbs).sort().join("\n") === expected.join("\n"); ``` 也可以直接运行项目内测试: ```powershell node --no-node-snapshot tests\xbs-basic.js ``` --- # XBS DOM API 使用说明 本文说明当前 `xbsVm` 在 `isolated-vm` Context 内提供的 DOM 能力。入口统一为 `window.xbs.dom`,不会修改 host 侧 `isolated-vm` 的原始 API。 ## 1. 当前公开 API 当前 `xbs.dom` 只保留一个公开方法: ```js xbs.dom.createDocument(options) ``` 以下 API 已删除,不再对外暴露: - `attachDocument()` / `detachDocument()` - `installConstructors()` / `getConstructors()` - `createElement()` - `createIframeDocument()` / `attachIframeDocument()` / `detachIframeDocument()` - `patchApi()` / `removeApi()` - `createProfile()` / `getProfile()` / `listProfiles()` - `snapshotSurface()` / `restoreSurface()` - `getLastError()` `createDocument()` 中也不再支持 `profile` 参数。 ## 2. 设计原则 - 默认不会把 `document` 挂到 `window` 上;调用方自行决定是否执行 `window.document = document`。 - `createDocument()` 会在返回的 document 对象上安装基础 DOM 属性、方法和原型链。 - DOM 构造函数不会默认挂到 `window` 上;对象仍然具有内部构造函数和原型链,例如 `document.constructor.name === "HTMLDocument"`。 - 删除或禁用某个 DOM API 应在创建 document 前通过配置声明,而不是运行时强删已发布的不可配置属性。 - C++ 文件中不再通过 JS bootstrap 字符串实现 DOM;DOM 相关逻辑位于 `src/xbs/dom/`。 ## 3. 快速示例 ```js const ivm = require("isolated-vm"); const isolate = new ivm.Isolate(); const context = isolate.createContextSync(); const result = isolate.compileScriptSync(` (() => { const document = xbs.dom.createDocument({ url: "https://example.com/", html: '
hello
' }); return { hasWindowDocument: "document" in window, text: document.querySelector(".hot").textContent, url: document.URL }; })() `).runSync(context, { copy: true }); console.log(result); ``` ## 4. createDocument(options) ### 参数 ```js const document = xbs.dom.createDocument({ url: "https://example.com/path", html: '
ok
', skeleton: true, omitApis: ["document.all"] }); ``` 支持字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `url` | `string` | 设置 `document.URL`,默认是 `about:blank`。 | | `html` | `string` | 创建后解析到 `document.body`,当前为简化 HTML 解析器。 | | `skeleton` | `boolean` | 是否创建 `html/head/body` 骨架,默认 `true`。 | | `omitApis` | `string[]` | 创建前不安装指定 API。 | | `disabledApis` | `string[]` | `omitApis` 的兼容别名。 | | `removedApis` | `string[]` | `omitApis` 的兼容别名。 | | `features.documentAll` | `boolean` | 为 `false` 时不安装 `document.all`。 | | `features.iframeContentDocument` | `boolean` | 为 `false` 时不安装 iframe 的 `contentDocument/contentWindow`。 | ## 5. 创建前禁用 API 示例:创建一个不带 `document.all` 的 document。 ```js const document = xbs.dom.createDocument({ omitApis: ["document.all"] }); console.log("all" in document); // false ``` 示例:创建一个不带 `Document.prototype.createComment` 的 document。 ```js const document = xbs.dom.createDocument({ omitApis: ["Document.prototype.createComment"] }); console.log(typeof document.createComment); // undefined ``` 示例:禁用 iframe 的 `contentDocument`。 ```js const document = xbs.dom.createDocument({ omitApis: ["HTMLIFrameElement.prototype.contentDocument"] }); const iframe = document.createElement("iframe"); console.log(typeof iframe.contentDocument); // undefined ``` 也支持通配前缀: ```js xbs.dom.createDocument({ omitApis: ["Element.prototype.*"] }); ``` ## 6. 常用 DOM 能力 当前基础实现包含以下对象和能力: - `HTMLDocument` / `Document` - `Node` / `Element` / `HTMLElement` / `HTMLIFrameElement` - `Text` / `Comment` / `CDATASection` / `ProcessingInstruction` / `DocumentFragment` - `Attr` - `DOMTokenList` / `NodeList` / `HTMLCollection` - `HTMLAllCollection` / `HTMLFormControlsCollection` / `HTMLOptionsCollection` / `RadioNodeList` - `NamedNodeMap` / `DOMStringMap` 常用示例: ```js const document = xbs.dom.createDocument(); const div = document.createElement("div"); div.id = "box"; div.className = "a b"; div.classList.add("c"); div.innerHTML = 'text'; document.body.appendChild(div); console.log(document.querySelector("#box") === div); // true console.log(div.querySelector("span").textContent); // text console.log(div.children.namedItem("s1").tagName); // SPAN ``` ## 7. 本阶段新增 DOM API 说明:`xbs.dom` 对外仍然只暴露 `createDocument()`。以下新增 API 是通过 `createDocument()` 返回的 `document`、其节点对象和原型链对外可用。 ### 7.1 Document 新增属性 ```js const document = xbs.dom.createDocument(); document.documentURI; // 与 document.URL 保持一致 document.embeds; // HTMLCollection document.plugins; // HTMLCollection ``` 当前 `Document` 可用的主要属性包括: - `documentElement` - `head` - `body` - `title` - `readyState` - `URL` - `documentURI` - `referrer` - `characterSet` - `compatMode` - `contentType` - `defaultView` - `activeElement` - `doctype` - `implementation` - `currentScript` - `all` - `scripts` - `forms` - `images` - `links` - `anchors` - `embeds` - `plugins` - `children` ### 7.2 Document 新增创建方法 ```js const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg:rect"); console.log(svg.namespaceURI); // http://www.w3.org/2000/svg console.log(svg.prefix); // svg console.log(svg.localName); // rect const attr = document.createAttribute("data-foo"); attr.value = "bar"; const attrNS = document.createAttributeNS("urn:x", "x:key"); const cdata = document.createCDATASection("abc"); console.log(cdata.nodeType); // 4 const pi = document.createProcessingInstruction("xml-stylesheet", "href=x"); console.log(pi.nodeType); // 7 ``` 当前 `Document` 可用的主要查询和创建方法包括: - `getElementById()` - `querySelector()` - `querySelectorAll()` - `getElementsByTagName()` - `getElementsByClassName()` - `getElementsByName()` - `createElement()` - `createElementNS()` - `createTextNode()` - `createComment()` - `createDocumentFragment()` - `createAttribute()` - `createAttributeNS()` - `createCDATASection()` - `createProcessingInstruction()` ### 7.3 Node 新增方法 ```js const node = document.createTextNode("hello"); const clone = node.cloneNode(false); console.log(node.isEqualNode(clone)); // true ``` 新增或补齐的方法: - `isEqualNode(otherNode)` - `lookupNamespaceURI(prefix)` - `lookupPrefix(namespaceURI)` - `isDefaultNamespace(namespaceURI)` 当前 `Node` 可用的主要属性和方法包括: - 属性:`nodeType`、`nodeName`、`nodeValue`、`textContent`、`parentNode`、`parentElement`、`childNodes`、`firstChild`、`lastChild`、`nextSibling`、`previousSibling`、`ownerDocument`、`isConnected`、`baseURI` - 方法:`appendChild()`、`removeChild()`、`insertBefore()`、`replaceChild()`、`cloneNode()`、`contains()`、`hasChildNodes()`、`getRootNode()`、`isSameNode()`、`isEqualNode()`、`compareDocumentPosition()`、`normalize()`、`lookupNamespaceURI()`、`lookupPrefix()`、`isDefaultNamespace()` ### 7.4 Element 新增属性和方法 ```js const el = document.createElementNS("http://www.w3.org/2000/svg", "svg:rect"); console.log(el.namespaceURI); // http://www.w3.org/2000/svg console.log(el.prefix); // svg console.log(el.localName); // rect el.setAttributeNS("urn:x", "x:key", "value"); console.log(el.getAttributeNS("urn:x", "key")); // value console.log(el.hasAttributeNS("urn:x", "key")); // true const attr = document.createAttribute("data-user-id"); attr.value = "10001"; el.setAttributeNode(attr); console.log(el.getAttributeNode("data-user-id").ownerElement === el); // true console.log(el.attributes.getNamedItem("data-user-id").value); // 10001 console.log(el.dataset.userId); // 10001 el.dataset.token = "abc"; console.log(el.getAttribute("data-token")); // abc delete el.dataset.token; ``` 新增或补齐的属性: - `namespaceURI` - `prefix` - `localName` - `slot` - `dir` - `shadowRoot` - `assignedSlot` - `attributes` - `dataset` 新增或补齐的方法: - `getAttributeNS()` - `setAttributeNS()` - `hasAttributeNS()` - `getAttributeNode()` - `setAttributeNode()` - `removeAttributeNode()` - `webkitMatchesSelector()` 当前 `Element` 可用的主要属性和方法包括: - 属性:`tagName`、`id`、`className`、`classList`、`attributes`、`innerHTML`、`outerHTML`、`innerText`、`textContent`、`children`、`firstElementChild`、`lastElementChild`、`nextElementSibling`、`previousElementSibling`、`childElementCount`、`namespaceURI`、`prefix`、`localName`、`style`、`dataset`、`slot`、`dir`、`shadowRoot`、`assignedSlot` - 方法:`getAttribute()`、`setAttribute()`、`hasAttribute()`、`removeAttribute()`、`toggleAttribute()`、`getAttributeNames()`、`getAttributeNS()`、`setAttributeNS()`、`hasAttributeNS()`、`getAttributeNode()`、`setAttributeNode()`、`removeAttributeNode()`、`matches()`、`webkitMatchesSelector()`、`closest()`、`querySelector()`、`querySelectorAll()`、`getElementsByTagName()`、`getElementsByClassName()`、`append()`、`prepend()`、`before()`、`after()`、`remove()`、`replaceWith()`、`replaceChildren()`、`insertAdjacentHTML()`、`insertAdjacentElement()`、`insertAdjacentText()`、`focus()`、`blur()`、`click()`、`getBoundingClientRect()`、`getClientRects()`、`scrollIntoView()` ### 7.5 Attr `Attr` 表示属性节点,可由 `document.createAttribute()` 或 `document.createAttributeNS()` 创建,也可通过 `element.getAttributeNode()` 取得。 ```js const attr = document.createAttribute("data-id"); attr.value = "1"; console.log(attr.name); // data-id console.log(attr.value); // 1 console.log(attr.nodeType); // 2 console.log(attr.nodeName); // data-id console.log(attr.nodeValue); // 1 console.log(attr.textContent); // 1 ``` 主要属性: - `name` - `value` - `namespaceURI` - `prefix` - `localName` - `specified` - `ownerElement` - `nodeType` - `nodeName` - `nodeValue` - `textContent` ### 7.6 NamedNodeMap `element.attributes` 返回 `NamedNodeMap`,用于访问元素属性节点。 ```js const el = document.createElement("div"); el.setAttribute("id", "box"); console.log(el.attributes.length); // 1 console.log(el.attributes.item(0).name); // id console.log(el.attributes.getNamedItem("id").value); // box ``` 主要 API: - `length` - `item(index)` - `getNamedItem(name)` - `getNamedItemNS(namespaceURI, localName)` - `setNamedItem(attr)` - `setNamedItemNS(attr)` - `removeNamedItem(name)` - `removeNamedItemNS(namespaceURI, localName)` ### 7.7 DOMStringMap `element.dataset` 返回 `DOMStringMap`,会映射到元素的 `data-*` 属性。 ```js const el = document.createElement("div"); el.dataset.userId = "10001"; console.log(el.getAttribute("data-user-id")); // 10001 console.log(el.dataset.userId); // 10001 delete el.dataset.userId; console.log(el.hasAttribute("data-user-id")); // false ``` ### 7.8 集合对象 本阶段新增和补齐的集合对象: - `HTMLAllCollection` - 来源:`document.all` - 特性:保持不可检测语义,`typeof document.all === "undefined"`,但仍可访问 `length`、`item()`、`namedItem()` - `HTMLFormControlsCollection` - 预留给表单控件集合能力 - 支持 `length`、`item()`、`namedItem()` - `HTMLOptionsCollection` - 预留给 `select.options` 类场景 - 支持 `length`、`selectedIndex`、`item()`、`namedItem()`、`add()`、`remove()` - `RadioNodeList` - 预留给同名 radio 控件集合 - 支持 `length`、`value`、`item()`、`namedItem()` 示例: ```js const document = xbs.dom.createDocument({ html: '
text
' }); console.log(document.all.length >= 1); // true console.log(document.all.item(0)); // html 元素 console.log(document.body.children.namedItem("s1").tagName); // SPAN ``` ### 7.9 手动创建 document.all 如果用户不使用 `xbs.dom.createDocument()`,而是自己创建 `document`,再通过 `xbs.createUndetectable()` 创建 `all` 并挂载到 `document` 上,需要注意: - `xbs.createUndetectable()` 只负责 HTMLDDA/不可检测语义,例如 `typeof all === "undefined"`、`Boolean(all) === false`、`all == null`。 - `document.all.length`、`item()`、`namedItem()`、`[0]`、命名属性、枚举和描述符,需要由 `HTMLAllCollection.prototype` 与 handlers 配合实现。 - all 创建建议都传入 handlers。handler 未命中时返回 `{ intercept: false }`,让 V8 继续查找 backing target 或原型链。 - `length / item / namedItem / constructor` 等原型链已有属性不要定义成 `document.all` 自有属性,否则会和真实浏览器不一致。 最小结构示例: ```js const all = xbs.createUndetectable(function (value) { if (arguments.length === 0 || value == null) return null; // 这里根据自己的 document 元素表处理索引或 id/name 查询 return null; }, { getter(target, property) { // 命中索引或 id/name 时,可 Object.defineProperty(target, property, descriptor) // 未命中或遇到 length/item/namedItem 等原型属性时必须放行 return { intercept: false }; }, query(target, property) { return { intercept: false }; }, descriptor(target, property) { return { intercept: false }; }, enumerator(target) { return Object.getOwnPropertyNames(target); }, }); Object.setPrototypeOf(all, HTMLAllCollection.prototype); ``` 如果直接挂到 `document`: ```js Object.defineProperty(document, "all", { value: all, enumerable: true, configurable: true, }); ``` 则 `Object.hasOwn(document, "all")` 会是 `true`。如果要更接近真实浏览器,应挂到 `Document.prototype`: ```js Object.defineProperty(Document.prototype, "all", { get: xbs.createGetter("all", 0, function () { return all; }), enumerable: true, configurable: true, }); ``` ## 8. 新增 API 的 omitApis 路径 新增 API 仍然遵循创建前禁用规则。常用路径示例: ```js const document = xbs.dom.createDocument({ omitApis: [ "Document.prototype.createElementNS", "Document.prototype.createAttribute", "Document.prototype.createCDATASection", "Document.prototype.createProcessingInstruction", "Element.prototype.dataset", "Element.prototype.getAttributeNS", "Element.prototype.setAttributeNS", "Element.prototype.webkitMatchesSelector", "Node.prototype.isEqualNode", "Node.prototype.lookupNamespaceURI", "Attr.prototype.value", "NamedNodeMap.prototype.getNamedItem" ] }); ``` 也可以继续使用通配前缀: ```js xbs.dom.createDocument({ omitApis: [ "Document.prototype.*", "Element.prototype.*", "Node.prototype.*" ] }); ``` ## 9. iframe 行为 不再提供 `createIframeDocument()` 或 `attachIframeDocument()`。现在 iframe 的 `contentDocument` 会按需创建。 ```js const document = xbs.dom.createDocument({ html: '' }); const iframe = document.getElementById("f"); console.log(iframe.contentDocument.body.tagName); // BODY console.log(iframe.contentWindow.document === iframe.contentDocument); // true ``` ## 10. 当前边界 当前 DOM 是补环境用的基础实现,不是完整浏览器内核: - HTML parser 为简化实现,适合基础标签、属性、文本和注释。 - selector 目前支持标签、`#id`、`.class`、简单属性选择器和逗号分组。 - 暂不支持完整 HTML5 解析、复杂 CSS selector、真实布局、CSSOM、事件系统、MutationObserver、Canvas、WebGL 等。