Files
reverse-skills-max/vendor-new/xbsReverseSkill/web-js-env-patcher/references/xbs-isolated-vm-api.md

1182 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 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/<platform>-<arch>/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: '<main id="app"><span class="hot">hello</span></main>'
});
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: '<div id="app">ok</div>',
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 = '<span name="s1">text</span>';
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: '<main id="app"><span name="s1">text</span></main>'
});
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: '<iframe id="f"></iframe>'
});
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 等。