39 KiB
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.prototypegetter,而不是直接作为 document 自有属性。 - 如果当前 xbs API 已能覆盖目标行为但代码没有优先使用或参数错误,应先修正用法,不得标记为 native 能力缺口。
XBS API 使用说明
本文档说明当前魔改版 isolated-vm 中 window.xbs 的用法。
xbs 只会注入到 isolated-vm 创建出的浏览器式 Context 内,即 window.xbs / globalThis.xbs,不会污染宿主侧 require("isolated-vm") 的原始 API。
1. 基本使用方式
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 内可以直接使用:
const fn = xbs.createNativeFunction(false, "demo", 0, function () {
return "ok";
});
fn(); // "ok"
当前 Context 默认具备以下基础关系:
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 回调。 |
普通函数示例
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。
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 使用。
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。
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 |
实例工厂创建对象后的初始化函数。 |
创建继承链
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,但框架内部需要创建实例的对象。
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() 之间不会互相污染。
const registry = xbs.getProtoChainRegistry();
registry.constructors; // 构造函数名称数组
registry.aliases; // 别名映射对象
xbs.deleteProtoChainRegistryEntry("URL"); // 删除指定构造函数或别名
xbs.clearProtoChainRegistry(); // 清空当前 isolate 的注册表
8. createNativeObject(options)
创建带 native-like 构造函数、实例对象与可选父级原型链的对象。新代码更推荐使用 createProtoChains,但该 API 仍可用于兼容旧逻辑。
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 时调用。 |
示例
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 类似对象。
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。
默认用法
const {
mimeTypes,
plugins,
PluginArray,
MimeTypeArray,
MimeType,
Plugin,
} = xbs.getMimeTypesAndPlugins();
navigator.plugins = plugins;
navigator.mimeTypes = mimeTypes;
参数化配置
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 行为。
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 的自有属性:
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 自有属性:
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 对象上,同时避免普通属性枚举或直接访问。
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。
function assertIllegalInvocation(thisValue, Ctor) {
if (!(thisValue instanceof Ctor)) {
xbs.throwTypeError("Illegal invocation");
}
}
注意:
- 不传参数会抛出参数数量错误。
- 第一个参数不是字符串会抛出类型错误。
15. 推荐实践
- 所有非 WebAPI 的辅助能力都放在
window.xbs下,不要直接污染window。 - 对外可见的浏览器对象优先使用
createProtoChains创建构造函数与原型链。 - 对手写
document.all这类特殊对象才优先使用createUndetectable(callback, handlers);如果需要基础 DOM,应优先使用xbs.dom.createDocument()自动提供的document.all。 - 对
navigator.plugins/navigator.mimeTypes优先使用getMimeTypesAndPlugins。 - 对集合对象优先使用
createNativeCollection,不要直接用普通数组代替。 - 对内部状态优先使用 private slot,减少普通属性泄露。
- 多 isolate 场景下,原型链注册表会按 isolate 隔离;清理 isolate 时会同步清理对应 XBS 注册表。
- 宿主侧
isolated-vmAPI 保持原样,xbs只存在于创建出的 Context 内。
16. 快速自检
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");
也可以直接运行项目内测试:
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 只保留一个公开方法:
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. 快速示例
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)
参数
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。
const document = xbs.dom.createDocument({
omitApis: ["document.all"]
});
console.log("all" in document); // false
示例:创建一个不带 Document.prototype.createComment 的 document。
const document = xbs.dom.createDocument({
omitApis: ["Document.prototype.createComment"]
});
console.log(typeof document.createComment); // undefined
示例:禁用 iframe 的 contentDocument。
const document = xbs.dom.createDocument({
omitApis: ["HTMLIFrameElement.prototype.contentDocument"]
});
const iframe = document.createElement("iframe");
console.log(typeof iframe.contentDocument); // undefined
也支持通配前缀:
xbs.dom.createDocument({
omitApis: ["Element.prototype.*"]
});
6. 常用 DOM 能力
当前基础实现包含以下对象和能力:
HTMLDocument/DocumentNode/Element/HTMLElement/HTMLIFrameElementText/Comment/CDATASection/ProcessingInstruction/DocumentFragmentAttrDOMTokenList/NodeList/HTMLCollectionHTMLAllCollection/HTMLFormControlsCollection/HTMLOptionsCollection/RadioNodeListNamedNodeMap/DOMStringMap
常用示例:
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 新增属性
const document = xbs.dom.createDocument();
document.documentURI; // 与 document.URL 保持一致
document.embeds; // HTMLCollection
document.plugins; // HTMLCollection
当前 Document 可用的主要属性包括:
documentElementheadbodytitlereadyStateURLdocumentURIreferrercharacterSetcompatModecontentTypedefaultViewactiveElementdoctypeimplementationcurrentScriptallscriptsformsimageslinksanchorsembedspluginschildren
7.2 Document 新增创建方法
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 新增方法
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 新增属性和方法
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;
新增或补齐的属性:
namespaceURIprefixlocalNameslotdirshadowRootassignedSlotattributesdataset
新增或补齐的方法:
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() 取得。
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
主要属性:
namevaluenamespaceURIprefixlocalNamespecifiedownerElementnodeTypenodeNamenodeValuetextContent
7.6 NamedNodeMap
element.attributes 返回 NamedNodeMap,用于访问元素属性节点。
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:
lengthitem(index)getNamedItem(name)getNamedItemNS(namespaceURI, localName)setNamedItem(attr)setNamedItemNS(attr)removeNamedItem(name)removeNamedItemNS(namespaceURI, localName)
7.7 DOMStringMap
element.dataset 返回 DOMStringMap,会映射到元素的 data-* 属性。
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()
示例:
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自有属性,否则会和真实浏览器不一致。
最小结构示例:
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:
Object.defineProperty(document, "all", {
value: all,
enumerable: true,
configurable: true,
});
则 Object.hasOwn(document, "all") 会是 true。如果要更接近真实浏览器,应挂到 Document.prototype:
Object.defineProperty(Document.prototype, "all", {
get: xbs.createGetter("all", 0, function () {
return all;
}),
enumerable: true,
configurable: true,
});
8. 新增 API 的 omitApis 路径
新增 API 仍然遵循创建前禁用规则。常用路径示例:
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"
]
});
也可以继续使用通配前缀:
xbs.dom.createDocument({
omitApis: [
"Document.prototype.*",
"Element.prototype.*",
"Node.prototype.*"
]
});
9. iframe 行为
不再提供 createIframeDocument() 或 attachIframeDocument()。现在 iframe 的 contentDocument 会按需创建。
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 等。