1182 lines
39 KiB
Markdown
1182 lines
39 KiB
Markdown
# 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 等。
|