@proteus-vue/render-backend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/dist/basic-containers.d.ts +87 -0
- package/dist/borrow-checker.d.ts +21 -0
- package/dist/conformance-component.d.ts +32 -0
- package/dist/conformance.d.ts +18 -0
- package/dist/container-conformance.d.ts +53 -0
- package/dist/container-spi.d.ts +169 -0
- package/dist/dispatcher.d.ts +98 -0
- package/dist/flutter.d.ts +16 -0
- package/dist/headless.d.ts +12 -0
- package/dist/host-conformance.d.ts +71 -0
- package/dist/host-matrix.d.ts +74 -0
- package/dist/hot-switch.d.ts +24 -0
- package/dist/hybrid.d.ts +60 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +4291 -0
- package/dist/native.d.ts +42 -0
- package/dist/ownership-observability.d.ts +99 -0
- package/dist/ownership.d.ts +206 -0
- package/dist/page-ownership.d.ts +55 -0
- package/dist/pss.d.ts +54 -0
- package/dist/spi.d.ts +73 -0
- package/dist/stack-container.d.ts +43 -0
- package/dist/superapp-container.d.ts +47 -0
- package/dist/vue-bridge.d.ts +22 -0
- package/dist/vue-dom.d.ts +6 -0
- package/dist/web-host.d.ts +35 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4291 @@
|
|
|
1
|
+
// src/conformance.ts
|
|
2
|
+
var LAYOUT_VALUES = ["yoga", "native", "none"];
|
|
3
|
+
var GLASS_VALUES = ["L3", "L2", "L1", "none"];
|
|
4
|
+
var BLUR_VALUES = ["true", "approximate", "none"];
|
|
5
|
+
var ANIMATION_VALUES = ["native", "js", "none"];
|
|
6
|
+
var INPUT_VALUES = ["touch", "cursor", "remote", "dial", "voice"];
|
|
7
|
+
var REQUIRED_METHODS = ["createElement", "insert", "remove", "patchProp", "setText"];
|
|
8
|
+
var OPTIONAL_METHODS = [
|
|
9
|
+
"querySelector",
|
|
10
|
+
"measure",
|
|
11
|
+
"layout",
|
|
12
|
+
"scheduleFrame",
|
|
13
|
+
"flush",
|
|
14
|
+
"dispatchInput",
|
|
15
|
+
"onMount",
|
|
16
|
+
"onUnmount",
|
|
17
|
+
"registerExternalTexture",
|
|
18
|
+
"unregisterExternalTexture"
|
|
19
|
+
];
|
|
20
|
+
function runBackendConformance(backend) {
|
|
21
|
+
const checks = [];
|
|
22
|
+
function check(name, pass, detail) {
|
|
23
|
+
checks.push({ name, pass, detail });
|
|
24
|
+
}
|
|
25
|
+
check("id", typeof backend.id === "string" && backend.id.length > 0, `id=${String(backend.id)}`);
|
|
26
|
+
check("version", typeof backend.version === "string" && backend.version.length > 0, `version=${String(backend.version)}`);
|
|
27
|
+
for (const m of REQUIRED_METHODS) {
|
|
28
|
+
const fn = backend[m];
|
|
29
|
+
check(`method.${String(m)}`, typeof fn === "function", typeof fn === "function" ? void 0 : `\u7F3A\u5931\u65B9\u6CD5 ${String(m)}`);
|
|
30
|
+
}
|
|
31
|
+
let unique = true;
|
|
32
|
+
try {
|
|
33
|
+
const a = backend.createElement({ type: "view", props: {}, children: [] });
|
|
34
|
+
const b = backend.createElement({ type: "view", props: {}, children: [] });
|
|
35
|
+
unique = a !== b;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
unique = false;
|
|
38
|
+
check("createElement.unique", false, "createElement \u629B\u9519: " + e.message);
|
|
39
|
+
}
|
|
40
|
+
if (!checks.some((c) => c.name === "createElement.unique")) {
|
|
41
|
+
check("createElement.unique", unique, unique ? void 0 : "\u4E24\u6B21\u8C03\u7528\u8FD4\u56DE\u76F8\u540C\u53E5\u67C4\uFF08\u975E\u552F\u4E00\uFF09");
|
|
42
|
+
}
|
|
43
|
+
const caps = backend.capabilities;
|
|
44
|
+
if (!caps) {
|
|
45
|
+
check("capabilities", false, "\u7F3A\u5931 capabilities \u58F0\u660E");
|
|
46
|
+
} else {
|
|
47
|
+
check("capabilities.layout", LAYOUT_VALUES.indexOf(caps.layout) >= 0, `layout=${String(caps.layout)}`);
|
|
48
|
+
check("capabilities.glass", GLASS_VALUES.indexOf(caps.glass) >= 0, `glass=${String(caps.glass)}`);
|
|
49
|
+
check("capabilities.blur", BLUR_VALUES.indexOf(caps.blur) >= 0, `blur=${String(caps.blur)}`);
|
|
50
|
+
check("capabilities.animation", ANIMATION_VALUES.indexOf(caps.animation) >= 0, `animation=${String(caps.animation)}`);
|
|
51
|
+
const badInput = (caps.input || []).filter((i) => INPUT_VALUES.indexOf(i) < 0);
|
|
52
|
+
check("capabilities.input", badInput.length === 0, badInput.length ? `\u975E\u6CD5 input \u7C7B\u578B: ${badInput.join(",")}` : void 0);
|
|
53
|
+
}
|
|
54
|
+
for (const m of OPTIONAL_METHODS) {
|
|
55
|
+
const fn = backend[m];
|
|
56
|
+
if (fn !== void 0 && fn !== null && typeof fn !== "function") {
|
|
57
|
+
check(`optional.${String(m)}`, false, `\u53EF\u9009\u65B9\u6CD5 ${String(m)} \u975E\u51FD\u6570`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ok: checks.every((c) => c.pass), checks };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/headless.ts
|
|
64
|
+
function toPlainTree(root) {
|
|
65
|
+
return {
|
|
66
|
+
id: root.id,
|
|
67
|
+
type: root.type,
|
|
68
|
+
props: { ...root.props },
|
|
69
|
+
text: root.text,
|
|
70
|
+
children: root.children.map(toPlainTree)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
var SEMANTIC_HEADLESS_MAP = {
|
|
74
|
+
"layout.box": "box",
|
|
75
|
+
"layout.stack": "stack",
|
|
76
|
+
"layout.grid": "grid",
|
|
77
|
+
"layout.fluid": "fluid",
|
|
78
|
+
"layout.adaptive": "adaptive",
|
|
79
|
+
"layout.fit": "fit",
|
|
80
|
+
"layout.split": "split",
|
|
81
|
+
"layout.safe": "safe",
|
|
82
|
+
"layout.sidebar": "sidebar",
|
|
83
|
+
"ui.text": "text",
|
|
84
|
+
"ui.button": "button",
|
|
85
|
+
"ui.image": "image",
|
|
86
|
+
"ui.input": "input",
|
|
87
|
+
"ui.list": "list",
|
|
88
|
+
"ui.nav": "nav",
|
|
89
|
+
"capability.scan-qr": "scan-qr",
|
|
90
|
+
"capability.pick-photo": "pick-photo",
|
|
91
|
+
"capability.location": "location",
|
|
92
|
+
// ★G-32 B1:新增 implemented 语义(与 component-ir SEMANTIC_BACKEND_MAP headless 列同源)
|
|
93
|
+
"layout.inline": "inline",
|
|
94
|
+
"layout.spacer": "spacer",
|
|
95
|
+
"layout.divider": "divider",
|
|
96
|
+
"layout.scroll": "scroll",
|
|
97
|
+
"layout.virtual-list": "virtual-list",
|
|
98
|
+
"layout.masonry": "masonry",
|
|
99
|
+
"ui.heading": "heading",
|
|
100
|
+
"ui.icon": "icon",
|
|
101
|
+
"ui.textarea": "textarea",
|
|
102
|
+
"ui.switch": "switch",
|
|
103
|
+
"ui.slider": "slider",
|
|
104
|
+
"shell.nav": "nav",
|
|
105
|
+
"shell.tabbar": "tabbar",
|
|
106
|
+
"shell.drawer": "drawer",
|
|
107
|
+
"shell.modal": "modal",
|
|
108
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐(与 SEMANTIC_BACKEND_MAP headless 列同源)
|
|
109
|
+
"shell.page": "page",
|
|
110
|
+
"shell.segment": "segment",
|
|
111
|
+
"shell.popover": "popover",
|
|
112
|
+
"shell.action-sheet": "action-sheet",
|
|
113
|
+
"ui.rich-text": "rich-text",
|
|
114
|
+
"ui.avatar": "avatar",
|
|
115
|
+
"ui.media": "media",
|
|
116
|
+
"ui.canvas": "canvas",
|
|
117
|
+
"ui.svg": "svg",
|
|
118
|
+
"ui.select": "select",
|
|
119
|
+
"ui.checkbox": "checkbox",
|
|
120
|
+
"ui.radio": "radio",
|
|
121
|
+
"ui.picker": "picker",
|
|
122
|
+
"ui.form": "form",
|
|
123
|
+
"gesture.draggable": "draggable",
|
|
124
|
+
"gesture.scrollable": "scrollable",
|
|
125
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link)
|
|
126
|
+
"engineering.router-link": "router-link",
|
|
127
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20)
|
|
128
|
+
"engineering.transition": "transition",
|
|
129
|
+
"engineering.animate": "animate"
|
|
130
|
+
};
|
|
131
|
+
var HEADLESS_CAPABILITIES = {
|
|
132
|
+
layout: "none",
|
|
133
|
+
// 框架 IR 求解(headless 无布局器)
|
|
134
|
+
glass: "none",
|
|
135
|
+
blur: "none",
|
|
136
|
+
animation: "js",
|
|
137
|
+
textureSharing: false,
|
|
138
|
+
remoteRendering: false,
|
|
139
|
+
ssr: true,
|
|
140
|
+
input: ["touch"]
|
|
141
|
+
};
|
|
142
|
+
function createHeadlessBackend() {
|
|
143
|
+
let nextId = 1;
|
|
144
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
145
|
+
function ensureNode(handle) {
|
|
146
|
+
const n = handle;
|
|
147
|
+
if (!n || typeof n.id !== "number") throw new Error("HeadlessBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
148
|
+
return n;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
id: "headless",
|
|
152
|
+
version: "0.1.0",
|
|
153
|
+
capabilities: HEADLESS_CAPABILITIES,
|
|
154
|
+
createElement(node) {
|
|
155
|
+
const viewType = node.semantic ? SEMANTIC_HEADLESS_MAP[node.semantic] ?? node.type : node.type;
|
|
156
|
+
const n = {
|
|
157
|
+
id: nextId++,
|
|
158
|
+
type: viewType,
|
|
159
|
+
props: { ...node.props },
|
|
160
|
+
children: [],
|
|
161
|
+
parent: null,
|
|
162
|
+
text: ""
|
|
163
|
+
};
|
|
164
|
+
nodes.set(n.id, n);
|
|
165
|
+
return n;
|
|
166
|
+
},
|
|
167
|
+
insert(child, parent, anchor) {
|
|
168
|
+
const c = ensureNode(child);
|
|
169
|
+
const p = ensureNode(parent);
|
|
170
|
+
if (c.parent) {
|
|
171
|
+
const oldIdx = c.parent.children.indexOf(c);
|
|
172
|
+
if (oldIdx >= 0) c.parent.children.splice(oldIdx, 1);
|
|
173
|
+
}
|
|
174
|
+
if (anchor) {
|
|
175
|
+
const a = ensureNode(anchor);
|
|
176
|
+
const idx = p.children.indexOf(a);
|
|
177
|
+
p.children.splice(idx >= 0 ? idx : p.children.length, 0, c);
|
|
178
|
+
} else {
|
|
179
|
+
p.children.push(c);
|
|
180
|
+
}
|
|
181
|
+
c.parent = p;
|
|
182
|
+
},
|
|
183
|
+
remove(child) {
|
|
184
|
+
const c = ensureNode(child);
|
|
185
|
+
if (c.parent) {
|
|
186
|
+
const idx = c.parent.children.indexOf(c);
|
|
187
|
+
if (idx >= 0) c.parent.children.splice(idx, 1);
|
|
188
|
+
c.parent = null;
|
|
189
|
+
}
|
|
190
|
+
nodes.delete(c.id);
|
|
191
|
+
},
|
|
192
|
+
patchProp(el, key, prev, next) {
|
|
193
|
+
const n = ensureNode(el);
|
|
194
|
+
if (next === null || next === void 0) {
|
|
195
|
+
delete n.props[key];
|
|
196
|
+
} else {
|
|
197
|
+
n.props[key] = next;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
setText(el, text) {
|
|
201
|
+
ensureNode(el).text = text;
|
|
202
|
+
},
|
|
203
|
+
measure() {
|
|
204
|
+
return { width: 0, height: 0 };
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/vue-dom.ts
|
|
210
|
+
function defaultDocument() {
|
|
211
|
+
const g = globalThis;
|
|
212
|
+
if (g.document && typeof g.document.createElement === "function") {
|
|
213
|
+
return g.document;
|
|
214
|
+
}
|
|
215
|
+
throw new Error("VueDomBackend: \u65E0 document \u73AF\u5883\uFF08SSR/Node\u2014\u2014\u8BF7\u6CE8\u5165 documentLike \u6216\u6539\u7528 HeadlessBackend\uFF09");
|
|
216
|
+
}
|
|
217
|
+
var VUE_DOM_CAPABILITIES = {
|
|
218
|
+
layout: "native",
|
|
219
|
+
// 浏览器原生布局
|
|
220
|
+
glass: "L1",
|
|
221
|
+
// backdrop-filter(基础玻璃)
|
|
222
|
+
blur: "approximate",
|
|
223
|
+
animation: "js",
|
|
224
|
+
textureSharing: false,
|
|
225
|
+
remoteRendering: false,
|
|
226
|
+
ssr: false,
|
|
227
|
+
input: ["touch", "cursor"]
|
|
228
|
+
};
|
|
229
|
+
var SEMANTIC_WEB_MAP = {
|
|
230
|
+
"layout.box": { tag: "div", className: "proteus-box" },
|
|
231
|
+
"layout.stack": { tag: "div", className: "proteus-stack" },
|
|
232
|
+
"layout.grid": { tag: "div", className: "proteus-grid" },
|
|
233
|
+
"layout.fluid": { tag: "div", className: "proteus-fluid" },
|
|
234
|
+
"layout.adaptive": { tag: "div", className: "proteus-adaptive" },
|
|
235
|
+
"layout.fit": { tag: "div", className: "proteus-fit" },
|
|
236
|
+
"layout.split": { tag: "div", className: "proteus-split" },
|
|
237
|
+
"layout.safe": { tag: "div", className: "proteus-safe" },
|
|
238
|
+
"layout.sidebar": { tag: "div", className: "proteus-sidebar" },
|
|
239
|
+
"ui.text": { tag: "span" },
|
|
240
|
+
"ui.button": { tag: "button" },
|
|
241
|
+
"ui.image": { tag: "img" },
|
|
242
|
+
"ui.input": { tag: "input" },
|
|
243
|
+
"ui.list": { tag: "div", className: "proteus-list" },
|
|
244
|
+
"ui.nav": { tag: "nav" },
|
|
245
|
+
// ★G-31 B5:能力入口(G-28 组件化)——Web 端用标准元素承载,能力实现由 useNative 注入
|
|
246
|
+
"capability.scan-qr": { tag: "button", className: "proteus-scan-qr" },
|
|
247
|
+
"capability.pick-photo": { tag: "input", className: "proteus-pick-photo" },
|
|
248
|
+
"capability.location": { tag: "button", className: "proteus-location" },
|
|
249
|
+
// ★G-32 B1:新增 implemented 语义(与 component-ir SEMANTIC_BACKEND_MAP vue-dom 列同源)
|
|
250
|
+
"layout.inline": { tag: "div", className: "proteus-inline" },
|
|
251
|
+
"layout.spacer": { tag: "div", className: "proteus-spacer" },
|
|
252
|
+
"layout.divider": { tag: "hr", className: "proteus-divider" },
|
|
253
|
+
"layout.scroll": { tag: "div", className: "proteus-scroll" },
|
|
254
|
+
"layout.virtual-list": { tag: "div", className: "proteus-virtual-list" },
|
|
255
|
+
"layout.masonry": { tag: "div", className: "proteus-masonry" },
|
|
256
|
+
"ui.heading": { tag: "div", className: "proteus-heading" },
|
|
257
|
+
"ui.icon": { tag: "span", className: "proteus-icon" },
|
|
258
|
+
"ui.textarea": { tag: "textarea" },
|
|
259
|
+
"ui.switch": { tag: "div", className: "proteus-switch" },
|
|
260
|
+
"ui.slider": { tag: "div", className: "proteus-slider" },
|
|
261
|
+
"shell.nav": { tag: "nav", className: "proteus-nav" },
|
|
262
|
+
"shell.tabbar": { tag: "nav", className: "proteus-tabbar" },
|
|
263
|
+
"shell.drawer": { tag: "aside", className: "proteus-drawer" },
|
|
264
|
+
"shell.modal": { tag: "div", className: "proteus-modal" },
|
|
265
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐(与 SEMANTIC_BACKEND_MAP vue-dom 列同源)
|
|
266
|
+
"shell.page": { tag: "div", className: "proteus-page" },
|
|
267
|
+
"shell.segment": { tag: "div", className: "proteus-segment" },
|
|
268
|
+
"shell.popover": { tag: "div", className: "proteus-popover" },
|
|
269
|
+
"shell.action-sheet": { tag: "div", className: "proteus-action-sheet" },
|
|
270
|
+
"ui.rich-text": { tag: "div", className: "proteus-rich-text" },
|
|
271
|
+
"ui.avatar": { tag: "div", className: "proteus-avatar" },
|
|
272
|
+
"ui.media": { tag: "div", className: "proteus-media" },
|
|
273
|
+
"ui.canvas": { tag: "canvas" },
|
|
274
|
+
"ui.svg": { tag: "svg" },
|
|
275
|
+
"ui.select": { tag: "div", className: "proteus-select" },
|
|
276
|
+
"ui.checkbox": { tag: "div", className: "proteus-checkbox" },
|
|
277
|
+
"ui.radio": { tag: "div", className: "proteus-radio" },
|
|
278
|
+
"ui.picker": { tag: "div", className: "proteus-picker" },
|
|
279
|
+
"ui.form": { tag: "form" },
|
|
280
|
+
// ★G-32 B4 ④ Gesture 组件形态
|
|
281
|
+
"gesture.draggable": { tag: "div", className: "proteus-draggable" },
|
|
282
|
+
"gesture.scrollable": { tag: "div", className: "proteus-scrollable" },
|
|
283
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link——a 元素语义)
|
|
284
|
+
"engineering.router-link": { tag: "a", className: "proteus-router-link" },
|
|
285
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20——纯 CSS 声明)
|
|
286
|
+
"engineering.transition": { tag: "div", className: "proteus-transition" },
|
|
287
|
+
"engineering.animate": { tag: "div", className: "proteus-animate" }
|
|
288
|
+
};
|
|
289
|
+
function createVueDomBackend(doc) {
|
|
290
|
+
const documentLike = doc ?? defaultDocument();
|
|
291
|
+
function ensureEl(handle) {
|
|
292
|
+
const el = handle;
|
|
293
|
+
if (!el || typeof el.tagName !== "string") throw new Error("VueDomBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
294
|
+
return el;
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
id: "vue-dom",
|
|
298
|
+
version: "0.1.0",
|
|
299
|
+
capabilities: VUE_DOM_CAPABILITIES,
|
|
300
|
+
createElement(node) {
|
|
301
|
+
if (node.semantic) {
|
|
302
|
+
const mapped = SEMANTIC_WEB_MAP[node.semantic];
|
|
303
|
+
if (mapped) {
|
|
304
|
+
const el = documentLike.createElement(mapped.tag);
|
|
305
|
+
if (mapped.className) el.setAttribute("class", mapped.className);
|
|
306
|
+
return el;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return documentLike.createElement(node.type);
|
|
310
|
+
},
|
|
311
|
+
insert(child, parent, anchor) {
|
|
312
|
+
const c = ensureEl(child);
|
|
313
|
+
const p = ensureEl(parent);
|
|
314
|
+
if (anchor) {
|
|
315
|
+
const a = ensureEl(anchor);
|
|
316
|
+
if (a.parentNode === p) {
|
|
317
|
+
p.insertBefore(c, a);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
p.appendChild(c);
|
|
322
|
+
},
|
|
323
|
+
remove(child) {
|
|
324
|
+
const c = ensureEl(child);
|
|
325
|
+
if (c.parentNode) c.parentNode.removeChild(c);
|
|
326
|
+
},
|
|
327
|
+
patchProp(el, key, prev, next) {
|
|
328
|
+
const target = ensureEl(el);
|
|
329
|
+
if (key.startsWith("on") && key.length > 2) {
|
|
330
|
+
const eventName = key.slice(2).toLowerCase();
|
|
331
|
+
if (typeof next === "function") {
|
|
332
|
+
if (prev !== next) target.addEventListener(eventName, next);
|
|
333
|
+
} else {
|
|
334
|
+
target.removeEventListener(eventName, prev);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (key === "style" && next && typeof next === "object") {
|
|
339
|
+
const style = next;
|
|
340
|
+
for (const k of Object.keys(style)) {
|
|
341
|
+
;
|
|
342
|
+
target.style[k] = style[k];
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (next === null || next === void 0) {
|
|
347
|
+
target.removeAttribute(key);
|
|
348
|
+
} else {
|
|
349
|
+
target.setAttribute(key, String(next));
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
setText(el, text) {
|
|
353
|
+
ensureEl(el).textContent = text;
|
|
354
|
+
},
|
|
355
|
+
measure(node, constraints) {
|
|
356
|
+
const el = ensureEl(node);
|
|
357
|
+
const w = constraints?.width ?? el.clientWidth ?? 0;
|
|
358
|
+
const h = constraints?.height ?? el.clientHeight ?? 0;
|
|
359
|
+
return { width: w, height: h };
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/native.ts
|
|
365
|
+
function createMockNativeAdapter() {
|
|
366
|
+
const ops = [];
|
|
367
|
+
return {
|
|
368
|
+
ops,
|
|
369
|
+
createView(descriptor) {
|
|
370
|
+
ops.push(`create:${descriptor.type}`);
|
|
371
|
+
return descriptor;
|
|
372
|
+
},
|
|
373
|
+
updateView(_handle, key, _prev, next) {
|
|
374
|
+
ops.push(`update:${key}=${String(next)}`);
|
|
375
|
+
},
|
|
376
|
+
insertView(child, _parent, _anchor) {
|
|
377
|
+
ops.push(`insert:${String(child.type)}`);
|
|
378
|
+
},
|
|
379
|
+
removeView(child) {
|
|
380
|
+
ops.push(`remove:${String(child.type)}`);
|
|
381
|
+
},
|
|
382
|
+
setViewText(_handle, text) {
|
|
383
|
+
ops.push(`setText:${text}`);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
var NATIVE_CAPABILITIES = {
|
|
388
|
+
layout: "native",
|
|
389
|
+
// 原生布局(AutoLayout / ConstraintLayout / ArkUI 约束)
|
|
390
|
+
glass: "L3",
|
|
391
|
+
// iOS UIGlassEffect / 鸿蒙 fractal(G-07 系统级玻璃语义)
|
|
392
|
+
blur: "true",
|
|
393
|
+
animation: "native",
|
|
394
|
+
// 系统原生转场/动画
|
|
395
|
+
textureSharing: true,
|
|
396
|
+
// PlatformView / TextureView 混合
|
|
397
|
+
remoteRendering: false,
|
|
398
|
+
ssr: false,
|
|
399
|
+
input: ["touch", "cursor", "remote"]
|
|
400
|
+
};
|
|
401
|
+
var SEMANTIC_NATIVE_MAPS = {
|
|
402
|
+
ios: {
|
|
403
|
+
"layout.box": "UIView",
|
|
404
|
+
"layout.stack": "UIStackView",
|
|
405
|
+
"layout.grid": "UICollectionView",
|
|
406
|
+
"layout.fluid": "UIView.fluid",
|
|
407
|
+
"layout.adaptive": "UISheet",
|
|
408
|
+
"layout.fit": "UIView.fit",
|
|
409
|
+
"layout.split": "UISplitViewController",
|
|
410
|
+
"layout.safe": "safeAreaLayoutGuide",
|
|
411
|
+
"layout.sidebar": "UISplitViewController.side",
|
|
412
|
+
"ui.text": "UILabel",
|
|
413
|
+
"ui.button": "UIButton",
|
|
414
|
+
"ui.image": "UIImageView",
|
|
415
|
+
"ui.input": "UITextField",
|
|
416
|
+
"ui.list": "UITableView",
|
|
417
|
+
"ui.nav": "UINavigationController",
|
|
418
|
+
"capability.scan-qr": "AVCaptureSession",
|
|
419
|
+
"capability.pick-photo": "UIImagePicker",
|
|
420
|
+
"capability.location": "CLLocationManager",
|
|
421
|
+
// ★G-32 B1:新增 implemented 语义
|
|
422
|
+
"layout.inline": "UITextAttachment",
|
|
423
|
+
"layout.spacer": "UILayoutGuide",
|
|
424
|
+
"layout.divider": "UIView.divider",
|
|
425
|
+
"layout.scroll": "UIScrollView",
|
|
426
|
+
"layout.virtual-list": "UICollectionView",
|
|
427
|
+
"layout.masonry": "UICollectionView.masonry",
|
|
428
|
+
"ui.heading": "UILabel.heading",
|
|
429
|
+
"ui.icon": "UIImageView.icon",
|
|
430
|
+
"ui.textarea": "UITextView",
|
|
431
|
+
"ui.switch": "UISwitch",
|
|
432
|
+
"ui.slider": "UISlider",
|
|
433
|
+
"shell.nav": "UINavigationBar",
|
|
434
|
+
"shell.tabbar": "UITabBar",
|
|
435
|
+
"shell.drawer": "UIView.drawer",
|
|
436
|
+
"shell.modal": "UIAlertController",
|
|
437
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐
|
|
438
|
+
"shell.page": "UIViewController",
|
|
439
|
+
"shell.segment": "UISegmentedControl",
|
|
440
|
+
"shell.popover": "UIPopoverController",
|
|
441
|
+
"shell.action-sheet": "UIAlertController.actionSheet",
|
|
442
|
+
"ui.rich-text": "UITextView.attributed",
|
|
443
|
+
"ui.avatar": "UIImageView.avatar",
|
|
444
|
+
"ui.media": "AVPlayerView",
|
|
445
|
+
"ui.canvas": "UIView.canvas",
|
|
446
|
+
"ui.svg": "UIView.svg",
|
|
447
|
+
"ui.select": "UIPickerView",
|
|
448
|
+
"ui.checkbox": "UIButton.checkbox",
|
|
449
|
+
"ui.radio": "UIButton.radio",
|
|
450
|
+
"ui.picker": "UIDatePicker",
|
|
451
|
+
"ui.form": "UIView.form",
|
|
452
|
+
"gesture.draggable": "UIPanGestureRecognizer",
|
|
453
|
+
"gesture.scrollable": "UIScrollView.gesture",
|
|
454
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link)
|
|
455
|
+
"engineering.router-link": "UIButton.link",
|
|
456
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20)
|
|
457
|
+
"engineering.transition": "UIView.transition",
|
|
458
|
+
"engineering.animate": "CAKeyframeAnimation"
|
|
459
|
+
},
|
|
460
|
+
android: {
|
|
461
|
+
"layout.box": "FrameLayout",
|
|
462
|
+
"layout.stack": "LinearLayout",
|
|
463
|
+
"layout.grid": "GridLayoutManager",
|
|
464
|
+
"layout.fluid": "ConstraintLayout",
|
|
465
|
+
"layout.adaptive": "BottomSheetDialog",
|
|
466
|
+
"layout.fit": "wrapContent",
|
|
467
|
+
"layout.split": "SlidingPaneLayout",
|
|
468
|
+
"layout.safe": "WindowInsets",
|
|
469
|
+
"layout.sidebar": "NavigationRail",
|
|
470
|
+
"ui.text": "TextView",
|
|
471
|
+
"ui.button": "Button",
|
|
472
|
+
"ui.image": "ImageView",
|
|
473
|
+
"ui.input": "EditText",
|
|
474
|
+
"ui.list": "RecyclerView",
|
|
475
|
+
"ui.nav": "NavigationRail",
|
|
476
|
+
"capability.scan-qr": "CameraX",
|
|
477
|
+
"capability.pick-photo": "PhotoPicker",
|
|
478
|
+
"capability.location": "FusedLocation",
|
|
479
|
+
// ★G-32 B1:新增 implemented 语义
|
|
480
|
+
"layout.inline": "TextView.inline",
|
|
481
|
+
"layout.spacer": "Space",
|
|
482
|
+
"layout.divider": "View.divider",
|
|
483
|
+
"layout.scroll": "ScrollView",
|
|
484
|
+
"layout.virtual-list": "RecyclerView",
|
|
485
|
+
"layout.masonry": "StaggeredGridLayoutManager",
|
|
486
|
+
"ui.heading": "TextView.heading",
|
|
487
|
+
"ui.icon": "ImageView.icon",
|
|
488
|
+
"ui.textarea": "EditText.multiline",
|
|
489
|
+
"ui.switch": "Switch",
|
|
490
|
+
"ui.slider": "SeekBar",
|
|
491
|
+
"shell.nav": "Toolbar",
|
|
492
|
+
"shell.tabbar": "BottomNavigationView",
|
|
493
|
+
"shell.drawer": "DrawerLayout",
|
|
494
|
+
"shell.modal": "Dialog",
|
|
495
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐
|
|
496
|
+
"shell.page": "Activity",
|
|
497
|
+
"shell.segment": "TabLayout",
|
|
498
|
+
"shell.popover": "PopupWindow",
|
|
499
|
+
"shell.action-sheet": "BottomSheet",
|
|
500
|
+
"ui.rich-text": "TextView.html",
|
|
501
|
+
"ui.avatar": "ImageView.avatar",
|
|
502
|
+
"ui.media": "VideoView",
|
|
503
|
+
"ui.canvas": "SurfaceView",
|
|
504
|
+
"ui.svg": "VectorDrawable",
|
|
505
|
+
"ui.select": "Spinner",
|
|
506
|
+
"ui.checkbox": "CheckBox",
|
|
507
|
+
"ui.radio": "RadioButton",
|
|
508
|
+
"ui.picker": "DatePicker",
|
|
509
|
+
"ui.form": "LinearLayout.form",
|
|
510
|
+
"gesture.draggable": "GestureDetector",
|
|
511
|
+
"gesture.scrollable": "NestedScrollView",
|
|
512
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link)
|
|
513
|
+
"engineering.router-link": "TextView.link",
|
|
514
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20)
|
|
515
|
+
"engineering.transition": "View.animate.transition",
|
|
516
|
+
"engineering.animate": "ValueAnimator"
|
|
517
|
+
},
|
|
518
|
+
harmony: {
|
|
519
|
+
"layout.box": "Stack",
|
|
520
|
+
"layout.stack": "Flex",
|
|
521
|
+
"layout.grid": "Grid",
|
|
522
|
+
"layout.fluid": "Flex.fluid",
|
|
523
|
+
"layout.adaptive": "Sheet",
|
|
524
|
+
"layout.fit": "fitContent",
|
|
525
|
+
"layout.split": "SideBarContainer",
|
|
526
|
+
"layout.safe": "getAvoidArea",
|
|
527
|
+
"layout.sidebar": "SideBarContainer",
|
|
528
|
+
"ui.text": "Text",
|
|
529
|
+
"ui.button": "Button",
|
|
530
|
+
"ui.image": "Image",
|
|
531
|
+
"ui.input": "TextInput",
|
|
532
|
+
"ui.list": "List",
|
|
533
|
+
"ui.nav": "Navigation",
|
|
534
|
+
"capability.scan-qr": "ScanKit",
|
|
535
|
+
"capability.pick-photo": "PhotoViewPicker",
|
|
536
|
+
"capability.location": "geoLocationManager",
|
|
537
|
+
// ★G-32 B1:新增 implemented 语义
|
|
538
|
+
"layout.inline": "Span",
|
|
539
|
+
"layout.spacer": "Blank",
|
|
540
|
+
"layout.divider": "Divider",
|
|
541
|
+
"layout.scroll": "Scroll",
|
|
542
|
+
"layout.virtual-list": "List",
|
|
543
|
+
"layout.masonry": "WaterFlow",
|
|
544
|
+
"ui.heading": "Text.heading",
|
|
545
|
+
"ui.icon": "SymbolGlyph",
|
|
546
|
+
"ui.textarea": "TextArea",
|
|
547
|
+
"ui.switch": "Toggle",
|
|
548
|
+
"ui.slider": "Slider",
|
|
549
|
+
"shell.nav": "NavigationBar",
|
|
550
|
+
"shell.tabbar": "Tabs",
|
|
551
|
+
"shell.drawer": "Panel",
|
|
552
|
+
"shell.modal": "CustomDialog",
|
|
553
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐
|
|
554
|
+
"shell.page": "Page",
|
|
555
|
+
"shell.segment": "Segmented",
|
|
556
|
+
"shell.popover": "Popup",
|
|
557
|
+
"shell.action-sheet": "ActionSheet",
|
|
558
|
+
"ui.rich-text": "RichText",
|
|
559
|
+
"ui.avatar": "Image.avatar",
|
|
560
|
+
"ui.media": "Video",
|
|
561
|
+
"ui.canvas": "Canvas",
|
|
562
|
+
"ui.svg": "Shape",
|
|
563
|
+
"ui.select": "Select",
|
|
564
|
+
"ui.checkbox": "Checkbox",
|
|
565
|
+
"ui.radio": "Radio",
|
|
566
|
+
"ui.picker": "DatePicker",
|
|
567
|
+
"ui.form": "FormComponent",
|
|
568
|
+
"gesture.draggable": "PanGesture",
|
|
569
|
+
"gesture.scrollable": "Scroll.gesture",
|
|
570
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link)
|
|
571
|
+
"engineering.router-link": "Text.link",
|
|
572
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20)
|
|
573
|
+
"engineering.transition": "animateTo.transition",
|
|
574
|
+
"engineering.animate": "Animator.transition"
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
function createNativeBackend(adapter, platform = "ios") {
|
|
578
|
+
const viewAdapter = adapter ?? createMockNativeAdapter();
|
|
579
|
+
const semanticMap = SEMANTIC_NATIVE_MAPS[platform];
|
|
580
|
+
const id = platform === "ios" ? "native-ios" : platform === "android" ? "native-android" : "native-harmony";
|
|
581
|
+
let nextId = 1;
|
|
582
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
583
|
+
function ensureNode(handle) {
|
|
584
|
+
const n = handle;
|
|
585
|
+
if (!n || typeof n.id !== "number") throw new Error("NativeBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
586
|
+
return n;
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
id,
|
|
590
|
+
version: "0.1.0",
|
|
591
|
+
capabilities: NATIVE_CAPABILITIES,
|
|
592
|
+
createElement(node) {
|
|
593
|
+
const viewType = node.semantic ? semanticMap[node.semantic] ?? node.type : node.type;
|
|
594
|
+
const descriptor = {
|
|
595
|
+
id: nextId++,
|
|
596
|
+
type: viewType,
|
|
597
|
+
props: { ...node.props },
|
|
598
|
+
children: [],
|
|
599
|
+
parent: null,
|
|
600
|
+
text: "",
|
|
601
|
+
handle: null
|
|
602
|
+
};
|
|
603
|
+
descriptor.handle = viewAdapter.createView(descriptor);
|
|
604
|
+
nodes.set(descriptor.id, descriptor);
|
|
605
|
+
return descriptor;
|
|
606
|
+
},
|
|
607
|
+
insert(child, parent, anchor) {
|
|
608
|
+
const c = ensureNode(child);
|
|
609
|
+
const p = ensureNode(parent);
|
|
610
|
+
if (c.parent) {
|
|
611
|
+
const oldIdx = c.parent.children.indexOf(c);
|
|
612
|
+
if (oldIdx >= 0) c.parent.children.splice(oldIdx, 1);
|
|
613
|
+
}
|
|
614
|
+
if (anchor) {
|
|
615
|
+
const a = ensureNode(anchor);
|
|
616
|
+
const idx = p.children.indexOf(a);
|
|
617
|
+
p.children.splice(idx >= 0 ? idx : p.children.length, 0, c);
|
|
618
|
+
} else {
|
|
619
|
+
p.children.push(c);
|
|
620
|
+
}
|
|
621
|
+
c.parent = p;
|
|
622
|
+
viewAdapter.insertView(c.handle, p.handle, anchor ? ensureNode(anchor).handle : void 0);
|
|
623
|
+
},
|
|
624
|
+
remove(child) {
|
|
625
|
+
const c = ensureNode(child);
|
|
626
|
+
if (c.parent) {
|
|
627
|
+
const idx = c.parent.children.indexOf(c);
|
|
628
|
+
if (idx >= 0) c.parent.children.splice(idx, 1);
|
|
629
|
+
c.parent = null;
|
|
630
|
+
}
|
|
631
|
+
nodes.delete(c.id);
|
|
632
|
+
viewAdapter.removeView(c.handle);
|
|
633
|
+
},
|
|
634
|
+
patchProp(el, key, prev, next) {
|
|
635
|
+
const n = ensureNode(el);
|
|
636
|
+
if (next === null || next === void 0) {
|
|
637
|
+
delete n.props[key];
|
|
638
|
+
} else {
|
|
639
|
+
n.props[key] = next;
|
|
640
|
+
}
|
|
641
|
+
viewAdapter.updateView(n.handle, key, prev, next);
|
|
642
|
+
},
|
|
643
|
+
setText(el, text) {
|
|
644
|
+
const n = ensureNode(el);
|
|
645
|
+
n.text = text;
|
|
646
|
+
viewAdapter.setViewText(n.handle, text);
|
|
647
|
+
},
|
|
648
|
+
measure() {
|
|
649
|
+
return { width: 0, height: 0 };
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/flutter.ts
|
|
655
|
+
var WIDGET_MAP = {
|
|
656
|
+
view: "Container",
|
|
657
|
+
text: "Text",
|
|
658
|
+
button: "FilledButton",
|
|
659
|
+
image: "Image",
|
|
660
|
+
input: "TextField",
|
|
661
|
+
textarea: "TextField",
|
|
662
|
+
"scroll-view": "SingleChildScrollView",
|
|
663
|
+
switch: "Switch",
|
|
664
|
+
slider: "Slider",
|
|
665
|
+
icon: "Icon",
|
|
666
|
+
progress: "LinearProgressIndicator",
|
|
667
|
+
navigator: "GestureDetector",
|
|
668
|
+
"p-grid": "Wrap",
|
|
669
|
+
"p-stack": "Flex",
|
|
670
|
+
"p-split": "Row"
|
|
671
|
+
};
|
|
672
|
+
var SEMANTIC_FLUTTER_MAP = {
|
|
673
|
+
"layout.box": "Container",
|
|
674
|
+
"layout.stack": "Flex",
|
|
675
|
+
"layout.grid": "GridView",
|
|
676
|
+
"layout.fluid": "Wrap",
|
|
677
|
+
"layout.adaptive": "showModal",
|
|
678
|
+
"layout.fit": "IntrinsicWidth",
|
|
679
|
+
"layout.split": "Row",
|
|
680
|
+
"layout.safe": "SafeArea",
|
|
681
|
+
"layout.sidebar": "NavigationRail",
|
|
682
|
+
"ui.text": "Text",
|
|
683
|
+
"ui.button": "FilledButton",
|
|
684
|
+
"ui.image": "Image",
|
|
685
|
+
"ui.input": "TextField",
|
|
686
|
+
"ui.list": "ListView",
|
|
687
|
+
"ui.nav": "Navigator",
|
|
688
|
+
"capability.scan-qr": "scanQR",
|
|
689
|
+
"capability.pick-photo": "pickPhoto",
|
|
690
|
+
"capability.location": "getLocation",
|
|
691
|
+
// ★G-32 B1:新增 implemented 语义
|
|
692
|
+
"layout.inline": "InlineSpan",
|
|
693
|
+
"layout.spacer": "Spacer",
|
|
694
|
+
"layout.divider": "Divider",
|
|
695
|
+
"layout.scroll": "ScrollView",
|
|
696
|
+
"layout.virtual-list": "ListView",
|
|
697
|
+
"layout.masonry": "SliverMasonryGrid",
|
|
698
|
+
"ui.heading": "Text.heading",
|
|
699
|
+
"ui.icon": "Icon",
|
|
700
|
+
"ui.textarea": "TextField.multiline",
|
|
701
|
+
"ui.switch": "Switch",
|
|
702
|
+
"ui.slider": "Slider",
|
|
703
|
+
"shell.nav": "AppBar",
|
|
704
|
+
"shell.tabbar": "BottomNavigationBar",
|
|
705
|
+
"shell.drawer": "Drawer",
|
|
706
|
+
"shell.modal": "showDialog",
|
|
707
|
+
// ★G-32 B4:Shell 补齐 + UI 补齐
|
|
708
|
+
"shell.page": "Scaffold",
|
|
709
|
+
"shell.segment": "SegmentedButton",
|
|
710
|
+
"shell.popover": "showMenu",
|
|
711
|
+
"shell.action-sheet": "showModalBottomSheet",
|
|
712
|
+
"ui.rich-text": "RichText",
|
|
713
|
+
"ui.avatar": "CircleAvatar",
|
|
714
|
+
"ui.media": "VideoPlayer",
|
|
715
|
+
"ui.canvas": "CustomPaint",
|
|
716
|
+
"ui.svg": "SvgPicture",
|
|
717
|
+
"ui.select": "DropdownButton",
|
|
718
|
+
"ui.checkbox": "Checkbox",
|
|
719
|
+
"ui.radio": "Radio",
|
|
720
|
+
"ui.picker": "showDatePicker",
|
|
721
|
+
"ui.form": "Form",
|
|
722
|
+
"gesture.draggable": "Draggable",
|
|
723
|
+
"gesture.scrollable": "Scrollable",
|
|
724
|
+
// ★G-32 B5 尾巴:E18 声明式导航(p-router-link)
|
|
725
|
+
"engineering.router-link": "TextButton",
|
|
726
|
+
// ★G-32 B5 续二:工程原语动画组件形态(E19/E20)
|
|
727
|
+
"engineering.transition": "AnimatedOpacity",
|
|
728
|
+
"engineering.animate": "AnimationController"
|
|
729
|
+
};
|
|
730
|
+
function mapWidgetType(type) {
|
|
731
|
+
return WIDGET_MAP[type] ?? type;
|
|
732
|
+
}
|
|
733
|
+
function toWidgetTree(root) {
|
|
734
|
+
return {
|
|
735
|
+
id: root.id,
|
|
736
|
+
widget: root.widget,
|
|
737
|
+
props: { ...root.props },
|
|
738
|
+
text: root.text,
|
|
739
|
+
children: root.children.map(toWidgetTree)
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
var FLUTTER_CAPABILITIES = {
|
|
743
|
+
layout: "yoga",
|
|
744
|
+
// Flutter 自带布局引擎(Yoga/C++——后端可选,spike 标注)
|
|
745
|
+
glass: "L3",
|
|
746
|
+
// Skia/Impeller 绘制玻璃(系统级渲染语义)
|
|
747
|
+
blur: "true",
|
|
748
|
+
animation: "native",
|
|
749
|
+
// Flutter 动画管线
|
|
750
|
+
textureSharing: true,
|
|
751
|
+
// Texture/PlatformView 混合
|
|
752
|
+
remoteRendering: false,
|
|
753
|
+
ssr: false,
|
|
754
|
+
input: ["touch", "cursor", "remote"]
|
|
755
|
+
};
|
|
756
|
+
function createFlutterBackend() {
|
|
757
|
+
let nextId = 1;
|
|
758
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
759
|
+
function ensureNode(handle) {
|
|
760
|
+
const n = handle;
|
|
761
|
+
if (!n || typeof n.id !== "number") throw new Error("FlutterBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
762
|
+
return n;
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
id: "flutter",
|
|
766
|
+
version: "0.1.0",
|
|
767
|
+
capabilities: FLUTTER_CAPABILITIES,
|
|
768
|
+
createElement(node) {
|
|
769
|
+
const widget = node.semantic ? SEMANTIC_FLUTTER_MAP[node.semantic] ?? mapWidgetType(node.type) : mapWidgetType(node.type);
|
|
770
|
+
const descriptor = {
|
|
771
|
+
id: nextId++,
|
|
772
|
+
widget,
|
|
773
|
+
props: { ...node.props },
|
|
774
|
+
children: [],
|
|
775
|
+
parent: null,
|
|
776
|
+
text: ""
|
|
777
|
+
};
|
|
778
|
+
nodes.set(descriptor.id, descriptor);
|
|
779
|
+
return descriptor;
|
|
780
|
+
},
|
|
781
|
+
insert(child, parent, anchor) {
|
|
782
|
+
const c = ensureNode(child);
|
|
783
|
+
const p = ensureNode(parent);
|
|
784
|
+
if (c.parent) {
|
|
785
|
+
const oldIdx = c.parent.children.indexOf(c);
|
|
786
|
+
if (oldIdx >= 0) c.parent.children.splice(oldIdx, 1);
|
|
787
|
+
}
|
|
788
|
+
if (anchor) {
|
|
789
|
+
const a = ensureNode(anchor);
|
|
790
|
+
const idx = p.children.indexOf(a);
|
|
791
|
+
p.children.splice(idx >= 0 ? idx : p.children.length, 0, c);
|
|
792
|
+
} else {
|
|
793
|
+
p.children.push(c);
|
|
794
|
+
}
|
|
795
|
+
c.parent = p;
|
|
796
|
+
},
|
|
797
|
+
remove(child) {
|
|
798
|
+
const c = ensureNode(child);
|
|
799
|
+
if (c.parent) {
|
|
800
|
+
const idx = c.parent.children.indexOf(c);
|
|
801
|
+
if (idx >= 0) c.parent.children.splice(idx, 1);
|
|
802
|
+
c.parent = null;
|
|
803
|
+
}
|
|
804
|
+
nodes.delete(c.id);
|
|
805
|
+
},
|
|
806
|
+
patchProp(el, key, prev, next) {
|
|
807
|
+
const n = ensureNode(el);
|
|
808
|
+
if (next === null || next === void 0) {
|
|
809
|
+
delete n.props[key];
|
|
810
|
+
} else {
|
|
811
|
+
n.props[key] = next;
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
setText(el, text) {
|
|
815
|
+
ensureNode(el).text = text;
|
|
816
|
+
},
|
|
817
|
+
measure() {
|
|
818
|
+
return { width: 0, height: 0 };
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/conformance-component.ts
|
|
824
|
+
function createControlReader(backendId) {
|
|
825
|
+
switch (backendId) {
|
|
826
|
+
case "vue-dom":
|
|
827
|
+
return (h) => {
|
|
828
|
+
const el = h;
|
|
829
|
+
const cls = el.getAttribute?.("class") ?? "";
|
|
830
|
+
return el.tagName.toLowerCase() + (cls ? "." + cls : "");
|
|
831
|
+
};
|
|
832
|
+
case "native-ios":
|
|
833
|
+
case "native-android":
|
|
834
|
+
case "native-harmony":
|
|
835
|
+
return (h) => h.type;
|
|
836
|
+
case "flutter":
|
|
837
|
+
return (h) => h.widget;
|
|
838
|
+
case "headless":
|
|
839
|
+
return (h) => h.type;
|
|
840
|
+
default:
|
|
841
|
+
return (h) => String(h?.type ?? h);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function renderComponentSnapshot(backend, ir, readControl) {
|
|
845
|
+
const snapOf = /* @__PURE__ */ new Map();
|
|
846
|
+
function visit(node) {
|
|
847
|
+
const handle = backend.createElement(node);
|
|
848
|
+
for (const key of Object.keys(node.props)) {
|
|
849
|
+
backend.patchProp(handle, key, void 0, node.props[key]);
|
|
850
|
+
}
|
|
851
|
+
const snap = {
|
|
852
|
+
type: node.type,
|
|
853
|
+
semantic: node.semantic ?? node.type,
|
|
854
|
+
control: readControl(handle),
|
|
855
|
+
props: { ...node.props },
|
|
856
|
+
text: "",
|
|
857
|
+
children: []
|
|
858
|
+
};
|
|
859
|
+
snapOf.set(handle, snap);
|
|
860
|
+
for (const child of node.children) {
|
|
861
|
+
const childHandle = visit(child);
|
|
862
|
+
backend.insert(childHandle, handle);
|
|
863
|
+
const childSnap = snapOf.get(childHandle);
|
|
864
|
+
if (childSnap) snap.children.push(childSnap);
|
|
865
|
+
}
|
|
866
|
+
return handle;
|
|
867
|
+
}
|
|
868
|
+
const rootHandle = visit(ir);
|
|
869
|
+
return snapOf.get(rootHandle);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/hybrid.ts
|
|
873
|
+
function createHybridRenderer(options) {
|
|
874
|
+
const { defaultBackend } = options;
|
|
875
|
+
const regions = options.regions ?? [];
|
|
876
|
+
const devtools = options.devtools !== false;
|
|
877
|
+
const nodeBackend = /* @__PURE__ */ new WeakMap();
|
|
878
|
+
const traceList = [];
|
|
879
|
+
function routeFor(node) {
|
|
880
|
+
for (const r of regions) {
|
|
881
|
+
if (r.match(node)) return r.backend;
|
|
882
|
+
}
|
|
883
|
+
return defaultBackend;
|
|
884
|
+
}
|
|
885
|
+
function regionName(node) {
|
|
886
|
+
for (const r of regions) {
|
|
887
|
+
if (r.match(node)) return r.name ?? "region";
|
|
888
|
+
}
|
|
889
|
+
return "default";
|
|
890
|
+
}
|
|
891
|
+
function backendOf(handle) {
|
|
892
|
+
if (typeof handle !== "object" || handle === null) return void 0;
|
|
893
|
+
return nodeBackend.get(handle)?.backend;
|
|
894
|
+
}
|
|
895
|
+
function textureBackends() {
|
|
896
|
+
const sharing = regions.map((r) => r.backend).concat([defaultBackend]).filter((b) => b.capabilities.textureSharing === true);
|
|
897
|
+
return sharing.length ? sharing : [defaultBackend];
|
|
898
|
+
}
|
|
899
|
+
const defaultId = "hybrid";
|
|
900
|
+
const hybrid = {
|
|
901
|
+
id: defaultId,
|
|
902
|
+
version: "0.1.0",
|
|
903
|
+
capabilities: {
|
|
904
|
+
layout: "none",
|
|
905
|
+
// 无自带布局器——委托各后端(框架 IR 求解)
|
|
906
|
+
glass: "L1",
|
|
907
|
+
blur: "none",
|
|
908
|
+
animation: "js",
|
|
909
|
+
textureSharing: true,
|
|
910
|
+
remoteRendering: false,
|
|
911
|
+
ssr: true,
|
|
912
|
+
input: ["touch", "cursor", "remote"]
|
|
913
|
+
},
|
|
914
|
+
createElement(node) {
|
|
915
|
+
const backend = routeFor(node);
|
|
916
|
+
const handle = backend.createElement(node);
|
|
917
|
+
if (typeof handle === "object" && handle !== null) {
|
|
918
|
+
nodeBackend.set(handle, { backend, region: regionName(node) });
|
|
919
|
+
}
|
|
920
|
+
if (devtools) {
|
|
921
|
+
traceList.push({ semantic: node.semantic, type: node.type, backendId: backend.id, region: regionName(node) });
|
|
922
|
+
}
|
|
923
|
+
return handle;
|
|
924
|
+
},
|
|
925
|
+
insert(child, parent, anchor) {
|
|
926
|
+
const b = backendOf(child);
|
|
927
|
+
if (!b) return;
|
|
928
|
+
b.insert(child, parent, anchor);
|
|
929
|
+
},
|
|
930
|
+
remove(child) {
|
|
931
|
+
const b = backendOf(child);
|
|
932
|
+
if (b) b.remove(child);
|
|
933
|
+
},
|
|
934
|
+
patchProp(el, key, prev, next) {
|
|
935
|
+
const b = backendOf(el);
|
|
936
|
+
if (b) b.patchProp(el, key, prev, next);
|
|
937
|
+
},
|
|
938
|
+
setText(el, text) {
|
|
939
|
+
const b = backendOf(el);
|
|
940
|
+
if (b) b.setText(el, text);
|
|
941
|
+
},
|
|
942
|
+
// —— 可选方法:委托归属后端(存在时) ——
|
|
943
|
+
querySelector(selector) {
|
|
944
|
+
return defaultBackend.querySelector ? defaultBackend.querySelector(selector) : null;
|
|
945
|
+
},
|
|
946
|
+
measure(node, constraints) {
|
|
947
|
+
const b = backendOf(node);
|
|
948
|
+
if (b && b.measure) return b.measure(node, constraints);
|
|
949
|
+
return { width: 0, height: 0 };
|
|
950
|
+
},
|
|
951
|
+
layout(root, constraints) {
|
|
952
|
+
const b = backendOf(root);
|
|
953
|
+
if (b && b.layout) b.layout(root, constraints);
|
|
954
|
+
},
|
|
955
|
+
scheduleFrame(task) {
|
|
956
|
+
defaultBackend.scheduleFrame ? defaultBackend.scheduleFrame(task) : task();
|
|
957
|
+
},
|
|
958
|
+
flush() {
|
|
959
|
+
defaultBackend.flush ? defaultBackend.flush() : void 0;
|
|
960
|
+
},
|
|
961
|
+
dispatchInput(event) {
|
|
962
|
+
const b = backendOf(event.target);
|
|
963
|
+
if (b && b.dispatchInput) b.dispatchInput(event);
|
|
964
|
+
},
|
|
965
|
+
onMount(root) {
|
|
966
|
+
const b = backendOf(root);
|
|
967
|
+
if (b && b.onMount) b.onMount(root);
|
|
968
|
+
},
|
|
969
|
+
onUnmount(root) {
|
|
970
|
+
const b = backendOf(root);
|
|
971
|
+
if (b && b.onUnmount) b.onUnmount(root);
|
|
972
|
+
},
|
|
973
|
+
// —— ★B6 纹理共享:广播注册/注销(跨后端全局 id) ——
|
|
974
|
+
registerExternalTexture(id, texture) {
|
|
975
|
+
for (const b of textureBackends()) {
|
|
976
|
+
if (b.registerExternalTexture) b.registerExternalTexture(id, texture);
|
|
977
|
+
}
|
|
978
|
+
},
|
|
979
|
+
unregisterExternalTexture(id) {
|
|
980
|
+
for (const b of textureBackends()) {
|
|
981
|
+
if (b.unregisterExternalTexture) b.unregisterExternalTexture(id);
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
// —— 混合专属 ——
|
|
985
|
+
routeFor,
|
|
986
|
+
backendOf,
|
|
987
|
+
traces: () => traceList.slice(),
|
|
988
|
+
clearTraces: () => {
|
|
989
|
+
traceList.length = 0;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
return hybrid;
|
|
993
|
+
}
|
|
994
|
+
function textureRef(textureId) {
|
|
995
|
+
return { textureId };
|
|
996
|
+
}
|
|
997
|
+
function runHybridConformance(renderer) {
|
|
998
|
+
const checks = [];
|
|
999
|
+
function check(name, pass, detail) {
|
|
1000
|
+
checks.push({ name, pass, detail });
|
|
1001
|
+
}
|
|
1002
|
+
check("id", typeof renderer.id === "string" && renderer.id.length > 0, `id=${String(renderer.id)}`);
|
|
1003
|
+
check("method.createElement", typeof renderer.createElement === "function");
|
|
1004
|
+
check("method.insert", typeof renderer.insert === "function");
|
|
1005
|
+
check("method.remove", typeof renderer.remove === "function");
|
|
1006
|
+
check("method.patchProp", typeof renderer.patchProp === "function");
|
|
1007
|
+
check("method.setText", typeof renderer.setText === "function");
|
|
1008
|
+
const probe = { type: "p-box", semantic: "layout.box", props: {}, children: [] };
|
|
1009
|
+
const routed = renderer.routeFor(probe);
|
|
1010
|
+
check("route.default-non-null", routed !== void 0 && routed !== null);
|
|
1011
|
+
const handle = renderer.createElement(probe);
|
|
1012
|
+
const owned = renderer.backendOf(handle);
|
|
1013
|
+
check("route.backendOf-owned", owned !== void 0, "createElement \u540E backendOf \u5E94\u6709\u5F52\u5C5E");
|
|
1014
|
+
if (owned) check("route.consistent", owned === routed, "routeFor \u4E0E backendOf \u5E94\u4E00\u81F4");
|
|
1015
|
+
check("optional.registerExternalTexture", typeof renderer.registerExternalTexture === "function");
|
|
1016
|
+
check("optional.unregisterExternalTexture", typeof renderer.unregisterExternalTexture === "function");
|
|
1017
|
+
check("devtools.trace-recorded", renderer.traces().length > 0, `traces=${renderer.traces().length}`);
|
|
1018
|
+
return { ok: checks.every((c) => c.pass), checks };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// ../component-ir/dist/index.js
|
|
1022
|
+
var TAG_SEMANTIC_MAP = {
|
|
1023
|
+
// G-32 ① 布局原语(12)
|
|
1024
|
+
"p-box": "layout.box",
|
|
1025
|
+
"p-inline": "layout.inline",
|
|
1026
|
+
"p-stack": "layout.stack",
|
|
1027
|
+
"p-grid": "layout.grid",
|
|
1028
|
+
"p-fluid": "layout.fluid",
|
|
1029
|
+
"p-adaptive": "layout.adaptive",
|
|
1030
|
+
"p-fit": "layout.fit",
|
|
1031
|
+
"p-spacer": "layout.spacer",
|
|
1032
|
+
"p-divider": "layout.divider",
|
|
1033
|
+
"p-scroll": "layout.scroll",
|
|
1034
|
+
"p-virtual-list": "layout.virtual-list",
|
|
1035
|
+
"p-masonry": "layout.masonry",
|
|
1036
|
+
// G-32 ② UI 原语(18)
|
|
1037
|
+
"p-text": "ui.text",
|
|
1038
|
+
"p-heading": "ui.heading",
|
|
1039
|
+
"p-rich-text": "ui.rich-text",
|
|
1040
|
+
"p-icon": "ui.icon",
|
|
1041
|
+
"p-image": "ui.image",
|
|
1042
|
+
"p-avatar": "ui.avatar",
|
|
1043
|
+
"p-media": "ui.media",
|
|
1044
|
+
"p-canvas": "ui.canvas",
|
|
1045
|
+
"p-svg": "ui.svg",
|
|
1046
|
+
"p-input": "ui.input",
|
|
1047
|
+
"p-textarea": "ui.textarea",
|
|
1048
|
+
"p-select": "ui.select",
|
|
1049
|
+
"p-checkbox": "ui.checkbox",
|
|
1050
|
+
"p-radio": "ui.radio",
|
|
1051
|
+
"p-switch": "ui.switch",
|
|
1052
|
+
"p-slider": "ui.slider",
|
|
1053
|
+
"p-picker": "ui.picker",
|
|
1054
|
+
"p-form": "ui.form",
|
|
1055
|
+
"p-button": "ui.button",
|
|
1056
|
+
// 既有按钮
|
|
1057
|
+
// G-32 ③ Shell 原语(10)
|
|
1058
|
+
"p-page": "shell.page",
|
|
1059
|
+
"p-nav": "shell.nav",
|
|
1060
|
+
"p-tabbar": "shell.tabbar",
|
|
1061
|
+
"p-segment": "shell.segment",
|
|
1062
|
+
"p-drawer": "shell.drawer",
|
|
1063
|
+
"p-modal": "shell.modal",
|
|
1064
|
+
"p-popover": "shell.popover",
|
|
1065
|
+
"p-toast": "shell.toast",
|
|
1066
|
+
"p-action-sheet": "shell.action-sheet",
|
|
1067
|
+
"p-split": "layout.split",
|
|
1068
|
+
// ★已落地绑定(G-32 S10 分栏语义——layout.split 承载)
|
|
1069
|
+
// G-32 ④ Gesture 组件形态(2)
|
|
1070
|
+
"p-draggable": "gesture.draggable",
|
|
1071
|
+
"p-scrollable": "gesture.scrollable",
|
|
1072
|
+
// G-32 ⑥ Engineering 组件形态(3)
|
|
1073
|
+
"p-router-link": "engineering.router-link",
|
|
1074
|
+
// ★E18 组件形态(p- 前缀产 C-IR)
|
|
1075
|
+
"router-link": "engineering.router-link",
|
|
1076
|
+
// ★兼容别名(Vue Router 风格 <router-link> 标签;非 catalog 条目——p-view 先例 G-31 B4)
|
|
1077
|
+
"p-transition": "engineering.transition",
|
|
1078
|
+
"p-animate": "engineering.animate",
|
|
1079
|
+
// G-31 能力入口
|
|
1080
|
+
"p-scan-qr": "capability.scan-qr",
|
|
1081
|
+
"p-pick-photo": "capability.pick-photo",
|
|
1082
|
+
"p-location": "capability.location",
|
|
1083
|
+
// ★G-31 B4 现有组件对齐(src/components 实际标签 → L1 语义)
|
|
1084
|
+
"p-view": "layout.box",
|
|
1085
|
+
// 原子容器 = p-box 角色
|
|
1086
|
+
"p-list-view": "ui.list",
|
|
1087
|
+
"p-nav-bar": "ui.nav",
|
|
1088
|
+
// ★G-31 B4 Fluid 体系扩展语义(有明确系统原生对应——原则 #10.8)
|
|
1089
|
+
"p-safe": "layout.safe",
|
|
1090
|
+
"p-sidebar": "layout.sidebar"
|
|
1091
|
+
};
|
|
1092
|
+
function toComponentIR(tag, props = {}, children = []) {
|
|
1093
|
+
if (!tag.startsWith("p-")) return null;
|
|
1094
|
+
const semantic = TAG_SEMANTIC_MAP[tag];
|
|
1095
|
+
if (!semantic) return null;
|
|
1096
|
+
return { tag, semantic, props: { ...props }, children };
|
|
1097
|
+
}
|
|
1098
|
+
var LAYOUT = [
|
|
1099
|
+
{ id: "L1", kind: "layout", semantic: "layout.box", tag: "p-box", props: ["aspectRatio", "overflow"], mpEquiv: "<view>", tier: "L1", status: "implemented" },
|
|
1100
|
+
{ id: "L2", kind: "layout", semantic: "layout.inline", tag: "p-inline", props: ["wrap"], mpEquiv: "<text> \u5185\u8054", tier: "L1", status: "implemented" },
|
|
1101
|
+
{ id: "L3", kind: "layout", semantic: "layout.stack", tag: "p-stack", props: ["direction", "gap", "align", "wrap", "snap", "loop"], mpEquiv: "flex + scroll-view + swiper", tier: "L1", status: "implemented" },
|
|
1102
|
+
{ id: "L4", kind: "layout", semantic: "layout.grid", tag: "p-grid", props: ["minColWidth", "maxCols", "gap", "autoFlow"], mpEquiv: "<view> + CSS Grid", tier: "L1", status: "implemented" },
|
|
1103
|
+
{ id: "L5", kind: "layout", semantic: "layout.fluid", tag: "p-fluid", props: ["breakpoints", "minItemWidth"], mpEquiv: "\u54CD\u5E94\u5F0F CSS", tier: "L1", status: "implemented" },
|
|
1104
|
+
{ id: "L6", kind: "layout", semantic: "layout.adaptive", tag: "p-adaptive", props: ["sheet", "dialog", "popover", "drawer"], mpEquiv: "\u65E0\uFF08\u5BB9\u5668\u5BBD\u5EA6\u8BED\u4E49\u65AD\u70B9\uFF09", tier: "L1", status: "implemented" },
|
|
1105
|
+
{ id: "L7", kind: "layout", semantic: "layout.fit", tag: "p-fit", props: ["mode"], mpEquiv: "fit-content", tier: "L1", status: "implemented" },
|
|
1106
|
+
{ id: "L8", kind: "layout", semantic: "layout.spacer", tag: "p-spacer", props: ["grow", "shrink"], mpEquiv: "flex:1", tier: "L1", status: "implemented" },
|
|
1107
|
+
{ id: "L9", kind: "layout", semantic: "layout.divider", tag: "p-divider", props: ["orientation", "inset"], mpEquiv: "<view> + border", tier: "L1", status: "implemented" },
|
|
1108
|
+
{ id: "L10", kind: "layout", semantic: "layout.scroll", tag: "p-scroll", props: ["axis", "paging", "refresh", "indicator"], mpEquiv: "<scroll-view>", tier: "L1", status: "implemented" },
|
|
1109
|
+
{ id: "L11", kind: "layout", semantic: "layout.virtual-list", tag: "p-virtual-list", props: ["itemSize", "buffer", "direction"], mpEquiv: "<scroll-view> + \u624B\u52A8\u56DE\u6536", tier: "L1", status: "implemented" },
|
|
1110
|
+
{ id: "L12", kind: "layout", semantic: "layout.masonry", tag: "p-masonry", props: ["colCount", "gap"], mpEquiv: "\u7B2C\u4E09\u65B9\u7011\u5E03\u6D41", tier: "L1", status: "implemented" }
|
|
1111
|
+
];
|
|
1112
|
+
var UI = [
|
|
1113
|
+
{ id: "U1", kind: "ui", semantic: "ui.text", tag: "p-text", props: ["content", "selectable", "truncate", "align"], mpEquiv: "<text>", tier: "L1", status: "implemented" },
|
|
1114
|
+
{ id: "U2", kind: "ui", semantic: "ui.heading", tag: "p-heading", props: ["level"], mpEquiv: "<h1>-<h6>", tier: "L1", status: "implemented" },
|
|
1115
|
+
{ id: "U3", kind: "ui", semantic: "ui.rich-text", tag: "p-rich-text", props: ["source", "schema"], mpEquiv: "<rich-text>", tier: "L1", status: "implemented" },
|
|
1116
|
+
{ id: "U4", kind: "ui", semantic: "ui.icon", tag: "p-icon", props: ["name", "size", "color", "spin"], mpEquiv: "<icon>", tier: "L1", status: "implemented" },
|
|
1117
|
+
{ id: "U5", kind: "ui", semantic: "ui.image", tag: "p-image", props: ["src", "fit", "placeholder", "lazy"], mpEquiv: "<image>", tier: "L1", status: "implemented" },
|
|
1118
|
+
{ id: "U6", kind: "ui", semantic: "ui.avatar", tag: "p-avatar", props: ["src", "shape", "size", "fallback"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1119
|
+
{ id: "U7", kind: "ui", semantic: "ui.media", tag: "p-media", props: ["kind", "controls", "autoplay", "poster", "loop", "muted", "pictureInPicture"], mpEquiv: "<video>+<audio>", tier: "L1", status: "implemented" },
|
|
1120
|
+
{ id: "U8", kind: "ui", semantic: "ui.canvas", tag: "p-canvas", props: ["engine", "resolution"], mpEquiv: "<canvas>", tier: "L1", status: "implemented" },
|
|
1121
|
+
{ id: "U9", kind: "ui", semantic: "ui.svg", tag: "p-svg", props: ["path", "viewbox"], mpEquiv: "\u65E0", tier: "L1", status: "implemented" },
|
|
1122
|
+
{ id: "U10", kind: "ui", semantic: "ui.input", tag: "p-input", props: ["type", "mask", "validation", "clearable"], mpEquiv: "<input>", tier: "L1", status: "implemented" },
|
|
1123
|
+
{ id: "U11", kind: "ui", semantic: "ui.textarea", tag: "p-textarea", props: ["autosize", "maxLength", "count"], mpEquiv: "<textarea>", tier: "L1", status: "implemented" },
|
|
1124
|
+
{ id: "U12", kind: "ui", semantic: "ui.select", tag: "p-select", props: ["options", "multiple", "searchable", "cascader"], mpEquiv: "<picker> \u90E8\u5206", tier: "L1", status: "implemented" },
|
|
1125
|
+
{ id: "U13", kind: "ui", semantic: "ui.checkbox", tag: "p-checkbox", props: ["checked", "indeterminate", "group"], mpEquiv: "<checkbox>", tier: "L1", status: "implemented" },
|
|
1126
|
+
{ id: "U14", kind: "ui", semantic: "ui.radio", tag: "p-radio", props: ["value", "group"], mpEquiv: "<radio>", tier: "L1", status: "implemented" },
|
|
1127
|
+
{ id: "U15", kind: "ui", semantic: "ui.switch", tag: "p-switch", props: ["checked", "loading"], mpEquiv: "<switch>", tier: "L1", status: "implemented" },
|
|
1128
|
+
{ id: "U16", kind: "ui", semantic: "ui.slider", tag: "p-slider", props: ["min", "max", "step", "range"], mpEquiv: "<slider>", tier: "L1", status: "implemented" },
|
|
1129
|
+
{ id: "U17", kind: "ui", semantic: "ui.picker", tag: "p-picker", props: ["mode", "start", "end"], mpEquiv: "<picker>", tier: "L1", status: "implemented" },
|
|
1130
|
+
{ id: "U18", kind: "ui", semantic: "ui.form", tag: "p-form", props: ["model", "rules", "layout"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" }
|
|
1131
|
+
];
|
|
1132
|
+
var SHELL = [
|
|
1133
|
+
{ id: "S1", kind: "shell", semantic: "shell.page", tag: "p-page", props: ["title", "statusBar", "pullRefresh"], mpEquiv: "<page>", tier: "L1", status: "implemented" },
|
|
1134
|
+
{ id: "S2", kind: "shell", semantic: "shell.nav", tag: "p-nav", props: ["title", "transparent"], mpEquiv: "\u5BFC\u822A\u680F\u914D\u7F6E", tier: "L1", status: "implemented" },
|
|
1135
|
+
{ id: "S3", kind: "shell", semantic: "shell.tabbar", tag: "p-tabbar", props: ["tabs", "active", "badge"], mpEquiv: "<tabbar>", tier: "L1", status: "implemented" },
|
|
1136
|
+
{ id: "S4", kind: "shell", semantic: "shell.segment", tag: "p-segment", props: ["options", "active"], mpEquiv: "<segment>", tier: "L1", status: "implemented" },
|
|
1137
|
+
{ id: "S5", kind: "shell", semantic: "shell.drawer", tag: "p-drawer", props: ["side", "width", "overlay"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1138
|
+
{ id: "S6", kind: "shell", semantic: "shell.modal", tag: "p-modal", props: ["open", "dismissible", "sheet", "dialog", "alert"], mpEquiv: "<modal>+wx.showModal", tier: "L1", status: "implemented" },
|
|
1139
|
+
{ id: "S7", kind: "shell", semantic: "shell.popover", tag: "p-popover", props: ["trigger", "placement"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1140
|
+
{ id: "S8", kind: "shell", semantic: "shell.toast", tag: "p-toast", props: ["message", "duration", "type"], mpEquiv: "wx.showToast", tier: "L1", status: "planned" },
|
|
1141
|
+
{ id: "S9", kind: "shell", semantic: "shell.action-sheet", tag: "p-action-sheet", props: ["actions", "cancel"], mpEquiv: "wx.showActionSheet", tier: "L1", status: "implemented" },
|
|
1142
|
+
{ id: "S10", kind: "shell", semantic: "layout.split", tag: "p-split", props: ["breakpoint", "ratio", "collapse"], mpEquiv: "\u65E0\uFF08\u5206\u680F\u5E03\u5C40\uFF09", tier: "L1", status: "implemented" }
|
|
1143
|
+
// ★已落地绑定:分栏语义由 layout.split 承载(G-32 文档为 shell.split——机器事实以实现为准)
|
|
1144
|
+
];
|
|
1145
|
+
var GESTURE = [
|
|
1146
|
+
{ id: "G1", kind: "gesture", semantic: "gesture.tap", api: "v-gesture:tap", props: ["count"], mpEquiv: "bindtap", tier: "L1", status: "planned" },
|
|
1147
|
+
{ id: "G2", kind: "gesture", semantic: "gesture.longpress", api: "v-gesture:longpress", props: ["duration", "onEnd"], mpEquiv: "bindlongpress", tier: "L1", status: "planned" },
|
|
1148
|
+
{ id: "G3", kind: "gesture", semantic: "gesture.swipe", api: "v-gesture:swipe", props: ["direction", "threshold"], mpEquiv: "bindswipe", tier: "L1", status: "planned" },
|
|
1149
|
+
{ id: "G4", kind: "gesture", semantic: "gesture.pan", api: "v-gesture:pan", props: ["axis", "bounds"], mpEquiv: "bindtouchmove", tier: "L1", status: "planned" },
|
|
1150
|
+
{ id: "G5", kind: "gesture", semantic: "gesture.pinch", api: "v-gesture:pinch", props: ["scale", "onChange"], mpEquiv: "touchstart/move \u7EC4\u5408", tier: "L1", status: "planned" },
|
|
1151
|
+
{ id: "G6", kind: "gesture", semantic: "gesture.rotate", api: "v-gesture:rotate", props: ["angle"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "planned" },
|
|
1152
|
+
{ id: "G7", kind: "gesture", semantic: "gesture.press", api: "v-gesture:press", props: ["force"], mpEquiv: "3D Touch", tier: "L1", status: "planned" },
|
|
1153
|
+
{ id: "G8", kind: "gesture", semantic: "gesture.draggable", tag: "p-draggable", props: ["ghost", "snapToGrid", "onDrop"], mpEquiv: "movable-view", tier: "L1", status: "implemented" },
|
|
1154
|
+
{ id: "G9", kind: "gesture", semantic: "gesture.scrollable", tag: "p-scrollable", props: ["bounce", "refresh", "loadMore"], mpEquiv: "<scroll-view>", tier: "L1", status: "implemented" },
|
|
1155
|
+
{ id: "G10", kind: "gesture", semantic: "gesture.use-gesture", api: "useGesture()", props: ["recognizers", "simultaneous"], mpEquiv: "\u65E0", tier: "L1", status: "planned" }
|
|
1156
|
+
];
|
|
1157
|
+
function cap(id, name, api, mpEquiv, returnType) {
|
|
1158
|
+
return { id, kind: "capability", semantic: `capability.${name}`, api, props: [returnType], mpEquiv, tier: "L1", status: "planned" };
|
|
1159
|
+
}
|
|
1160
|
+
var CAPABILITY = [
|
|
1161
|
+
// 7.1 设备/硬件(15)
|
|
1162
|
+
cap("C1", "camera", "useCamera()", "wx.createCameraContext", "Result<Media>"),
|
|
1163
|
+
cap("C2", "microphone", "useMicrophone()", "RecorderManager", "Result<AudioBuffer>"),
|
|
1164
|
+
{ id: "C3", kind: "capability", semantic: "capability.location", api: "useLocation()", props: ["Result<Coords>"], mpEquiv: "wx.getLocation", tier: "L1", status: "implemented" },
|
|
1165
|
+
cap("C4", "map", "useMap()", "wx.createMapContext", "MapController"),
|
|
1166
|
+
cap("C5", "sensor", "useSensor()", "onAccelerometer/onCompass/onGyroscope", "SensorStream"),
|
|
1167
|
+
cap("C6", "vibrate", "useVibrate()", "wx.vibrateShort/Long", "void"),
|
|
1168
|
+
cap("C7", "battery", "useBattery()", "wx.getBatteryInfo", "BatteryInfo"),
|
|
1169
|
+
cap("C8", "network", "useNetwork()", "wx.getNetworkType", "NetworkType"),
|
|
1170
|
+
cap("C9", "clipboard", "useClipboard()", "wx.set/getClipboardData", "Result<string>"),
|
|
1171
|
+
cap("C10", "screen", "useScreen()", "wx.getSystemInfo", "ScreenInfo"),
|
|
1172
|
+
cap("C11", "device", "useDevice()", "wx.getSystemInfo", "DeviceInfo"),
|
|
1173
|
+
cap("C12", "orientation", "useOrientation()", "wx.onDeviceOrientationChange", "Orientation"),
|
|
1174
|
+
cap("C13", "brightness", "useBrightness()", "wx.setScreenBrightness", "Result<void>"),
|
|
1175
|
+
cap("C14", "keyboard", "useKeyboard()", "wx.onKeyboardHeightChange", "KeyboardInfo"),
|
|
1176
|
+
cap("C15", "storage", "useStorage()", "wx.set/getStorage", "StorageAPI"),
|
|
1177
|
+
// 7.2 系统/OS(10)
|
|
1178
|
+
cap("C16", "permission", "usePermission()", "wx.authorize", "Result<PermissionStatus>"),
|
|
1179
|
+
cap("C17", "notification", "useNotification()", "wx.requestSubscribeMessage", "NotificationAPI"),
|
|
1180
|
+
cap("C18", "share", "useShare()", "wx.shareAppMessage", "Result<void>"),
|
|
1181
|
+
cap("C19", "contact", "useContact()", "wx.chooseContact", "Result<Contact[]>"),
|
|
1182
|
+
cap("C20", "calendar", "useCalendar()", "wx.addPhoneCalendar", "CalendarAPI"),
|
|
1183
|
+
cap("C21", "phone-call", "usePhoneCall()", "wx.makePhoneCall", "Result<void>"),
|
|
1184
|
+
cap("C22", "sms", "useSMS()", "wx.??\uFF08\u53D7\u9650\uFF09", "Result<void>"),
|
|
1185
|
+
cap("C23", "app-lifecycle", "useAppLifecycle()", "App.onLaunch/onShow", "LifecycleHooks"),
|
|
1186
|
+
cap("C24", "page-lifecycle", "usePageLifecycle()", "Page.onLoad/onShow", "LifecycleHooks"),
|
|
1187
|
+
cap("C25", "background", "useBackground()", "wx.onBackground", "BackgroundAPI"),
|
|
1188
|
+
// 7.3 通信/数据(10)
|
|
1189
|
+
cap("C26", "fetch", "useFetch()", "wx.request", "Promise<T>"),
|
|
1190
|
+
cap("C27", "websocket", "useWebSocket()", "wx.connectSocket", "WSConnection"),
|
|
1191
|
+
cap("C28", "socket-task", "useSocketTask()", "wx.SocketTask", "SocketTask"),
|
|
1192
|
+
cap("C29", "upload", "useUpload()", "wx.uploadFile", "Progress<Result>"),
|
|
1193
|
+
cap("C30", "download", "useDownload()", "wx.downloadFile", "Progress<Result>"),
|
|
1194
|
+
cap("C31", "data-channel", "useDataChannel()", "wx...\uFF08\u76F4\u64AD/\u5B9E\u65F6\uFF09", "Channel"),
|
|
1195
|
+
cap("C32", "cookie", "useCookie()", "\u65E0", "CookieJar"),
|
|
1196
|
+
cap("C33", "auth", "useAuth()", "\u7EC4\u5408", "AuthState"),
|
|
1197
|
+
cap("C34", "analytics", "useAnalytics()", "wx.reportEvent", "TrackAPI"),
|
|
1198
|
+
cap("C35", "log", "useLog()", "console + \u4E0A\u62A5", "Logger"),
|
|
1199
|
+
// 7.4 扩展能力(10)
|
|
1200
|
+
cap("C36", "bluetooth", "useBluetooth()", "wx.openBluetoothAdapter", "BluetoothAPI"),
|
|
1201
|
+
cap("C37", "nfc", "useNFC()", "wx.getHCEState", "NFCAPI"),
|
|
1202
|
+
cap("C38", "biometric", "useBiometric()", "wx.checkIsSupportFingerPrint", "Result<boolean>"),
|
|
1203
|
+
cap("C39", "face-id", "useFaceID()", "\u7EC4\u5408", "Result<boolean>"),
|
|
1204
|
+
cap("C40", "payment", "usePayment()", "wx.requestPayment", "Result<PayResult>"),
|
|
1205
|
+
cap("C41", "login", "useLogin()", "wx.login", "Result<Token>"),
|
|
1206
|
+
cap("C42", "qr-code", "useQRCode()", "wx.scanCode + canvas", "Result<string>"),
|
|
1207
|
+
cap("C43", "file-system", "useFileSystem()", "wx.getFileSystemManager", "FSAdapter"),
|
|
1208
|
+
cap("C44", "archive", "useArchive()", "wx.compressFile", "Result<void>"),
|
|
1209
|
+
cap("C45", "shortcut", "useShortcut()", "wx.addToDesktop", "Result<void>"),
|
|
1210
|
+
cap("C46", "in-app-purchase", "useInAppPurchase()", "wx.requestPayment \u6269\u5C55", "Result<Receipt>"),
|
|
1211
|
+
// 7.5 平台特有(4——C47-C50,此前 7.4 末 C46,总 50)
|
|
1212
|
+
{ id: "C47", kind: "capability", semantic: "capability.mini-program", api: "useMiniProgram()", props: ["MPContext"], mpEquiv: "wx.navigateToMiniProgram", tier: "L1", status: "planned" },
|
|
1213
|
+
{ id: "C48", kind: "capability", semantic: "capability.embedded", api: "useEmbedded()", props: ["HostContext"], mpEquiv: "\u65E0\uFF08\u88AB\u5BBF\u4E3B\u5D4C\u5165\uFF09", tier: "L1", status: "planned" },
|
|
1214
|
+
{ id: "C49", kind: "capability", semantic: "capability.live", api: "useLive()", props: ["LiveRoom"], mpEquiv: "wx...\uFF08\u76F4\u64AD\u7EC4\u4EF6\uFF09", tier: "L1", status: "planned" },
|
|
1215
|
+
{ id: "C50", kind: "capability", semantic: "capability.extension", api: "useExtension()", props: ["ExtensionAPI"], mpEquiv: "\u65E0\uFF08\u63D2\u4EF6/\u6269\u5C55\u70B9 G-21\uFF09", tier: "L1", status: "planned" }
|
|
1216
|
+
];
|
|
1217
|
+
var ENGINEERING = [
|
|
1218
|
+
{ id: "E1", kind: "engineering", semantic: "engineering.state", api: "useState()", mpEquiv: "data", tier: "L1", status: "planned" },
|
|
1219
|
+
{ id: "E2", kind: "engineering", semantic: "engineering.computed", api: "useComputed()", mpEquiv: "computed", tier: "L1", status: "planned" },
|
|
1220
|
+
{ id: "E3", kind: "engineering", semantic: "engineering.watch", api: "useWatch()", mpEquiv: "watch", tier: "L1", status: "planned" },
|
|
1221
|
+
{ id: "E4", kind: "engineering", semantic: "engineering.store", api: "useStore()", mpEquiv: "getApp().globalData", tier: "L1", status: "planned" },
|
|
1222
|
+
{ id: "E5", kind: "engineering", semantic: "engineering.provide-inject", api: "useProvide()/useInject()", mpEquiv: "\u65E0", tier: "L1", status: "planned" },
|
|
1223
|
+
{ id: "E6", kind: "engineering", semantic: "engineering.lifecycle", api: "useLifecycle()", mpEquiv: "onLoad/onShow/onHide/onUnload", tier: "L1", status: "planned" },
|
|
1224
|
+
{ id: "E7", kind: "engineering", semantic: "engineering.ready", api: "useReady()", mpEquiv: "onReady", tier: "L1", status: "planned" },
|
|
1225
|
+
{ id: "E8", kind: "engineering", semantic: "engineering.error-boundary", api: "useErrorBoundary()", mpEquiv: "onError", tier: "L1", status: "planned" },
|
|
1226
|
+
{ id: "E9", kind: "engineering", semantic: "engineering.page-param", api: "usePageParam()", mpEquiv: "onLoad(options)", tier: "L1", status: "planned" },
|
|
1227
|
+
{ id: "E10", kind: "engineering", semantic: "engineering.route", api: "useRoute()", mpEquiv: "getCurrentPages()", tier: "L1", status: "planned" },
|
|
1228
|
+
{ id: "E11", kind: "engineering", semantic: "engineering.router-push", api: "router.push()", mpEquiv: "wx.navigateTo", tier: "L1", status: "planned" },
|
|
1229
|
+
{ id: "E12", kind: "engineering", semantic: "engineering.router-replace", api: "router.replace()", mpEquiv: "wx.redirectTo", tier: "L1", status: "planned" },
|
|
1230
|
+
{ id: "E13", kind: "engineering", semantic: "engineering.router-back", api: "router.back()", mpEquiv: "wx.navigateBack", tier: "L1", status: "planned" },
|
|
1231
|
+
{ id: "E14", kind: "engineering", semantic: "engineering.router-switch-tab", api: "router.switchTab()", mpEquiv: "wx.switchTab", tier: "L1", status: "planned" },
|
|
1232
|
+
{ id: "E15", kind: "engineering", semantic: "engineering.router-relaunch", api: "router.reLaunch()", mpEquiv: "wx.reLaunch", tier: "L1", status: "planned" },
|
|
1233
|
+
{ id: "E16", kind: "engineering", semantic: "engineering.router-before-each", api: "router.beforeEach()", mpEquiv: "onLaunch \u624B\u52A8", tier: "L1", status: "planned" },
|
|
1234
|
+
{ id: "E17", kind: "engineering", semantic: "engineering.router-after-each", api: "router.afterEach()", mpEquiv: "\u65E0", tier: "L1", status: "planned" },
|
|
1235
|
+
{ id: "E18", kind: "engineering", semantic: "engineering.router-link", tag: "p-router-link", props: ["to", "replace", "switchTab"], mpEquiv: "<navigator>", tier: "L1", status: "implemented" },
|
|
1236
|
+
{ id: "E19", kind: "engineering", semantic: "engineering.transition", tag: "p-transition", props: ["name", "mode"], mpEquiv: "transition CSS", tier: "L1", status: "implemented" },
|
|
1237
|
+
{ id: "E20", kind: "engineering", semantic: "engineering.animate", tag: "p-animate", props: ["keyframes", "duration"], mpEquiv: "animation CSS", tier: "L1", status: "implemented" },
|
|
1238
|
+
{ id: "E21", kind: "engineering", semantic: "engineering.animation", api: "useAnimation()", mpEquiv: "wx.createAnimation", tier: "L1", status: "planned" },
|
|
1239
|
+
{ id: "E22", kind: "engineering", semantic: "engineering.gesture-animation", api: "useGestureAnimation()", mpEquiv: "\u7EC4\u5408", tier: "L1", status: "planned" },
|
|
1240
|
+
{ id: "E23", kind: "engineering", semantic: "engineering.scroll-animation", api: "useScrollAnimation()", mpEquiv: "\u7EC4\u5408", tier: "L1", status: "planned" },
|
|
1241
|
+
{ id: "E24", kind: "engineering", semantic: "engineering.devtools", api: "useDevTools()", mpEquiv: "\u5C0F\u7A0B\u5E8F DevTools", tier: "L1", status: "planned" },
|
|
1242
|
+
{ id: "E25", kind: "engineering", semantic: "engineering.inspector", api: "useInspector()", mpEquiv: "\u65E0", tier: "L1", status: "planned" },
|
|
1243
|
+
{ id: "E26", kind: "engineering", semantic: "engineering.performance", api: "usePerformance()", mpEquiv: "wx.reportPerformance", tier: "L1", status: "planned" },
|
|
1244
|
+
{ id: "E27", kind: "engineering", semantic: "engineering.define-component", api: "defineComponent()", mpEquiv: "Component()", tier: "L1", status: "planned" },
|
|
1245
|
+
{ id: "E28", kind: "engineering", semantic: "engineering.define-capability", api: "defineCapability()", mpEquiv: "\u65E0", tier: "L1", status: "planned" }
|
|
1246
|
+
];
|
|
1247
|
+
var PRIMITIVE_CATALOG = [...LAYOUT, ...UI, ...SHELL, ...GESTURE, ...CAPABILITY, ...ENGINEERING];
|
|
1248
|
+
var MP_COMPONENTS = [
|
|
1249
|
+
{ mp: "<view>", proteus: "layout.box / layout.stack", status: "ok", group: "component" },
|
|
1250
|
+
{ mp: "<text>", proteus: "ui.text / ui.heading", status: "ok", group: "component" },
|
|
1251
|
+
{ mp: "<image>", proteus: "ui.image", status: "ok", group: "component" },
|
|
1252
|
+
{ mp: "<scroll-view>", proteus: "layout.scroll / layout.virtual-list", status: "ok", group: "component" },
|
|
1253
|
+
{ mp: "<swiper>", proteus: "layout.stack snap/loop\uFF08\u6D88\u706D\u4E3A\u5C5E\u6027\uFF09", status: "ok", group: "component" },
|
|
1254
|
+
{ mp: "<swiper-item>", proteus: "layout.stack \u5B50\u9879", status: "ok", group: "component" },
|
|
1255
|
+
{ mp: "<movable-area>", proteus: "gesture.scrollable \u5BB9\u5668", status: "ok", group: "component" },
|
|
1256
|
+
{ mp: "<movable-view>", proteus: "gesture.draggable", status: "ok", group: "component" },
|
|
1257
|
+
{ mp: "<cover-view>", proteus: "p-overlay (L2)", status: "compat", group: "component" },
|
|
1258
|
+
{ mp: "<cover-image>", proteus: "p-overlay + ui.image", status: "compat", group: "component" },
|
|
1259
|
+
{ mp: "<icon>", proteus: "ui.icon", status: "ok", group: "component" },
|
|
1260
|
+
{ mp: "<progress>", proteus: "p-progress (L2)", status: "compat", group: "component" },
|
|
1261
|
+
{ mp: "<rich-text>", proteus: "ui.rich-text", status: "ok", group: "component" },
|
|
1262
|
+
{ mp: "<button>", proteus: "ui.button", status: "ok", group: "component" },
|
|
1263
|
+
{ mp: "<form>", proteus: "ui.form", status: "ok", group: "component" },
|
|
1264
|
+
{ mp: "<input>", proteus: "ui.input", status: "ok", group: "component" },
|
|
1265
|
+
{ mp: "<textarea>", proteus: "ui.textarea", status: "ok", group: "component" },
|
|
1266
|
+
{ mp: "<checkbox>", proteus: "ui.checkbox", status: "ok", group: "component" },
|
|
1267
|
+
{ mp: "<radio>", proteus: "ui.radio", status: "ok", group: "component" },
|
|
1268
|
+
{ mp: "<picker>", proteus: "ui.picker / ui.select", status: "ok", group: "component" },
|
|
1269
|
+
{ mp: "<picker-view>", proteus: "ui.picker mode=wheel", status: "ok", group: "component" },
|
|
1270
|
+
{ mp: "<slider>", proteus: "ui.slider", status: "ok", group: "component" },
|
|
1271
|
+
{ mp: "<switch>", proteus: "ui.switch", status: "ok", group: "component" },
|
|
1272
|
+
{ mp: "<label>", proteus: "p-label (L2)", status: "compat", group: "component" },
|
|
1273
|
+
{ mp: "<navigator>", proteus: "engineering.router-link / router.*", status: "ok", group: "component" },
|
|
1274
|
+
{ mp: "<audio>", proteus: "ui.media kind=audio\uFF08\u6D88\u706D\u4E3A\u5C5E\u6027\uFF09", status: "ok", group: "component" },
|
|
1275
|
+
{ mp: "<video>", proteus: "ui.media kind=video\uFF08\u6D88\u706D\u4E3A\u5C5E\u6027\uFF09", status: "ok", group: "component" },
|
|
1276
|
+
{ mp: "<camera>", proteus: "p-camera (L2) + capability.camera", status: "ok", group: "component" },
|
|
1277
|
+
{ mp: "<live-player>", proteus: "ui.media kind=live\uFF08\u6D88\u706D\u4E3A\u5C5E\u6027\uFF09", status: "ok", group: "component" },
|
|
1278
|
+
{ mp: "<live-pusher>", proteus: "ui.media kind=live mode=push", status: "ok", group: "component" },
|
|
1279
|
+
{ mp: "<canvas>", proteus: "ui.canvas", status: "ok", group: "component" },
|
|
1280
|
+
{ mp: "<map>", proteus: "p-map (L2) + capability.map", status: "ok", group: "component" },
|
|
1281
|
+
{ mp: "<web-view>", proteus: "p-webview (L2)", status: "compat", group: "component" },
|
|
1282
|
+
{ mp: "<editor>", proteus: "ui.rich-text editable", status: "ok", group: "component" },
|
|
1283
|
+
{ mp: "<ad>", proteus: "p-ad (L2)", status: "compat", group: "component" },
|
|
1284
|
+
{ mp: "<official-account>", proteus: "capability.mini-program\uFF08\u5FAE\u4FE1\u79C1\u6709\uFF09", status: "private", group: "component" },
|
|
1285
|
+
{ mp: "<open-data>", proteus: "capability.mini-program\uFF08\u5FAE\u4FE1\u79C1\u6709\uFF09", status: "private", group: "component" },
|
|
1286
|
+
{ mp: "<share-element>", proteus: "p-share-element (L2)", status: "compat", group: "component" },
|
|
1287
|
+
{ mp: "<aria-component>", proteus: "p-aria (L2)", status: "compat", group: "component" },
|
|
1288
|
+
{ mp: "<page-container>", proteus: "p-page-container (L2)", status: "compat", group: "component" },
|
|
1289
|
+
{ mp: "<voip-room>", proteus: "capability.mini-program\uFF08\u5FAE\u4FE1 VOIP\uFF09", status: "private", group: "component" },
|
|
1290
|
+
{ mp: "<guild-room>", proteus: "capability.mini-program\uFF08\u5FAE\u4FE1\u6E38\u620F\uFF09", status: "private", group: "component" }
|
|
1291
|
+
];
|
|
1292
|
+
var MP_API_GROUPS = [
|
|
1293
|
+
{ mp: "wx.request/upload/download/websocket\uFF08\u7F51\u7EDC\uFF09", proteus: "capability.fetch/upload/download/websocket", status: "ok", group: "api" },
|
|
1294
|
+
{ mp: "wx.requestPayment", proteus: "capability.payment", status: "ok", group: "api" },
|
|
1295
|
+
{ mp: "wx.chooseImage/chooseMedia/previewImage\uFF08\u5A92\u4F53\uFF09", proteus: "capability.camera + ui.image preview", status: "ok", group: "api" },
|
|
1296
|
+
{ mp: "wx.startRecord/RecorderManager\uFF08\u5F55\u97F3\uFF09", proteus: "capability.microphone", status: "ok", group: "api" },
|
|
1297
|
+
{ mp: "wx.createVideoContext/CameraContext", proteus: "capability.media + ui.media", status: "ok", group: "api" },
|
|
1298
|
+
{ mp: "wx.scanCode", proteus: "capability.qr-code / capability.scan-qr", status: "ok", group: "api" },
|
|
1299
|
+
{ mp: "wx.saveImageToPhotosAlbum", proteus: "capability.album (L2)", status: "compat", group: "api" },
|
|
1300
|
+
{ mp: "wx.getFileSystemManager/*\uFF08\u6587\u4EF6 30+\uFF09", proteus: "capability.file-system", status: "ok", group: "api" },
|
|
1301
|
+
{ mp: "wx.compressFile/unzip", proteus: "capability.archive", status: "ok", group: "api" },
|
|
1302
|
+
{ mp: "wx.set/get/remove/clearStorage(+Sync)", proteus: "capability.storage", status: "ok", group: "api" },
|
|
1303
|
+
{ mp: "wx.getLocation/chooseLocation/openLocation", proteus: "capability.location", status: "ok", group: "api" },
|
|
1304
|
+
{ mp: "wx.createMapContext", proteus: "capability.map", status: "ok", group: "api" },
|
|
1305
|
+
{ mp: "wx.getSystemInfo\uFF08\u8BBE\u5907/\u5C4F\u5E55/\u7F51\u7EDC/\u7535\u91CF/\u4EAE\u5EA6/\u65B9\u5411/\u9707\u52A8/\u4F20\u611F\u5668/\u526A\u8D34\u677F/\u7535\u8BDD\uFF09", proteus: "capability.device/screen/network/battery/brightness/orientation/vibrate/sensor/clipboard/phone-call", status: "ok", group: "api" },
|
|
1306
|
+
{ mp: "wx.openBluetoothAdapter\uFF08\u84DD\u7259 20+\uFF09", proteus: "capability.bluetooth", status: "ok", group: "api" },
|
|
1307
|
+
{ mp: "wx.getHCEState\uFF08NFC\uFF09", proteus: "capability.nfc", status: "ok", group: "api" },
|
|
1308
|
+
{ mp: "wx.checkIsSupportFingerPrint/FaceID", proteus: "capability.biometric / face-id", status: "ok", group: "api" },
|
|
1309
|
+
{ mp: "wx.showToast/showLoading/showModal/showActionSheet", proteus: "shell.toast + capability.toast/loading + shell.modal + shell.action-sheet", status: "ok", group: "api" },
|
|
1310
|
+
{ mp: "wx.setNavigationBarTitle/Color", proteus: "shell.nav", status: "ok", group: "api" },
|
|
1311
|
+
{ mp: "wx.setTabBarItem/Style/hide/show", proteus: "shell.tabbar", status: "ok", group: "api" },
|
|
1312
|
+
{ mp: "wx.pageScrollTo", proteus: "capability.page-scroll", status: "ok", group: "api" },
|
|
1313
|
+
{ mp: "wx.createAnimation", proteus: "engineering.animation", status: "ok", group: "api" },
|
|
1314
|
+
{ mp: "wx.createSelectorQuery/IntersectionObserver", proteus: "capability.element / intersection", status: "ok", group: "api" },
|
|
1315
|
+
{ mp: "wx.navigateTo/redirectTo/navigateBack/switchTab/reLaunch", proteus: "engineering.router-push/replace/back/switch-tab/relaunch", status: "ok", group: "api" },
|
|
1316
|
+
{ mp: "wx.getCurrentPages", proteus: "engineering.route", status: "ok", group: "api" },
|
|
1317
|
+
{ mp: "App()/Page() \u751F\u547D\u5468\u671F/getApp()", proteus: "engineering.lifecycle + capability.app-lifecycle/page-lifecycle", status: "ok", group: "api" },
|
|
1318
|
+
{ mp: "wx.shareAppMessage/requestSubscribeMessage", proteus: "capability.share / notification", status: "ok", group: "api" },
|
|
1319
|
+
{ mp: "wx.login/checkSession/getUserInfo/authorize", proteus: "capability.login / auth / permission", status: "ok", group: "api" },
|
|
1320
|
+
{ mp: "wx.getUpdateManager", proteus: "capability.update (L2)", status: "compat", group: "api" },
|
|
1321
|
+
{ mp: "wx.requestWeChatPay/navigateToMiniProgram/\u6A21\u677F\u6D88\u606F/\u5BA2\u670D\uFF08\u5FAE\u4FE1\u79C1\u6709\uFF09", proteus: "capability.mini-program", status: "private", group: "api" }
|
|
1322
|
+
];
|
|
1323
|
+
var MP_MAPPING_MATRIX = [...MP_COMPONENTS, ...MP_API_GROUPS];
|
|
1324
|
+
|
|
1325
|
+
// src/dispatcher.ts
|
|
1326
|
+
var DispatcherError = class extends Error {
|
|
1327
|
+
constructor(code, detail) {
|
|
1328
|
+
super(`unknown primitive tag: ${detail.type}\uFF08\u987B\u5728 G-32 \u539F\u8BED\u8868\u5185\uFF0C\u7F16\u8BD1\u671F\u62E6\u622A\uFF1B\u8FD0\u884C\u671F\u515C\u5E95\uFF09`);
|
|
1329
|
+
this.code = code;
|
|
1330
|
+
this.detail = detail;
|
|
1331
|
+
this.name = "DispatcherError";
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
function createNodeOpsDispatcher(initialBackend) {
|
|
1335
|
+
let current = initialBackend;
|
|
1336
|
+
const history = [];
|
|
1337
|
+
const trace = [];
|
|
1338
|
+
let seq = 0;
|
|
1339
|
+
let handleIds = /* @__PURE__ */ new WeakMap();
|
|
1340
|
+
let nextHandleId = 1;
|
|
1341
|
+
function idOf(handle) {
|
|
1342
|
+
if (handle && typeof handle === "object") {
|
|
1343
|
+
let id = handleIds.get(handle);
|
|
1344
|
+
if (id === void 0) {
|
|
1345
|
+
id = nextHandleId++;
|
|
1346
|
+
handleIds.set(handle, id);
|
|
1347
|
+
}
|
|
1348
|
+
return id;
|
|
1349
|
+
}
|
|
1350
|
+
return 0;
|
|
1351
|
+
}
|
|
1352
|
+
function toIRNode(type, props = {}) {
|
|
1353
|
+
const cir = toComponentIR(type, props);
|
|
1354
|
+
if (!cir) throw new DispatcherError("unknown.primitive", { type });
|
|
1355
|
+
return { type: cir.tag, semantic: cir.semantic, props: { ...cir.props }, children: [] };
|
|
1356
|
+
}
|
|
1357
|
+
const nodeOps = {
|
|
1358
|
+
createElement(type, props = {}) {
|
|
1359
|
+
const ir = toIRNode(type, props);
|
|
1360
|
+
const handle = current.createElement(ir);
|
|
1361
|
+
trace.push({ seq: ++seq, op: "createElement", type, semantic: ir.semantic ?? type });
|
|
1362
|
+
return handle;
|
|
1363
|
+
},
|
|
1364
|
+
insert(child, parent, anchor) {
|
|
1365
|
+
current.insert(child, parent, anchor);
|
|
1366
|
+
trace.push({ seq: ++seq, op: "insert", child: idOf(child), parent: parent ? idOf(parent) : null, anchor: anchor ? idOf(anchor) : null });
|
|
1367
|
+
},
|
|
1368
|
+
remove(child) {
|
|
1369
|
+
current.remove(child);
|
|
1370
|
+
trace.push({ seq: ++seq, op: "remove", child: idOf(child) });
|
|
1371
|
+
},
|
|
1372
|
+
patchProp(el, key, prev, next) {
|
|
1373
|
+
current.patchProp(el, key, prev, next);
|
|
1374
|
+
trace.push({ seq: ++seq, op: "patchProp", el: idOf(el), key, next });
|
|
1375
|
+
},
|
|
1376
|
+
setText(el, text) {
|
|
1377
|
+
current.setText(el, text);
|
|
1378
|
+
trace.push({ seq: ++seq, op: "setText", el: idOf(el), text });
|
|
1379
|
+
},
|
|
1380
|
+
createText(text) {
|
|
1381
|
+
const handle = current.createElement({ type: "text", props: {}, children: [], semantic: void 0 });
|
|
1382
|
+
trace.push({ seq: ++seq, op: "createText", text });
|
|
1383
|
+
return handle;
|
|
1384
|
+
},
|
|
1385
|
+
createComment(text) {
|
|
1386
|
+
const handle = current.createElement({ type: "comment", props: {}, children: [] });
|
|
1387
|
+
trace.push({ seq: ++seq, op: "createComment", text });
|
|
1388
|
+
return handle;
|
|
1389
|
+
},
|
|
1390
|
+
parentNode(node) {
|
|
1391
|
+
const fn = current.parentNode;
|
|
1392
|
+
const r = fn ? fn(node) : null;
|
|
1393
|
+
trace.push({ seq: ++seq, op: "parentNode", node: idOf(node), result: r ? idOf(r) : null });
|
|
1394
|
+
return r ?? null;
|
|
1395
|
+
},
|
|
1396
|
+
nextSibling(node) {
|
|
1397
|
+
const fn = current.nextSibling;
|
|
1398
|
+
const r = fn ? fn(node) : null;
|
|
1399
|
+
trace.push({ seq: ++seq, op: "nextSibling", node: idOf(node), result: r ? idOf(r) : null });
|
|
1400
|
+
return r ?? null;
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
return {
|
|
1404
|
+
get currentBackend() {
|
|
1405
|
+
return current;
|
|
1406
|
+
},
|
|
1407
|
+
switchBackend(next, opts) {
|
|
1408
|
+
const strategy = opts?.strategy ?? "rebuild";
|
|
1409
|
+
history.push(current);
|
|
1410
|
+
trace.push({ seq: ++seq, op: "switchBackend", from: current.id, to: next.id, strategy });
|
|
1411
|
+
current = next;
|
|
1412
|
+
},
|
|
1413
|
+
get switchHistory() {
|
|
1414
|
+
return history;
|
|
1415
|
+
},
|
|
1416
|
+
nodeOps,
|
|
1417
|
+
toIRNode,
|
|
1418
|
+
trace,
|
|
1419
|
+
clearTrace() {
|
|
1420
|
+
trace.length = 0;
|
|
1421
|
+
seq = 0;
|
|
1422
|
+
handleIds = /* @__PURE__ */ new WeakMap();
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function renderIRTree(backend, ir, insertInto) {
|
|
1427
|
+
const root = backend.createElement(ir);
|
|
1428
|
+
for (const child of ir.children) {
|
|
1429
|
+
const c = renderIRTree(backend, child);
|
|
1430
|
+
if (insertInto) insertInto(c, root);
|
|
1431
|
+
else backend.insert(c, root);
|
|
1432
|
+
}
|
|
1433
|
+
return root;
|
|
1434
|
+
}
|
|
1435
|
+
function semanticSequence(ir) {
|
|
1436
|
+
return [ir.semantic ?? ir.type, ...ir.children.flatMap(semanticSequence)];
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// src/host-conformance.ts
|
|
1440
|
+
function createHostRuntimeStub() {
|
|
1441
|
+
const rt = {
|
|
1442
|
+
id: "terminal",
|
|
1443
|
+
state: "created",
|
|
1444
|
+
threads: ["main"],
|
|
1445
|
+
workers: [],
|
|
1446
|
+
queue: [],
|
|
1447
|
+
bootstrap() {
|
|
1448
|
+
rt.state = "running";
|
|
1449
|
+
return rt;
|
|
1450
|
+
},
|
|
1451
|
+
suspend() {
|
|
1452
|
+
rt.state = "suspended";
|
|
1453
|
+
},
|
|
1454
|
+
resume() {
|
|
1455
|
+
rt.state = "running";
|
|
1456
|
+
},
|
|
1457
|
+
destroy() {
|
|
1458
|
+
rt.state = "destroyed";
|
|
1459
|
+
rt.queue = [];
|
|
1460
|
+
rt.workers = [];
|
|
1461
|
+
},
|
|
1462
|
+
createWorker() {
|
|
1463
|
+
const w = { id: `w${rt.workers.length + 1}`, thread: `worker${rt.workers.length + 1}` };
|
|
1464
|
+
rt.workers.push(w);
|
|
1465
|
+
rt.threads.push(w.thread);
|
|
1466
|
+
return w;
|
|
1467
|
+
},
|
|
1468
|
+
postMessage() {
|
|
1469
|
+
return true;
|
|
1470
|
+
},
|
|
1471
|
+
enqueue(task, priority = 2) {
|
|
1472
|
+
rt.queue.push({ task, priority });
|
|
1473
|
+
},
|
|
1474
|
+
nextTick(fn) {
|
|
1475
|
+
rt.queue.push({ task: fn, priority: 0 });
|
|
1476
|
+
},
|
|
1477
|
+
drain() {
|
|
1478
|
+
rt.queue.sort((a, b) => a.priority - b.priority);
|
|
1479
|
+
const out = [];
|
|
1480
|
+
while (rt.queue.length) rt.queue.shift();
|
|
1481
|
+
return out;
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
return rt;
|
|
1485
|
+
}
|
|
1486
|
+
function createCarrierStub(kind) {
|
|
1487
|
+
return {
|
|
1488
|
+
id: kind,
|
|
1489
|
+
boundaries: 0,
|
|
1490
|
+
capabilities: kind === "jsi" ? { threadAffinity: true, trueConcurrency: false, realtime: { capable: false } } : { threadAffinity: false, trueConcurrency: true, realtime: { capable: true } },
|
|
1491
|
+
cross() {
|
|
1492
|
+
if (kind === "aot") return 0;
|
|
1493
|
+
this.boundaries++;
|
|
1494
|
+
return this.boundaries;
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
function buildProductIR(dispatch) {
|
|
1499
|
+
const grid = dispatch.toIRNode("p-grid", { minColWidth: 160 });
|
|
1500
|
+
const box1 = dispatch.toIRNode("p-box", {});
|
|
1501
|
+
const box2 = dispatch.toIRNode("p-box", {});
|
|
1502
|
+
const text = dispatch.toIRNode("p-text", { content: "\u5546\u54C1 A" });
|
|
1503
|
+
const button = dispatch.toIRNode("p-button", { variant: "primary" });
|
|
1504
|
+
return {
|
|
1505
|
+
...dispatch.toIRNode("p-page", { title: "Product" }),
|
|
1506
|
+
children: [
|
|
1507
|
+
{ ...grid, children: [{ ...box1, children: [text] }, { ...box2, children: [button] }] }
|
|
1508
|
+
]
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
var tests = [];
|
|
1512
|
+
var register = (group) => (id, fn) => tests.push({ id: `${group}-${id}`, group, fn });
|
|
1513
|
+
var H01 = register("H-01");
|
|
1514
|
+
var H02 = register("H-02");
|
|
1515
|
+
var H03 = register("H-03");
|
|
1516
|
+
var H04 = register("H-04");
|
|
1517
|
+
var H05 = register("H-05");
|
|
1518
|
+
var H06 = register("H-06");
|
|
1519
|
+
var H07 = register("H-07");
|
|
1520
|
+
var H08 = register("H-08");
|
|
1521
|
+
var assert = (c, m) => {
|
|
1522
|
+
if (!c) throw new Error(m ?? "assertion failed");
|
|
1523
|
+
};
|
|
1524
|
+
H01("01", ({ host }) => assert(host.state === "running", "Runtime \u5E94\u5DF2 bootstrap"));
|
|
1525
|
+
H01("02", ({ carrier }) => assert(carrier && carrier.id === "jsi", "Carrier \u5E94\u5DF2\u6CE8\u518C"));
|
|
1526
|
+
H01("03", ({ dispatch, backendA }) => assert(dispatch.currentBackend === backendA, "Backend \u5E94\u5DF2\u6CE8\u518C"));
|
|
1527
|
+
H01("04", ({ host, dispatch, backendA }) => assert(host.state === "running" && dispatch.currentBackend === backendA, "\u6CE8\u518C\u5148\u4E8E bootstrap\uFF08G-41.6\uFF09"));
|
|
1528
|
+
H02("01", ({ host }) => {
|
|
1529
|
+
host.suspend();
|
|
1530
|
+
assert(host.state === "suspended");
|
|
1531
|
+
host.resume();
|
|
1532
|
+
});
|
|
1533
|
+
H02("02", ({ host }) => assert(host.state === "running"));
|
|
1534
|
+
H02("03", ({ host }) => {
|
|
1535
|
+
const w = host.createWorker();
|
|
1536
|
+
assert(host.threads.includes(w.thread), "createWorker \u5E94\u4EA7\u751F\u72EC\u7ACB\u7EBF\u7A0B");
|
|
1537
|
+
});
|
|
1538
|
+
H02("04", ({}) => {
|
|
1539
|
+
const r2 = createHostRuntimeStub();
|
|
1540
|
+
r2.bootstrap();
|
|
1541
|
+
r2.enqueue(() => 1);
|
|
1542
|
+
r2.destroy();
|
|
1543
|
+
assert(r2.state === "destroyed" && r2.queue.length === 0, "destroy \u5E94\u6E05\u7406\u961F\u5217");
|
|
1544
|
+
});
|
|
1545
|
+
var snapA;
|
|
1546
|
+
var snapB;
|
|
1547
|
+
H03("01", ({ backendA, productIR }) => {
|
|
1548
|
+
const root = renderIRTree(backendA, productIR);
|
|
1549
|
+
snapA = JSON.stringify(toPlainTree(root));
|
|
1550
|
+
assert(snapA.includes("grid"), "Backend A \u6E32\u67D3\u5E94\u542B grid");
|
|
1551
|
+
});
|
|
1552
|
+
H03("02", ({ dispatch, backendB }) => {
|
|
1553
|
+
dispatch.switchBackend(backendB);
|
|
1554
|
+
assert(dispatch.currentBackend === backendB, "switchBackend \u5E94\u751F\u6548");
|
|
1555
|
+
});
|
|
1556
|
+
H03("03", ({ backendB, productIR }) => {
|
|
1557
|
+
const root = renderIRTree(backendB, productIR);
|
|
1558
|
+
snapB = JSON.stringify(toWidgetTree(root));
|
|
1559
|
+
assert(snapB.includes("GridView"), "Backend B \u6E32\u67D3\u5E94\u542B GridView");
|
|
1560
|
+
});
|
|
1561
|
+
H03("04", () => {
|
|
1562
|
+
assert(snapA && snapB, "\u53CC\u5F15\u64CE\u5FEB\u7167\u7F3A\u5931");
|
|
1563
|
+
assert(snapA.length > 0 && snapB.length > 0);
|
|
1564
|
+
});
|
|
1565
|
+
H04("01", ({ backendA, productIR }) => {
|
|
1566
|
+
const root = renderIRTree(backendA, productIR);
|
|
1567
|
+
const tree = JSON.stringify(toPlainTree(root));
|
|
1568
|
+
assert(tree.includes('"grid"'), "\u540E\u7AEF\u5E94\u57FA\u4E8E semantic \u5206\u53D1\uFF08G-37.1\uFF1Alayout.grid \u2192 grid\uFF09");
|
|
1569
|
+
});
|
|
1570
|
+
H04("02", ({ dispatch }) => {
|
|
1571
|
+
let threw = false;
|
|
1572
|
+
try {
|
|
1573
|
+
dispatch.toIRNode("p-unknown-thing", {});
|
|
1574
|
+
} catch (e) {
|
|
1575
|
+
threw = e instanceof DispatcherError;
|
|
1576
|
+
}
|
|
1577
|
+
assert(threw, "\u672A\u77E5\u539F\u8BED\u5E94\u88AB\u62E6\u622A\uFF08G-32.2\uFF1B\u7F16\u8BD1\u671F\u62E6\u622A\uFF0C\u8FD0\u884C\u671F\u5151\u5E95\uFF09");
|
|
1578
|
+
});
|
|
1579
|
+
H04("03", ({ host }) => {
|
|
1580
|
+
const before = host.threads.length;
|
|
1581
|
+
host.createWorker();
|
|
1582
|
+
assert(host.threads.length === before + 1, "\u6846\u67B6\u4E0D\u5F97\u76F4\u63A5\u5EFA\u7EBF\u7A0B\uFF0C\u987B\u59D4\u6258 runtime.createWorker\uFF08G-41.1\uFF09");
|
|
1583
|
+
});
|
|
1584
|
+
H04("04", () => {
|
|
1585
|
+
const src = createHeadlessBackend.toString() + createFlutterBackend.toString();
|
|
1586
|
+
assert(!/from ['"]vue['"]/.test(src) && !/@vue\/runtime/.test(src), "\u5F15\u64CE\u4E0D\u5F97 import vue\uFF08G-41.3\uFF09");
|
|
1587
|
+
});
|
|
1588
|
+
H04("05", ({ host }) => {
|
|
1589
|
+
const src = (host.constructor?.toString?.() ?? "") + createHostRuntimeStub.toString();
|
|
1590
|
+
assert(!/semantic\s*===/.test(src), "\u5BBF\u4E3B\u4E0D\u5F97\u89E3\u6790 IR \u5B57\u6BB5\uFF08G-41.2\uFF09");
|
|
1591
|
+
});
|
|
1592
|
+
H05("01", ({ dispatch, backendB }) => {
|
|
1593
|
+
dispatch.switchBackend(backendB);
|
|
1594
|
+
assert(dispatch.currentBackend === backendB, "\u70ED\u5207\u6362\u540E currentBackend \u5E94\u53D8\u66F4");
|
|
1595
|
+
});
|
|
1596
|
+
H05("02", ({ backendB, productIR }) => {
|
|
1597
|
+
const root = renderIRTree(backendB, productIR);
|
|
1598
|
+
assert(JSON.stringify(toWidgetTree(root)).includes("FilledButton"), "\u70ED\u5207\u6362\u540E\u53EF\u91CD\u65B0\u6E32\u67D3");
|
|
1599
|
+
});
|
|
1600
|
+
H05("03", ({ dispatch, backendA, productIR }) => {
|
|
1601
|
+
dispatch.switchBackend(backendA);
|
|
1602
|
+
const root = renderIRTree(backendA, productIR);
|
|
1603
|
+
assert(JSON.stringify(toPlainTree(root)).includes("button"), "\u5207\u56DE Backend A \u4ECD\u6B63\u786E");
|
|
1604
|
+
});
|
|
1605
|
+
H05("04", ({ backendA }) => {
|
|
1606
|
+
const cap2 = backendA.capabilities;
|
|
1607
|
+
assert(cap2 && typeof cap2 === "object", "\u540E\u7AEF\u5FC5\u987B\u58F0\u660E capabilities \u5BF9\u8C61");
|
|
1608
|
+
for (const key of ["layout", "glass", "blur", "animation", "textureSharing", "remoteRendering", "ssr", "input"]) {
|
|
1609
|
+
assert(key in cap2, `capabilities \u7F3A\u5931\u5B57\u6BB5: ${key}\uFF08G-37.3 \u8BDA\u5B9E\u58F0\u660E\uFF09`);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
H06("01", ({ backendA, backendB }) => assert(backendA !== backendB, "\u540C\u9875\u9762\u53EF\u6301\u6709\u591A\u4E2A Backend \u5B9E\u4F8B"));
|
|
1613
|
+
H06("02", ({}) => {
|
|
1614
|
+
const cir = toComponentIR("p-canvas", { engine: "skia", resolution: 2 });
|
|
1615
|
+
assert(cir !== null && cir.semantic === "ui.canvas" && cir.props.engine === "skia", "\u5F15\u64CE/\u5C5E\u6027\u5E94\u4F5C\u4E3A\u8BED\u4E49\u5C5E\u6027\u8FDB C-IR");
|
|
1616
|
+
});
|
|
1617
|
+
H06("03", ({}) => {
|
|
1618
|
+
const cir = toComponentIR("p-grid", { minColWidth: 160 });
|
|
1619
|
+
assert(cir !== null && cir.props.minColWidth === 160, "\u539F\u8BED\u5C5E\u6027\u5E94\u900F\u4F20\u8FDB C-IR");
|
|
1620
|
+
});
|
|
1621
|
+
H06("04", ({ dispatch, backendB }) => {
|
|
1622
|
+
dispatch.switchBackend(backendB);
|
|
1623
|
+
assert(dispatch.currentBackend === backendB, "Dispatcher \u5355\u5B9E\u4F8B\u652F\u6301\u591A\u540E\u7AEF\uFF08\u65B9\u6848 B\uFF09");
|
|
1624
|
+
});
|
|
1625
|
+
H07("01", ({ carrier }) => assert(typeof carrier.capabilities.threadAffinity === "boolean", "Carrier \u5E94\u58F0\u660E threadAffinity"));
|
|
1626
|
+
H07("02", ({}) => {
|
|
1627
|
+
const jsi = createCarrierStub("jsi");
|
|
1628
|
+
const aot = createCarrierStub("aot");
|
|
1629
|
+
assert(jsi.capabilities.trueConcurrency === false && aot.capabilities.trueConcurrency === true, "JSI \u53D7\u9650\u3001AOT \u4E0D\u53D7\u9650\uFF08G-40\uFF09");
|
|
1630
|
+
});
|
|
1631
|
+
H07("03", ({}) => {
|
|
1632
|
+
const aot = createCarrierStub("aot");
|
|
1633
|
+
aot.cross();
|
|
1634
|
+
aot.cross();
|
|
1635
|
+
assert(aot.boundaries === 0, "AOT \u8DE8\u754C\u6210\u672C\u4E3A 0");
|
|
1636
|
+
});
|
|
1637
|
+
H07("04", ({}) => {
|
|
1638
|
+
const jsi = createCarrierStub("jsi");
|
|
1639
|
+
const aot = createCarrierStub("aot");
|
|
1640
|
+
assert(jsi.capabilities.realtime.capable === false && aot.capabilities.realtime.capable === true, "realtime \u80FD\u529B\u4EC5\u5728 AOT \u53EF\u7528");
|
|
1641
|
+
});
|
|
1642
|
+
H08("01", ({ dispatch }) => {
|
|
1643
|
+
let ok = false;
|
|
1644
|
+
try {
|
|
1645
|
+
dispatch.toIRNode("div", {});
|
|
1646
|
+
} catch {
|
|
1647
|
+
ok = true;
|
|
1648
|
+
}
|
|
1649
|
+
assert(ok, "\u672A\u77E5\u539F\u8BED\u5E94\u629B\u9519\u800C\u975E\u9759\u9ED8");
|
|
1650
|
+
});
|
|
1651
|
+
H08("02", ({ dispatch }) => {
|
|
1652
|
+
const ir = dispatch.toIRNode("p-grid", { minColWidth: 160 });
|
|
1653
|
+
assert(ir.semantic === "layout.grid", "\u6846\u67B6\u8DEF\u5F84 semantic \u6052\u5B58\u5728\uFF08G-37.1\uFF1A\u540E\u7AEF\u6309 semantic \u5206\u53D1\u7684\u524D\u63D0\uFF09");
|
|
1654
|
+
});
|
|
1655
|
+
H08("03", ({}) => {
|
|
1656
|
+
const r = createHostRuntimeStub();
|
|
1657
|
+
r.bootstrap();
|
|
1658
|
+
r.destroy();
|
|
1659
|
+
assert(r.state === "destroyed", "destroy \u540E\u72B6\u6001\u5E94\u4E3A destroyed");
|
|
1660
|
+
});
|
|
1661
|
+
function runHostConformance(opts = {}) {
|
|
1662
|
+
const host = opts.host ?? createHostRuntimeStub();
|
|
1663
|
+
const carrier = opts.carrier ?? createCarrierStub("jsi");
|
|
1664
|
+
const backendA = opts.backendA ?? createHeadlessBackend();
|
|
1665
|
+
const backendB = opts.backendB ?? createFlutterBackend();
|
|
1666
|
+
const dispatch = createNodeOpsDispatcher(backendA);
|
|
1667
|
+
const productIR = buildProductIR(dispatch);
|
|
1668
|
+
const ctx = { host, carrier, backendA, backendB, dispatch, productIR };
|
|
1669
|
+
const results = [];
|
|
1670
|
+
host.bootstrap();
|
|
1671
|
+
snapA = "";
|
|
1672
|
+
snapB = "";
|
|
1673
|
+
for (const t of tests) {
|
|
1674
|
+
if (opts.only && !t.group.startsWith(opts.only)) continue;
|
|
1675
|
+
try {
|
|
1676
|
+
const ret = t.fn(ctx);
|
|
1677
|
+
if (ret === "SKIP") results.push({ id: t.id, status: "SKIP" });
|
|
1678
|
+
else results.push({ id: t.id, status: "PASS" });
|
|
1679
|
+
} catch (e) {
|
|
1680
|
+
results.push({ id: t.id, status: "FAIL", error: e.message });
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
total: results.length,
|
|
1685
|
+
pass: results.filter((r) => r.status === "PASS").length,
|
|
1686
|
+
fail: results.filter((r) => r.status === "FAIL").length,
|
|
1687
|
+
skip: results.filter((r) => r.status === "SKIP").length,
|
|
1688
|
+
results
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function formatHostConformance(s) {
|
|
1692
|
+
const lines = ["[G-41 Host Conformance\uFF08H-01~H-08\uFF09]"];
|
|
1693
|
+
for (const r of s.results) {
|
|
1694
|
+
const icon = r.status === "PASS" ? "\u2705" : r.status === "SKIP" ? "\u23ED\uFE0F " : "\u274C";
|
|
1695
|
+
lines.push(` ${icon} ${r.id}${r.status === "FAIL" ? ` \u2014 ${r.error ?? ""}` : ""}`);
|
|
1696
|
+
}
|
|
1697
|
+
lines.push("\u2500".repeat(30));
|
|
1698
|
+
lines.push(`\u603B\u8BA1\uFF1APASS=${s.pass} FAIL=${s.fail} SKIP=${s.skip}\uFF08${s.total} \u9879\uFF09\u2014\u2014CMP058\uFF1Afailed=0 \u624D\u5141\u8BB8\u4E0A\u7EBF`);
|
|
1699
|
+
return lines.join("\n");
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// src/vue-bridge.ts
|
|
1703
|
+
import { createRenderer } from "@vue/runtime-core";
|
|
1704
|
+
function createVueRendererOptions(dispatch) {
|
|
1705
|
+
const d = dispatch.nodeOps;
|
|
1706
|
+
return {
|
|
1707
|
+
// Vue 真实签名:createElement(type, namespace?, isCustomizedBuiltIn?, vnodeProps?)
|
|
1708
|
+
createElement(type, _namespace, _isCustomizedBuiltIn, vnodeProps) {
|
|
1709
|
+
return d.createElement(type, vnodeProps ?? {});
|
|
1710
|
+
},
|
|
1711
|
+
createText(text) {
|
|
1712
|
+
return d.createText(text);
|
|
1713
|
+
},
|
|
1714
|
+
createComment(text) {
|
|
1715
|
+
return d.createComment(text);
|
|
1716
|
+
},
|
|
1717
|
+
setText(node, text) {
|
|
1718
|
+
d.setText(node, text);
|
|
1719
|
+
},
|
|
1720
|
+
// 元素直接文本内容(Vue 清空子树时调用)——语义等价 setText
|
|
1721
|
+
setElementText(el, text) {
|
|
1722
|
+
d.setText(el, text);
|
|
1723
|
+
},
|
|
1724
|
+
insert(child, parent, anchor) {
|
|
1725
|
+
d.insert(child, parent, anchor ?? void 0);
|
|
1726
|
+
},
|
|
1727
|
+
remove(node) {
|
|
1728
|
+
d.remove(node);
|
|
1729
|
+
},
|
|
1730
|
+
parentNode(node) {
|
|
1731
|
+
return d.parentNode(node) ?? null;
|
|
1732
|
+
},
|
|
1733
|
+
nextSibling(node) {
|
|
1734
|
+
return d.nextSibling(node) ?? null;
|
|
1735
|
+
},
|
|
1736
|
+
patchProp(el, key, prev, next) {
|
|
1737
|
+
d.patchProp(el, key, prev, next);
|
|
1738
|
+
},
|
|
1739
|
+
// —— 以下对齐 renderer-app host.ts 语义降级(非核心渲染路径) ——
|
|
1740
|
+
querySelector() {
|
|
1741
|
+
return null;
|
|
1742
|
+
},
|
|
1743
|
+
setScopeId() {
|
|
1744
|
+
},
|
|
1745
|
+
cloneNode() {
|
|
1746
|
+
return d.createComment("");
|
|
1747
|
+
},
|
|
1748
|
+
insertStaticContent(_content, parent, anchor) {
|
|
1749
|
+
const c = d.createComment("");
|
|
1750
|
+
if (parent) d.insert(c, parent, anchor ?? void 0);
|
|
1751
|
+
return [c, c];
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
function createProteusRenderer(dispatch) {
|
|
1756
|
+
const renderer = createRenderer(createVueRendererOptions(dispatch));
|
|
1757
|
+
return { dispatch, renderer };
|
|
1758
|
+
}
|
|
1759
|
+
function createProteusRendererForBackend(backend) {
|
|
1760
|
+
return createProteusRenderer(createNodeOpsDispatcher(backend));
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/web-host.ts
|
|
1764
|
+
function defaultWorkerFactory(scriptUrl) {
|
|
1765
|
+
const g = globalThis;
|
|
1766
|
+
if (typeof g.Worker === "function") {
|
|
1767
|
+
try {
|
|
1768
|
+
const w = new g.Worker(scriptUrl ?? "");
|
|
1769
|
+
return { raw: w, terminate: () => w.terminate() };
|
|
1770
|
+
} catch {
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return { raw: null, terminate() {
|
|
1774
|
+
} };
|
|
1775
|
+
}
|
|
1776
|
+
function createWebHostRuntime(opts = {}) {
|
|
1777
|
+
const createWorkerImpl = opts.createWorkerImpl ?? defaultWorkerFactory;
|
|
1778
|
+
const g = opts.env ?? globalThis;
|
|
1779
|
+
const rt = {
|
|
1780
|
+
id: "web",
|
|
1781
|
+
state: "created",
|
|
1782
|
+
threads: ["main"],
|
|
1783
|
+
workers: [],
|
|
1784
|
+
queue: [],
|
|
1785
|
+
bootstrap() {
|
|
1786
|
+
rt.state = "running";
|
|
1787
|
+
return rt;
|
|
1788
|
+
},
|
|
1789
|
+
suspend() {
|
|
1790
|
+
rt.state = "suspended";
|
|
1791
|
+
},
|
|
1792
|
+
resume() {
|
|
1793
|
+
rt.state = "running";
|
|
1794
|
+
},
|
|
1795
|
+
destroy() {
|
|
1796
|
+
rt.state = "destroyed";
|
|
1797
|
+
rt.queue = [];
|
|
1798
|
+
for (const w of rt.workers) w.terminate();
|
|
1799
|
+
rt.workers = [];
|
|
1800
|
+
rt.threads = ["main"];
|
|
1801
|
+
},
|
|
1802
|
+
createWorker(scriptUrl) {
|
|
1803
|
+
const impl = createWorkerImpl(scriptUrl);
|
|
1804
|
+
const w = {
|
|
1805
|
+
id: `w${rt.workers.length + 1}`,
|
|
1806
|
+
thread: `worker${rt.workers.length + 1}`,
|
|
1807
|
+
raw: impl.raw,
|
|
1808
|
+
terminate: impl.terminate
|
|
1809
|
+
};
|
|
1810
|
+
rt.workers.push(w);
|
|
1811
|
+
rt.threads.push(w.thread);
|
|
1812
|
+
return w;
|
|
1813
|
+
},
|
|
1814
|
+
postMessage() {
|
|
1815
|
+
return true;
|
|
1816
|
+
},
|
|
1817
|
+
enqueue(task, priority = 2) {
|
|
1818
|
+
rt.queue.push({ task, priority });
|
|
1819
|
+
},
|
|
1820
|
+
nextTick(fn) {
|
|
1821
|
+
rt.queue.push({ task: fn, priority: 0 });
|
|
1822
|
+
},
|
|
1823
|
+
drain() {
|
|
1824
|
+
rt.queue.sort((a, b) => a.priority - b.priority);
|
|
1825
|
+
const out = [];
|
|
1826
|
+
while (rt.queue.length) {
|
|
1827
|
+
const { task } = rt.queue.shift();
|
|
1828
|
+
out.push(task());
|
|
1829
|
+
}
|
|
1830
|
+
return out;
|
|
1831
|
+
},
|
|
1832
|
+
async flush() {
|
|
1833
|
+
if (!rt.queue.length) return;
|
|
1834
|
+
const tasks = rt.queue.splice(0).sort((a, b) => a.priority - b.priority);
|
|
1835
|
+
for (const { task } of tasks) {
|
|
1836
|
+
await Promise.resolve();
|
|
1837
|
+
task();
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
bindPageVisibility() {
|
|
1841
|
+
const doc = g.document;
|
|
1842
|
+
if (!doc || typeof doc.visibilityState !== "string") {
|
|
1843
|
+
return () => {
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
const onVis = () => {
|
|
1847
|
+
if (doc.visibilityState === "hidden") rt.suspend();
|
|
1848
|
+
else rt.resume();
|
|
1849
|
+
};
|
|
1850
|
+
doc.addEventListener("visibilitychange", onVis);
|
|
1851
|
+
return () => doc.removeEventListener("visibilitychange", onVis);
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
return rt;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/hot-switch.ts
|
|
1858
|
+
function createBackendSwitcher(dispatch) {
|
|
1859
|
+
let currentIR = null;
|
|
1860
|
+
let root = null;
|
|
1861
|
+
return {
|
|
1862
|
+
get dispatch() {
|
|
1863
|
+
return dispatch;
|
|
1864
|
+
},
|
|
1865
|
+
get currentIR() {
|
|
1866
|
+
return currentIR;
|
|
1867
|
+
},
|
|
1868
|
+
get root() {
|
|
1869
|
+
return root;
|
|
1870
|
+
},
|
|
1871
|
+
mount(ir) {
|
|
1872
|
+
currentIR = ir;
|
|
1873
|
+
root = renderIRTree(dispatch.currentBackend, ir);
|
|
1874
|
+
return root;
|
|
1875
|
+
},
|
|
1876
|
+
switchBackend(next, opts = {}) {
|
|
1877
|
+
const strategy = opts.strategy ?? "rebuild";
|
|
1878
|
+
const from = dispatch.currentBackend;
|
|
1879
|
+
opts.onBeforeSwitch?.(from, next);
|
|
1880
|
+
dispatch.switchBackend(next, { strategy });
|
|
1881
|
+
if (strategy === "rehydrate" && currentIR) {
|
|
1882
|
+
root = renderIRTree(next, currentIR);
|
|
1883
|
+
} else {
|
|
1884
|
+
root = null;
|
|
1885
|
+
}
|
|
1886
|
+
opts.onAfterSwitch?.(from, next, root);
|
|
1887
|
+
return root;
|
|
1888
|
+
},
|
|
1889
|
+
destroy() {
|
|
1890
|
+
currentIR = null;
|
|
1891
|
+
root = null;
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// src/container-spi.ts
|
|
1897
|
+
var CONTAINER_PROFILES = {
|
|
1898
|
+
singlepage: { pageStack: false, multiBusiness: false, crashIsolation: 0, resourceQuota: false, keepAlive: false, windowManagement: false, embedded: false },
|
|
1899
|
+
stack: { pageStack: true, multiBusiness: false, crashIsolation: 0, resourceQuota: true, keepAlive: true, windowManagement: false, embedded: false },
|
|
1900
|
+
superapp: { pageStack: true, multiBusiness: true, crashIsolation: 2, resourceQuota: true, keepAlive: true, windowManagement: false, embedded: false },
|
|
1901
|
+
miniprogram: { pageStack: true, multiBusiness: true, crashIsolation: 1, resourceQuota: true, keepAlive: true, windowManagement: false, embedded: false },
|
|
1902
|
+
window: { pageStack: true, multiBusiness: false, crashIsolation: 0, resourceQuota: true, keepAlive: true, windowManagement: true, embedded: false },
|
|
1903
|
+
embedded: { pageStack: false, multiBusiness: false, crashIsolation: 0, resourceQuota: false, keepAlive: false, windowManagement: false, embedded: true }
|
|
1904
|
+
};
|
|
1905
|
+
var PAGE_STATE_TRANSITIONS = {
|
|
1906
|
+
created: ["mounted", "destroyed"],
|
|
1907
|
+
mounted: ["hidden", "destroyed", "crashed"],
|
|
1908
|
+
hidden: ["mounted", "destroyed"],
|
|
1909
|
+
crashed: ["destroyed"],
|
|
1910
|
+
destroyed: ["recycled"],
|
|
1911
|
+
recycled: []
|
|
1912
|
+
};
|
|
1913
|
+
function canTransitionPageState(from, to) {
|
|
1914
|
+
return PAGE_STATE_TRANSITIONS[from].includes(to);
|
|
1915
|
+
}
|
|
1916
|
+
var FIVE_ATOMIC_STEPS = ["unmount", "unbindEvents", "releaseResources", "destroyIR", "releaseQuota"];
|
|
1917
|
+
function assertAtomicDestroy(report) {
|
|
1918
|
+
if (report.steps.length !== 5) {
|
|
1919
|
+
throw new Error(`G-42.2 \u8FDD\u53CD\uFF1A\u9875\u9762\u9500\u6BC1\u5FC5\u987B\u4E94\u539F\u5B50\uFF08\u5B9E\u9645 ${report.steps.length} \u6B65\uFF1A${report.steps.join("\u2192") || "\u65E0"}\uFF09`);
|
|
1920
|
+
}
|
|
1921
|
+
for (let i = 0; i < FIVE_ATOMIC_STEPS.length; i++) {
|
|
1922
|
+
if (report.steps[i] !== FIVE_ATOMIC_STEPS[i]) {
|
|
1923
|
+
throw new Error(`G-42.2 \u8FDD\u53CD\uFF1A\u4E94\u539F\u5B50\u6B65\u9AA4\u987A\u5E8F\u9519\u8BEF\uFF08\u7B2C ${i + 1} \u6B65\u5E94\u4E3A ${FIVE_ATOMIC_STEPS[i]}\uFF0C\u5B9E\u9645 ${report.steps[i]}\uFF09`);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function createDestroyReport(pageId, reclaimedBytes = 0, durationMs = 0) {
|
|
1928
|
+
return { pageId, steps: [...FIVE_ATOMIC_STEPS], leaked: [], reclaimedBytes, durationMs };
|
|
1929
|
+
}
|
|
1930
|
+
var DEFAULT_STACK_POLICY = {
|
|
1931
|
+
maxDepth: 10,
|
|
1932
|
+
overflowStrategy: "destroy-oldest",
|
|
1933
|
+
keepAlive: { maxCount: 3, memoryBudgetBytes: 64 * 1024 * 1024 }
|
|
1934
|
+
};
|
|
1935
|
+
function profileOfContainer(id) {
|
|
1936
|
+
return CONTAINER_PROFILES[id] ?? { pageStack: false, multiBusiness: false, crashIsolation: 0, resourceQuota: false, keepAlive: false, windowManagement: false, embedded: false };
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/ownership.ts
|
|
1940
|
+
var OwnershipError = class extends Error {
|
|
1941
|
+
constructor(code, message, extra) {
|
|
1942
|
+
super(message);
|
|
1943
|
+
this.name = "OwnershipError";
|
|
1944
|
+
this.code = code;
|
|
1945
|
+
this.resourceId = extra.resourceId;
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
var OWNERSHIP_ERRORS = {
|
|
1949
|
+
useAfterMove: (id, movedTo) => new OwnershipError("use_after_move", `\u8D44\u6E90 ${id} \u5DF2\u8F6C\u79FB\u7ED9 ${movedTo}\uFF0C\u4E0D\u53EF\u518D\u8BBF\u95EE`, { resourceId: id }),
|
|
1950
|
+
useAfterDrop: (id) => new OwnershipError("use_after_drop", `\u8D44\u6E90 ${id} \u5DF2\u91CA\u653E\uFF0C\u4E0D\u53EF\u518D\u8BBF\u95EE`, { resourceId: id }),
|
|
1951
|
+
doubleMove: (id) => new OwnershipError("double_move", `\u8D44\u6E90 ${id} \u5DF2\u8F6C\u79FB\u8FC7\uFF0C\u4E0D\u53EF\u91CD\u590D\u8F6C\u79FB`, { resourceId: id }),
|
|
1952
|
+
hasActiveBorrows: (id, count) => new OwnershipError("has_active_borrows", `\u8D44\u6E90 ${id} \u6709 ${count} \u4E2A\u6D3B\u8DC3\u501F\u7528\uFF0C\u4E0D\u53EF\u91CA\u653E`, { resourceId: id }),
|
|
1953
|
+
alreadyDropped: (id) => new OwnershipError("already_dropped", `\u8D44\u6E90 ${id} \u5DF2\u91CA\u653E`, { resourceId: id }),
|
|
1954
|
+
notTransferable: (id, reason) => new OwnershipError("resource_not_transferable", `\u8D44\u6E90 ${id} \u4E0D\u53EF\u8F6C\u79FB\uFF1A${reason}`, { resourceId: id })
|
|
1955
|
+
};
|
|
1956
|
+
var OwnershipGraph = class {
|
|
1957
|
+
constructor(opts = {}) {
|
|
1958
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
1959
|
+
this.edges = [];
|
|
1960
|
+
this._nextId = 1;
|
|
1961
|
+
this._listeners = /* @__PURE__ */ new Set();
|
|
1962
|
+
if (opts.onMutate) this._listeners.add(opts.onMutate);
|
|
1963
|
+
}
|
|
1964
|
+
nextId() {
|
|
1965
|
+
return `res_${this._nextId++}`;
|
|
1966
|
+
}
|
|
1967
|
+
/** ★G-43 B4:动态订阅图 mutation(可任意时刻挂接;返回解绑) */
|
|
1968
|
+
subscribe(cb) {
|
|
1969
|
+
this._listeners.add(cb);
|
|
1970
|
+
return () => {
|
|
1971
|
+
this._listeners.delete(cb);
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
_emit(m) {
|
|
1975
|
+
for (const fn of this._listeners) fn(m);
|
|
1976
|
+
}
|
|
1977
|
+
register(opts) {
|
|
1978
|
+
const node = {
|
|
1979
|
+
id: opts.id,
|
|
1980
|
+
type: opts.type,
|
|
1981
|
+
byteSize: opts.byteSize,
|
|
1982
|
+
owner: opts.owner ?? null,
|
|
1983
|
+
state: "alive",
|
|
1984
|
+
createdAt: Date.now(),
|
|
1985
|
+
sourceLocation: opts.sourceLocation ?? null
|
|
1986
|
+
};
|
|
1987
|
+
this.nodes.set(opts.id, node);
|
|
1988
|
+
if (opts.owner) {
|
|
1989
|
+
this.edges.push({ kind: "owns", from: opts.owner, to: opts.id });
|
|
1990
|
+
}
|
|
1991
|
+
this._emit({ kind: "register", id: opts.id, owner: node.owner, type: node.type, byteSize: node.byteSize, sourceLocation: node.sourceLocation, ts: Date.now() });
|
|
1992
|
+
return node;
|
|
1993
|
+
}
|
|
1994
|
+
addEdge(edge) {
|
|
1995
|
+
this.edges.push(edge);
|
|
1996
|
+
this._emit({ kind: "edge", edge, ts: Date.now() });
|
|
1997
|
+
}
|
|
1998
|
+
setState(id, state) {
|
|
1999
|
+
const node = this.nodes.get(id);
|
|
2000
|
+
if (node) node.state = state;
|
|
2001
|
+
this._emit({ kind: "state", id, state, ts: Date.now() });
|
|
2002
|
+
}
|
|
2003
|
+
resourcesOf(owner) {
|
|
2004
|
+
return [...this.nodes.values()].filter((n) => n.owner === owner && n.state === "alive");
|
|
2005
|
+
}
|
|
2006
|
+
/** 无主资源检测(G-43.1——owner === null 的存活节点数为 0) */
|
|
2007
|
+
findOrphans() {
|
|
2008
|
+
return [...this.nodes.values()].filter((n) => n.owner === null && n.state === "alive");
|
|
2009
|
+
}
|
|
2010
|
+
/** 泄漏检测:页面销毁后仍存活的资源 + 反向引用链(谁还持有它——DevTools 泄漏定位) */
|
|
2011
|
+
detectLeaks(destroyedScope) {
|
|
2012
|
+
return this.resourcesOf(destroyedScope).map((n) => ({
|
|
2013
|
+
resourceId: n.id,
|
|
2014
|
+
type: n.type,
|
|
2015
|
+
byteSize: n.byteSize,
|
|
2016
|
+
sourceLocation: n.sourceLocation,
|
|
2017
|
+
referenceChain: this.backTrace(n.id)
|
|
2018
|
+
}));
|
|
2019
|
+
}
|
|
2020
|
+
/** 反向引用链(资源 ← 谁持有 ← 谁又持有……) */
|
|
2021
|
+
backTrace(resourceId) {
|
|
2022
|
+
const chain = [];
|
|
2023
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2024
|
+
const walk = (id) => {
|
|
2025
|
+
if (visited.has(id)) return;
|
|
2026
|
+
visited.add(id);
|
|
2027
|
+
for (const e of this.edges) {
|
|
2028
|
+
if (e.to === id && e.from !== id) {
|
|
2029
|
+
chain.push(`${e.from} --${e.kind}--> ${id}`);
|
|
2030
|
+
walk(e.from);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
walk(resourceId);
|
|
2035
|
+
return chain;
|
|
2036
|
+
}
|
|
2037
|
+
stats() {
|
|
2038
|
+
const alive = [...this.nodes.values()].filter((n) => n.state === "alive");
|
|
2039
|
+
return {
|
|
2040
|
+
total: this.nodes.size,
|
|
2041
|
+
alive: alive.length,
|
|
2042
|
+
moved: [...this.nodes.values()].filter((n) => n.state === "moved").length,
|
|
2043
|
+
dropped: [...this.nodes.values()].filter((n) => n.state === "dropped").length,
|
|
2044
|
+
totalBytes: alive.reduce((s, n) => s + n.byteSize, 0),
|
|
2045
|
+
edges: this.edges.length
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
};
|
|
2049
|
+
function createQuotaTracker() {
|
|
2050
|
+
const usage = /* @__PURE__ */ new Map();
|
|
2051
|
+
return {
|
|
2052
|
+
request(owner, bytes) {
|
|
2053
|
+
usage.set(owner, (usage.get(owner) ?? 0) + bytes);
|
|
2054
|
+
return usage.get(owner);
|
|
2055
|
+
},
|
|
2056
|
+
release(owner, bytes) {
|
|
2057
|
+
usage.set(owner, Math.max(0, (usage.get(owner) ?? 0) - bytes));
|
|
2058
|
+
return usage.get(owner);
|
|
2059
|
+
},
|
|
2060
|
+
usageOf(owner) {
|
|
2061
|
+
return usage.get(owner) ?? 0;
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
var Owned = class _Owned {
|
|
2066
|
+
constructor(opts) {
|
|
2067
|
+
this._state = "alive";
|
|
2068
|
+
this._movedTo = null;
|
|
2069
|
+
this._activeBorrows = 0;
|
|
2070
|
+
this._borrows = /* @__PURE__ */ new Set();
|
|
2071
|
+
this._listeners = /* @__PURE__ */ new Set();
|
|
2072
|
+
this.id = opts.id;
|
|
2073
|
+
this.resourceType = opts.resourceType;
|
|
2074
|
+
this.byteSize = opts.byteSize;
|
|
2075
|
+
this.owner = opts.owner;
|
|
2076
|
+
this._value = opts.value;
|
|
2077
|
+
this._graph = opts.graph;
|
|
2078
|
+
this._transferable = opts.transferable ?? false;
|
|
2079
|
+
this._releaseHook = opts.releaseHook ?? null;
|
|
2080
|
+
this._sourceLocation = opts.sourceLocation ?? null;
|
|
2081
|
+
this._graph.register({ id: opts.id, type: opts.resourceType, byteSize: opts.byteSize, owner: opts.owner, sourceLocation: this._sourceLocation });
|
|
2082
|
+
}
|
|
2083
|
+
get state() {
|
|
2084
|
+
return this._state;
|
|
2085
|
+
}
|
|
2086
|
+
get activeBorrows() {
|
|
2087
|
+
return this._activeBorrows;
|
|
2088
|
+
}
|
|
2089
|
+
/** ★G-43 B5:订阅状态变化(moved/dropped 时通知——返回解绑) */
|
|
2090
|
+
subscribe(cb) {
|
|
2091
|
+
this._listeners.add(cb);
|
|
2092
|
+
return () => {
|
|
2093
|
+
this._listeners.delete(cb);
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
_notify() {
|
|
2097
|
+
for (const fn of this._listeners) fn(this._state);
|
|
2098
|
+
}
|
|
2099
|
+
/** 存活断言(G-43.2/43.6:Move/Drop 后禁访问) */
|
|
2100
|
+
_assertAlive(op) {
|
|
2101
|
+
if (this._state === "moved") {
|
|
2102
|
+
throw OWNERSHIP_ERRORS.useAfterMove(this.id, this._movedTo ?? "?");
|
|
2103
|
+
}
|
|
2104
|
+
if (this._state === "dropped") {
|
|
2105
|
+
throw OWNERSHIP_ERRORS.useAfterDrop(this.id);
|
|
2106
|
+
}
|
|
2107
|
+
void op;
|
|
2108
|
+
}
|
|
2109
|
+
/** 读取(G-43.2:Move/Drop 后禁访问——use-after-move 拦截) */
|
|
2110
|
+
read() {
|
|
2111
|
+
this._assertAlive("read");
|
|
2112
|
+
return this._value;
|
|
2113
|
+
}
|
|
2114
|
+
/** ★Move 语义:转移所有权后原所有者不可再访问 */
|
|
2115
|
+
transferTo(targetOwner) {
|
|
2116
|
+
this._assertAlive("transferTo");
|
|
2117
|
+
if (this._state === "moved") throw OWNERSHIP_ERRORS.doubleMove(this.id);
|
|
2118
|
+
if (this._activeBorrows > 0) throw OWNERSHIP_ERRORS.hasActiveBorrows(this.id, this._activeBorrows);
|
|
2119
|
+
this._state = "moved";
|
|
2120
|
+
this._movedTo = targetOwner;
|
|
2121
|
+
this._graph.setState(this.id, "moved");
|
|
2122
|
+
this._graph.addEdge({ kind: "moved-from", from: targetOwner, to: this.id });
|
|
2123
|
+
this._notify();
|
|
2124
|
+
return new _Owned({
|
|
2125
|
+
id: `${this.id}@${targetOwner}`,
|
|
2126
|
+
resourceType: this.resourceType,
|
|
2127
|
+
byteSize: this.byteSize,
|
|
2128
|
+
owner: targetOwner,
|
|
2129
|
+
value: this._value,
|
|
2130
|
+
graph: this._graph,
|
|
2131
|
+
transferable: this._transferable,
|
|
2132
|
+
releaseHook: this._releaseHook ?? void 0,
|
|
2133
|
+
sourceLocation: this._sourceLocation
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
/** ★Borrow:临时借用(G-43.3 不逃逸作用域——drop 后失效) */
|
|
2137
|
+
borrow(scopeName = "anonymous") {
|
|
2138
|
+
this._assertAlive("borrow");
|
|
2139
|
+
const b = new Borrow(this, scopeName, this._graph);
|
|
2140
|
+
this._activeBorrows++;
|
|
2141
|
+
this._borrows.add(b);
|
|
2142
|
+
this._graph.addEdge({ kind: "borrows", from: scopeName, to: this.id, since: Date.now() });
|
|
2143
|
+
return b;
|
|
2144
|
+
}
|
|
2145
|
+
/** ★Weak:弱引用(打破循环) */
|
|
2146
|
+
weak() {
|
|
2147
|
+
return new Weak(this);
|
|
2148
|
+
}
|
|
2149
|
+
/** ★确定性 Drop(五阶段协议) */
|
|
2150
|
+
drop(opts = {}) {
|
|
2151
|
+
const { force = false } = opts;
|
|
2152
|
+
if (this._state === "dropped") return { ok: false, freedBytes: 0, freedHandles: 0, invalidatedBorrows: 0, error: OWNERSHIP_ERRORS.alreadyDropped(this.id) };
|
|
2153
|
+
if (this._state === "moved") return { ok: false, freedBytes: 0, freedHandles: 0, invalidatedBorrows: 0, error: OWNERSHIP_ERRORS.useAfterMove(this.id, this._movedTo ?? "?") };
|
|
2154
|
+
if (!force && this._activeBorrows > 0) {
|
|
2155
|
+
return { ok: false, freedBytes: 0, freedHandles: 0, invalidatedBorrows: 0, error: OWNERSHIP_ERRORS.hasActiveBorrows(this.id, this._activeBorrows) };
|
|
2156
|
+
}
|
|
2157
|
+
let invalidated = 0;
|
|
2158
|
+
for (const b of this._borrows) {
|
|
2159
|
+
if (b.valid) {
|
|
2160
|
+
b.invalidate();
|
|
2161
|
+
invalidated++;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
this._borrows.clear();
|
|
2165
|
+
this._activeBorrows = 0;
|
|
2166
|
+
let freedBytes = 0;
|
|
2167
|
+
if (this._releaseHook) {
|
|
2168
|
+
try {
|
|
2169
|
+
this._releaseHook(this._value);
|
|
2170
|
+
} catch {
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
freedBytes = this.byteSize;
|
|
2174
|
+
this._state = "dropped";
|
|
2175
|
+
this._graph.setState(this.id, "dropped");
|
|
2176
|
+
this._notify();
|
|
2177
|
+
return { ok: true, freedBytes, freedHandles: 1, invalidatedBorrows: invalidated };
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
var Borrow = class {
|
|
2181
|
+
constructor(source, scopeName, graph) {
|
|
2182
|
+
this.borrowedAt = Date.now();
|
|
2183
|
+
this._valid = true;
|
|
2184
|
+
this._source = source;
|
|
2185
|
+
this._scopeName = scopeName;
|
|
2186
|
+
this._graph = graph;
|
|
2187
|
+
}
|
|
2188
|
+
get valid() {
|
|
2189
|
+
return this._valid && this._source.state === "alive";
|
|
2190
|
+
}
|
|
2191
|
+
get durationMs() {
|
|
2192
|
+
return Date.now() - this.borrowedAt;
|
|
2193
|
+
}
|
|
2194
|
+
get() {
|
|
2195
|
+
if (!this.valid) return void 0;
|
|
2196
|
+
return this._source.read();
|
|
2197
|
+
}
|
|
2198
|
+
release() {
|
|
2199
|
+
if (!this._valid) return;
|
|
2200
|
+
this.invalidate();
|
|
2201
|
+
void this._graph;
|
|
2202
|
+
void this._scopeName;
|
|
2203
|
+
}
|
|
2204
|
+
invalidate() {
|
|
2205
|
+
this._valid = false;
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
var Weak = class {
|
|
2209
|
+
constructor(source) {
|
|
2210
|
+
this._source = source;
|
|
2211
|
+
}
|
|
2212
|
+
get alive() {
|
|
2213
|
+
return this._source.state === "alive";
|
|
2214
|
+
}
|
|
2215
|
+
/** 升级为借用(弱引用仅在持有者存活时可用) */
|
|
2216
|
+
upgrade() {
|
|
2217
|
+
if (!this.alive) return void 0;
|
|
2218
|
+
return this._source.borrow("weak-upgrade");
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
var Managed = class {
|
|
2222
|
+
constructor(opts) {
|
|
2223
|
+
this.disposed = false;
|
|
2224
|
+
this.value = opts.value;
|
|
2225
|
+
this.owner = opts.owner;
|
|
2226
|
+
this._disposeFn = opts.disposeFn ?? null;
|
|
2227
|
+
opts.registry?.add(this);
|
|
2228
|
+
}
|
|
2229
|
+
/** 释放(框架在页面销毁时自动调用——业务零心智负担) */
|
|
2230
|
+
dispose() {
|
|
2231
|
+
if (this.disposed) return false;
|
|
2232
|
+
this.disposed = true;
|
|
2233
|
+
try {
|
|
2234
|
+
this._disposeFn?.();
|
|
2235
|
+
} catch {
|
|
2236
|
+
}
|
|
2237
|
+
return true;
|
|
2238
|
+
}
|
|
2239
|
+
};
|
|
2240
|
+
var ManagedRegistry = class {
|
|
2241
|
+
constructor() {
|
|
2242
|
+
this._items = /* @__PURE__ */ new Set();
|
|
2243
|
+
}
|
|
2244
|
+
add(m) {
|
|
2245
|
+
this._items.add(m);
|
|
2246
|
+
}
|
|
2247
|
+
/** 全部释放(返回释放数) */
|
|
2248
|
+
disposeAll() {
|
|
2249
|
+
let n = 0;
|
|
2250
|
+
for (const m of this._items) {
|
|
2251
|
+
if (m.dispose()) n++;
|
|
2252
|
+
}
|
|
2253
|
+
this._items.clear();
|
|
2254
|
+
return n;
|
|
2255
|
+
}
|
|
2256
|
+
get size() {
|
|
2257
|
+
return this._items.size;
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
var singletonGraph = null;
|
|
2261
|
+
function getProteusOwnershipGraph() {
|
|
2262
|
+
if (!singletonGraph) singletonGraph = new OwnershipGraph();
|
|
2263
|
+
return singletonGraph;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// src/page-ownership.ts
|
|
2267
|
+
function createPageOwnership(owner, opts) {
|
|
2268
|
+
const { graph } = opts;
|
|
2269
|
+
const owned = /* @__PURE__ */ new Set();
|
|
2270
|
+
const managed = new ManagedRegistry();
|
|
2271
|
+
const quota = createQuotaTracker();
|
|
2272
|
+
function requestQuota(byteSize) {
|
|
2273
|
+
const used = quota.usageOf(owner);
|
|
2274
|
+
if (opts.quotaBytes !== void 0 && used + byteSize > opts.quotaBytes) {
|
|
2275
|
+
throw new Error(`G-43 \u914D\u989D\u4E0D\u8DB3\uFF1A${owner} \u5DF2\u7528 ${used}B / \u9650\u989D ${opts.quotaBytes}B\uFF08\u7533\u8BF7 ${byteSize}B\uFF09`);
|
|
2276
|
+
}
|
|
2277
|
+
quota.request(owner, byteSize);
|
|
2278
|
+
}
|
|
2279
|
+
return {
|
|
2280
|
+
owner,
|
|
2281
|
+
alloc(allocOpts) {
|
|
2282
|
+
const type = allocOpts.type ?? "shared-buffer";
|
|
2283
|
+
const value = allocOpts.value ?? new ArrayBuffer(allocOpts.byteSize);
|
|
2284
|
+
requestQuota(allocOpts.byteSize);
|
|
2285
|
+
const res = new Owned({
|
|
2286
|
+
id: graph.nextId(),
|
|
2287
|
+
resourceType: type,
|
|
2288
|
+
byteSize: allocOpts.byteSize,
|
|
2289
|
+
owner,
|
|
2290
|
+
value,
|
|
2291
|
+
graph,
|
|
2292
|
+
transferable: allocOpts.transferable ?? false,
|
|
2293
|
+
releaseHook: allocOpts.releaseHook,
|
|
2294
|
+
sourceLocation: allocOpts.sourceLocation ?? null
|
|
2295
|
+
});
|
|
2296
|
+
owned.add(res);
|
|
2297
|
+
return res;
|
|
2298
|
+
},
|
|
2299
|
+
register(res) {
|
|
2300
|
+
requestQuota(res.byteSize);
|
|
2301
|
+
owned.add(res);
|
|
2302
|
+
},
|
|
2303
|
+
managed,
|
|
2304
|
+
resourcesOf() {
|
|
2305
|
+
return graph.resourcesOf(owner);
|
|
2306
|
+
},
|
|
2307
|
+
destroy(destroyOpts = {}) {
|
|
2308
|
+
const force = destroyOpts.force ?? true;
|
|
2309
|
+
let freedCount = 0;
|
|
2310
|
+
let freedBytes = 0;
|
|
2311
|
+
let invalidatedBorrows = 0;
|
|
2312
|
+
const leaked = [];
|
|
2313
|
+
for (const res of [...owned]) {
|
|
2314
|
+
const r = res.drop({ force });
|
|
2315
|
+
if (r.ok) {
|
|
2316
|
+
freedCount++;
|
|
2317
|
+
freedBytes += r.freedBytes;
|
|
2318
|
+
invalidatedBorrows += r.invalidatedBorrows;
|
|
2319
|
+
quota.release(owner, r.freedBytes);
|
|
2320
|
+
owned.delete(res);
|
|
2321
|
+
} else if (r.error && (r.error.code === "already_dropped" || r.error.code === "use_after_move")) {
|
|
2322
|
+
owned.delete(res);
|
|
2323
|
+
} else if (r.error && r.error.code === "has_active_borrows") {
|
|
2324
|
+
leaked.push({ resourceId: r.error.resourceId, reason: r.error.code });
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
const managedDisposed = managed.disposeAll();
|
|
2328
|
+
const remaining = quota.usageOf(owner);
|
|
2329
|
+
if (leaked.length === 0 && remaining > 0) quota.release(owner, remaining);
|
|
2330
|
+
return { freedCount, freedBytes, invalidatedBorrows, managedDisposed, quotaRemaining: quota.usageOf(owner), leaked };
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/stack-container.ts
|
|
2336
|
+
function createResourcePool(ownerLabel) {
|
|
2337
|
+
const timers = /* @__PURE__ */ new Set();
|
|
2338
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2339
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
2340
|
+
const requests = /* @__PURE__ */ new Set();
|
|
2341
|
+
let seq = 0;
|
|
2342
|
+
const nextId = (kind) => `${ownerLabel}-${kind}-${++seq}`;
|
|
2343
|
+
return {
|
|
2344
|
+
timer(fn, ms, opts) {
|
|
2345
|
+
const h = {
|
|
2346
|
+
id: nextId("timer"),
|
|
2347
|
+
label: opts?.once ? "once-timer" : "timer",
|
|
2348
|
+
cleared: false,
|
|
2349
|
+
cancel() {
|
|
2350
|
+
h.cleared = true;
|
|
2351
|
+
timers.delete(h);
|
|
2352
|
+
}
|
|
2353
|
+
};
|
|
2354
|
+
timers.add(h);
|
|
2355
|
+
return h;
|
|
2356
|
+
},
|
|
2357
|
+
interval(fn, ms) {
|
|
2358
|
+
const h = { id: nextId("interval"), label: "interval", cleared: false, cancel() {
|
|
2359
|
+
h.cleared = true;
|
|
2360
|
+
timers.delete(h);
|
|
2361
|
+
} };
|
|
2362
|
+
timers.add(h);
|
|
2363
|
+
return h;
|
|
2364
|
+
},
|
|
2365
|
+
on(target, type, fn) {
|
|
2366
|
+
const b = { id: nextId("listener"), released: false };
|
|
2367
|
+
listeners.add(b);
|
|
2368
|
+
return () => {
|
|
2369
|
+
b.released = true;
|
|
2370
|
+
listeners.delete(b);
|
|
2371
|
+
};
|
|
2372
|
+
},
|
|
2373
|
+
bus(topic, fn) {
|
|
2374
|
+
const s = { id: nextId("bus"), released: false };
|
|
2375
|
+
subscriptions.add(s);
|
|
2376
|
+
return () => {
|
|
2377
|
+
s.released = true;
|
|
2378
|
+
subscriptions.delete(s);
|
|
2379
|
+
};
|
|
2380
|
+
},
|
|
2381
|
+
fetch() {
|
|
2382
|
+
const r = { id: nextId("req"), released: false };
|
|
2383
|
+
requests.add(r);
|
|
2384
|
+
return Object.assign(new Promise(() => {
|
|
2385
|
+
}), { cancel: () => {
|
|
2386
|
+
r.released = true;
|
|
2387
|
+
requests.delete(r);
|
|
2388
|
+
} });
|
|
2389
|
+
},
|
|
2390
|
+
subscribe() {
|
|
2391
|
+
const s = { id: nextId("subscribe"), released: false };
|
|
2392
|
+
subscriptions.add(s);
|
|
2393
|
+
return () => {
|
|
2394
|
+
s.released = true;
|
|
2395
|
+
subscriptions.delete(s);
|
|
2396
|
+
};
|
|
2397
|
+
},
|
|
2398
|
+
releaseAll() {
|
|
2399
|
+
const report = { timersCleared: timers.size, listenersUnbound: listeners.size, requestsAborted: requests.size };
|
|
2400
|
+
timers.forEach((t) => t.cleared = true);
|
|
2401
|
+
listeners.forEach((l) => l.released = true);
|
|
2402
|
+
requests.forEach((r) => r.released = true);
|
|
2403
|
+
timers.clear();
|
|
2404
|
+
listeners.clear();
|
|
2405
|
+
subscriptions.clear();
|
|
2406
|
+
requests.clear();
|
|
2407
|
+
return report;
|
|
2408
|
+
},
|
|
2409
|
+
get total() {
|
|
2410
|
+
return timers.size + listeners.size + subscriptions.size + requests.size;
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function createQuotaManager(limitBytes) {
|
|
2415
|
+
let usedBytes = 0;
|
|
2416
|
+
let pageCount = 0;
|
|
2417
|
+
let sandboxCount = 0;
|
|
2418
|
+
function pressure() {
|
|
2419
|
+
if (usedBytes >= limitBytes) return "critical";
|
|
2420
|
+
if (usedBytes >= limitBytes * 0.8) return "warning";
|
|
2421
|
+
return "normal";
|
|
2422
|
+
}
|
|
2423
|
+
return {
|
|
2424
|
+
attach(scope, bytes) {
|
|
2425
|
+
if (usedBytes + bytes > limitBytes) return null;
|
|
2426
|
+
usedBytes += bytes;
|
|
2427
|
+
pageCount++;
|
|
2428
|
+
return {
|
|
2429
|
+
bytes,
|
|
2430
|
+
scope,
|
|
2431
|
+
release: () => {
|
|
2432
|
+
usedBytes = Math.max(0, usedBytes - bytes);
|
|
2433
|
+
pageCount = Math.max(0, pageCount - 1);
|
|
2434
|
+
}
|
|
2435
|
+
};
|
|
2436
|
+
},
|
|
2437
|
+
request(bytes) {
|
|
2438
|
+
if (usedBytes + bytes > limitBytes) return null;
|
|
2439
|
+
usedBytes += bytes;
|
|
2440
|
+
pageCount++;
|
|
2441
|
+
return { bytes, scope: "page", release: () => {
|
|
2442
|
+
usedBytes = Math.max(0, usedBytes - bytes);
|
|
2443
|
+
pageCount = Math.max(0, pageCount - 1);
|
|
2444
|
+
} };
|
|
2445
|
+
},
|
|
2446
|
+
release(handle) {
|
|
2447
|
+
handle.release();
|
|
2448
|
+
},
|
|
2449
|
+
get usage() {
|
|
2450
|
+
return { usedBytes, limitBytes, pageCount, sandboxCount };
|
|
2451
|
+
},
|
|
2452
|
+
get pressure() {
|
|
2453
|
+
return pressure();
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
function setPageState(page, to) {
|
|
2458
|
+
if (!canTransitionPageState(page.state, to)) {
|
|
2459
|
+
throw new Error(`G-42 \u72B6\u6001\u673A\u62D2\u7EDD\uFF1A${page.state} \u2192 ${to}\uFF08page ${page.pageId}\uFF09`);
|
|
2460
|
+
}
|
|
2461
|
+
;
|
|
2462
|
+
page.state = to;
|
|
2463
|
+
}
|
|
2464
|
+
function createStackContainer(opts = {}) {
|
|
2465
|
+
const policy = opts.policy ?? DEFAULT_STACK_POLICY;
|
|
2466
|
+
const quota = createQuotaManager(opts.quotaLimitBytes ?? 512 * 1024 * 1024);
|
|
2467
|
+
const pages = /* @__PURE__ */ new Map();
|
|
2468
|
+
const stack = [];
|
|
2469
|
+
const events = {};
|
|
2470
|
+
const pressureHandlers = [];
|
|
2471
|
+
let seq = 0;
|
|
2472
|
+
const emit = (event, payload) => {
|
|
2473
|
+
for (const fn of events[event] ?? []) fn(payload);
|
|
2474
|
+
};
|
|
2475
|
+
function createPage(config) {
|
|
2476
|
+
const pageId = config.pageId ?? `page-${++seq}`;
|
|
2477
|
+
const page = {
|
|
2478
|
+
pageId,
|
|
2479
|
+
irId: config.irId,
|
|
2480
|
+
state: "created",
|
|
2481
|
+
mountPoint: null,
|
|
2482
|
+
resourcePool: createResourcePool(pageId),
|
|
2483
|
+
eventRegistry: {
|
|
2484
|
+
bind() {
|
|
2485
|
+
return () => {
|
|
2486
|
+
};
|
|
2487
|
+
},
|
|
2488
|
+
unbindAll() {
|
|
2489
|
+
},
|
|
2490
|
+
size: 0
|
|
2491
|
+
},
|
|
2492
|
+
ir: opts.createIR ? opts.createIR(config) : { irId: config.irId },
|
|
2493
|
+
budgetBytes: config.budgetBytes ?? 0,
|
|
2494
|
+
quotaHandle: null,
|
|
2495
|
+
alive: true,
|
|
2496
|
+
// keepAlive 标记(keep-alive 配额仅统计此类页面)
|
|
2497
|
+
keepAlive: config.keepAlive ?? false,
|
|
2498
|
+
// ★G-43 B3:ownership 选项启用 → 页面 = 所有权 scope(页面销毁时该页资源 forceDrop 回收)
|
|
2499
|
+
ownership: opts.ownership ? createPageOwnership(pageId, { graph: opts.ownership.graph, quotaBytes: opts.ownership.quotaBytes }) : null
|
|
2500
|
+
};
|
|
2501
|
+
pages.set(pageId, page);
|
|
2502
|
+
emit("page-created", { pageId });
|
|
2503
|
+
return page;
|
|
2504
|
+
}
|
|
2505
|
+
async function mountPage(handle) {
|
|
2506
|
+
const page = pageRecordOf(handle);
|
|
2507
|
+
const keepAlivePages = [...pages.values()].filter((p) => p.state !== "destroyed" && p.state !== "created" && p.keepAlive);
|
|
2508
|
+
if (keepAlivePages.length > policy.keepAlive.maxCount) {
|
|
2509
|
+
const oldest = stack[0];
|
|
2510
|
+
if (oldest) await destroyPage(oldest);
|
|
2511
|
+
}
|
|
2512
|
+
setPageState(page, "mounted");
|
|
2513
|
+
page.mountPoint = {};
|
|
2514
|
+
page.quotaHandle = page.budgetBytes > 0 ? quota.attach(page.pageId, page.budgetBytes) : null;
|
|
2515
|
+
}
|
|
2516
|
+
async function unmountPage(handle) {
|
|
2517
|
+
const page = pageRecordOf(handle);
|
|
2518
|
+
setPageState(page, "hidden");
|
|
2519
|
+
page.mountPoint = null;
|
|
2520
|
+
}
|
|
2521
|
+
async function destroyPage(handle) {
|
|
2522
|
+
const page = pageRecordOf(handle);
|
|
2523
|
+
const steps = [];
|
|
2524
|
+
page.mountPoint = null;
|
|
2525
|
+
steps.push(FIVE_ATOMIC_STEPS[0]);
|
|
2526
|
+
page.eventRegistry.unbindAll();
|
|
2527
|
+
steps.push(FIVE_ATOMIC_STEPS[1]);
|
|
2528
|
+
page.resourcePool.releaseAll();
|
|
2529
|
+
let reclaimedBytes = 0;
|
|
2530
|
+
const ownershipReport = page.ownership ? page.ownership.destroy({ force: true }) : null;
|
|
2531
|
+
if (ownershipReport) {
|
|
2532
|
+
reclaimedBytes += ownershipReport.freedBytes;
|
|
2533
|
+
if (ownershipReport.quotaRemaining !== 0) {
|
|
2534
|
+
throw new Error(`G-43 \u9875\u9762\u9500\u6BC1\u540E\u914D\u989D\u672A\u5F52\u96F6\uFF1A${page.pageId} \u5269\u4F59 ${ownershipReport.quotaRemaining}B`);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
steps.push(FIVE_ATOMIC_STEPS[2]);
|
|
2538
|
+
page.ir = null;
|
|
2539
|
+
steps.push(FIVE_ATOMIC_STEPS[3]);
|
|
2540
|
+
page.quotaHandle?.release();
|
|
2541
|
+
page.quotaHandle = null;
|
|
2542
|
+
steps.push(FIVE_ATOMIC_STEPS[4]);
|
|
2543
|
+
const report = { pageId: page.pageId, steps, leaked: [], reclaimedBytes, durationMs: 0 };
|
|
2544
|
+
assertAtomicDestroy(report);
|
|
2545
|
+
page.alive = false;
|
|
2546
|
+
page.state = "destroyed";
|
|
2547
|
+
pages.delete(page.pageId);
|
|
2548
|
+
const stackIdx = stack.indexOf(page);
|
|
2549
|
+
if (stackIdx >= 0) stack.splice(stackIdx, 1);
|
|
2550
|
+
emit("page-destroyed", { pageId: page.pageId, reclaimedBytes: report.reclaimedBytes });
|
|
2551
|
+
return report;
|
|
2552
|
+
}
|
|
2553
|
+
function pageRecordOf(handle) {
|
|
2554
|
+
const page = pages.get(handle.pageId);
|
|
2555
|
+
if (!page) throw new Error(`G-42: \u672A\u77E5\u9875\u9762 ${handle.pageId}`);
|
|
2556
|
+
return page;
|
|
2557
|
+
}
|
|
2558
|
+
const container = {
|
|
2559
|
+
id: "stack",
|
|
2560
|
+
version: "0.1.0",
|
|
2561
|
+
capabilities: CONTAINER_PROFILES.stack,
|
|
2562
|
+
async initialize() {
|
|
2563
|
+
},
|
|
2564
|
+
dispose() {
|
|
2565
|
+
pages.clear();
|
|
2566
|
+
stack.length = 0;
|
|
2567
|
+
},
|
|
2568
|
+
createPage(config) {
|
|
2569
|
+
return createPage(config);
|
|
2570
|
+
},
|
|
2571
|
+
mountPage,
|
|
2572
|
+
unmountPage,
|
|
2573
|
+
destroyPage,
|
|
2574
|
+
async push(config) {
|
|
2575
|
+
if (stack.length >= policy.maxDepth) {
|
|
2576
|
+
if (policy.overflowStrategy === "destroy-oldest") {
|
|
2577
|
+
const oldest = stack[0];
|
|
2578
|
+
if (oldest) {
|
|
2579
|
+
await destroyPage(oldest);
|
|
2580
|
+
emit("overflow", { strategy: "destroy-oldest", pageId: oldest.pageId });
|
|
2581
|
+
}
|
|
2582
|
+
} else if (policy.overflowStrategy === "reject") {
|
|
2583
|
+
throw new Error(`G-42 \u6808\u6EA2\u51FA\uFF1AmaxDepth=${policy.maxDepth}\uFF08reject \u7B56\u7565\uFF09`);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
const page = createPage(config);
|
|
2587
|
+
await mountPage(page);
|
|
2588
|
+
stack.push(page);
|
|
2589
|
+
return page;
|
|
2590
|
+
},
|
|
2591
|
+
async pop() {
|
|
2592
|
+
const page = stack.pop();
|
|
2593
|
+
if (!page) return null;
|
|
2594
|
+
await destroyPage(page);
|
|
2595
|
+
return page;
|
|
2596
|
+
},
|
|
2597
|
+
getCurrent() {
|
|
2598
|
+
return stack[stack.length - 1] ?? null;
|
|
2599
|
+
},
|
|
2600
|
+
getStackDepth() {
|
|
2601
|
+
return stack.length;
|
|
2602
|
+
},
|
|
2603
|
+
async createSandbox(bizId, manifest) {
|
|
2604
|
+
void bizId;
|
|
2605
|
+
void manifest;
|
|
2606
|
+
throw new Error("G-42: stack \u5BB9\u5668\u65E0\u6C99\u7BB1\uFF08capabilities.multiBusiness=false\uFF09\u2014\u2014SuperApp \u5BB9\u5668 B4 \u63D0\u4F9B");
|
|
2607
|
+
},
|
|
2608
|
+
async destroySandbox(bizId) {
|
|
2609
|
+
return createDestroyReport(bizId);
|
|
2610
|
+
},
|
|
2611
|
+
listSandboxes() {
|
|
2612
|
+
return [];
|
|
2613
|
+
},
|
|
2614
|
+
quota,
|
|
2615
|
+
onMemoryPressure(cb) {
|
|
2616
|
+
pressureHandlers.push(cb);
|
|
2617
|
+
return () => {
|
|
2618
|
+
const i = pressureHandlers.indexOf(cb);
|
|
2619
|
+
if (i >= 0) pressureHandlers.splice(i, 1);
|
|
2620
|
+
};
|
|
2621
|
+
},
|
|
2622
|
+
on(event, handler) {
|
|
2623
|
+
events[event] = events[event] ?? [];
|
|
2624
|
+
events[event].push(handler);
|
|
2625
|
+
},
|
|
2626
|
+
get stackView() {
|
|
2627
|
+
return [...stack];
|
|
2628
|
+
},
|
|
2629
|
+
get events() {
|
|
2630
|
+
return events;
|
|
2631
|
+
},
|
|
2632
|
+
ownershipOf(pageId) {
|
|
2633
|
+
const page = pages.get(pageId);
|
|
2634
|
+
return page ? page.ownership : null;
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
return container;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// src/container-conformance.ts
|
|
2641
|
+
var FORK_SIGNATURES = [
|
|
2642
|
+
/packages\/core\/src\//,
|
|
2643
|
+
// 框架源码副本
|
|
2644
|
+
/proteus-core\/internal\//,
|
|
2645
|
+
// 内部模块
|
|
2646
|
+
/from\s+['"]@proteus\/core\/internal/,
|
|
2647
|
+
// 直接 import 内部模块
|
|
2648
|
+
/__PROTEUS_FORKED__/
|
|
2649
|
+
// fork 标记
|
|
2650
|
+
];
|
|
2651
|
+
function scanRepoForFork(fileContents) {
|
|
2652
|
+
const hits = [];
|
|
2653
|
+
for (const [filename, content] of Object.entries(fileContents)) {
|
|
2654
|
+
for (const sig of FORK_SIGNATURES) {
|
|
2655
|
+
if (sig.test(content)) hits.push({ filename, pattern: String(sig) });
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return hits;
|
|
2659
|
+
}
|
|
2660
|
+
function checkBizManifest(input, opts) {
|
|
2661
|
+
if (opts.requireSignature && !input.signature) {
|
|
2662
|
+
return { ok: false, code: "G39_SIGN", message: `business ${input.bizId} missing signature` };
|
|
2663
|
+
}
|
|
2664
|
+
const illegal = (input.capabilities ?? []).filter((c) => !opts.whitelist.includes(c));
|
|
2665
|
+
if (illegal.length > 0) {
|
|
2666
|
+
return { ok: false, code: "G39_CAP", message: `business ${input.bizId} illegal capabilities: ${illegal.join(",")}` };
|
|
2667
|
+
}
|
|
2668
|
+
return { ok: true };
|
|
2669
|
+
}
|
|
2670
|
+
var SENSITIVE_CAPABILITIES = ["location", "camera", "contacts", "phone-call", "biometric", "payment", "storage", "clipboard"];
|
|
2671
|
+
function checkCapabilityAuthorization(capabilities, opts) {
|
|
2672
|
+
const sensitive = opts.sensitive ?? SENSITIVE_CAPABILITIES;
|
|
2673
|
+
const denied = capabilities.filter((c) => sensitive.includes(c) && !opts.granted.includes(c));
|
|
2674
|
+
if (denied.length > 0) return { ok: false, code: "G39_AUTH", denied };
|
|
2675
|
+
return { ok: true };
|
|
2676
|
+
}
|
|
2677
|
+
var tests2 = [];
|
|
2678
|
+
var register2 = (group) => (id, fn) => tests2.push({ id: `${group}-${id}`, group, fn });
|
|
2679
|
+
var C01 = register2("C-01");
|
|
2680
|
+
var C02 = register2("C-02");
|
|
2681
|
+
var C03 = register2("C-03");
|
|
2682
|
+
var C04 = register2("C-04");
|
|
2683
|
+
var C05 = register2("C-05");
|
|
2684
|
+
var C06 = register2("C-06");
|
|
2685
|
+
var C07 = register2("C-07");
|
|
2686
|
+
var C08 = register2("C-08");
|
|
2687
|
+
var assert2 = (c, m) => {
|
|
2688
|
+
if (!c) throw new Error(m ?? "assertion failed");
|
|
2689
|
+
};
|
|
2690
|
+
C01("01", ({ container }) => assert2(typeof container.id === "string" && container.id.length > 0, "\u5BB9\u5668\u5E94\u58F0\u660E id"));
|
|
2691
|
+
C01("02", ({ container }) => assert2(typeof container.version === "string" && container.version.length > 0, "\u5BB9\u5668\u5E94\u58F0\u660E version"));
|
|
2692
|
+
C01("03", ({ container }) => assert2(container.capabilities && typeof container.capabilities === "object", "capabilities \u5E94\u5B58\u5728"));
|
|
2693
|
+
C01("04", ({ container }) => {
|
|
2694
|
+
const cap2 = container.capabilities;
|
|
2695
|
+
assert2(typeof cap2.pageStack === "boolean", "pageStack \u5E94\u58F0\u660E\u4E3A boolean");
|
|
2696
|
+
});
|
|
2697
|
+
C02("01", async ({ container }) => {
|
|
2698
|
+
const page = await container.push({ irId: "ir-c02-1" });
|
|
2699
|
+
assert2(page && page.pageId, "push \u5E94\u521B\u5EFA\u9875\u9762\u5E76\u8FD4\u56DE\u53E5\u67C4");
|
|
2700
|
+
});
|
|
2701
|
+
C02("02", async ({ container }) => {
|
|
2702
|
+
const page = await container.push({ irId: "ir-c02-2" });
|
|
2703
|
+
assert2(page.state === "mounted", "\u9875\u9762\u5E94 mounted");
|
|
2704
|
+
});
|
|
2705
|
+
C02("03", async ({ container }) => {
|
|
2706
|
+
const page = await container.push({ irId: "ir-c02-3" });
|
|
2707
|
+
assert2(page.irId === "ir-c02-3", "IR \u5B9E\u4F8B\u5DF2\u521B\u5EFA\u4E14\u5B58\u6D3B\uFF08\u552F\u4E00\u771F\u76F8 G-42.1\uFF09");
|
|
2708
|
+
});
|
|
2709
|
+
C02("04", async ({ container }) => {
|
|
2710
|
+
const page = await container.push({ irId: "ir-c02-4" });
|
|
2711
|
+
await container.unmountPage(page);
|
|
2712
|
+
assert2(page.state === "hidden", "unmount \u2192 hidden");
|
|
2713
|
+
});
|
|
2714
|
+
C02("05", async ({ container }) => {
|
|
2715
|
+
const page = await container.push({ irId: "ir-c02-5" });
|
|
2716
|
+
await container.unmountPage(page);
|
|
2717
|
+
await container.mountPage(page);
|
|
2718
|
+
assert2(page.state === "mounted", "\u91CD\u65B0 mount \u2192 mounted\uFF08show \u8BED\u4E49\uFF09");
|
|
2719
|
+
});
|
|
2720
|
+
async function fiveAtomicFlow(container, irId = "ir-c03") {
|
|
2721
|
+
const page = await container.push({ irId });
|
|
2722
|
+
return container.destroyPage(page);
|
|
2723
|
+
}
|
|
2724
|
+
C03("01", async ({ container }) => {
|
|
2725
|
+
const report = await fiveAtomicFlow(container);
|
|
2726
|
+
assert2(report.steps.length === 5, `\u9500\u6BC1\u5E94\u6070\u597D 5 \u6B65\uFF08\u5B9E\u9645 ${report.steps.length}\uFF09`);
|
|
2727
|
+
});
|
|
2728
|
+
C03("02", async ({ container }) => {
|
|
2729
|
+
const report = await fiveAtomicFlow(container);
|
|
2730
|
+
assert2(JSON.stringify(report.steps) === JSON.stringify(["unmount", "unbindEvents", "releaseResources", "destroyIR", "releaseQuota"]), "\u6B65\u5E8F\u5E94\u4E3A unmount\u2192unbindEvents\u2192releaseResources\u2192destroyIR\u2192releaseQuota");
|
|
2731
|
+
});
|
|
2732
|
+
C03("03", async ({ container }) => {
|
|
2733
|
+
const page = await container.push({ irId: "ir-c03-3" });
|
|
2734
|
+
await container.destroyPage(page);
|
|
2735
|
+
assert2(page.mountPoint == null, "\u6302\u8F7D\u70B9\u5E94\u7F6E\u7A7A\uFF08\u2460 \u2461\u5DF2\u5378\u8F7D\uFF09");
|
|
2736
|
+
});
|
|
2737
|
+
C03("04", async ({ container }) => {
|
|
2738
|
+
const page = await container.push({ irId: "ir-c03-4" });
|
|
2739
|
+
const pool = page.resourcePool;
|
|
2740
|
+
pool.total;
|
|
2741
|
+
await container.destroyPage(page);
|
|
2742
|
+
assert2(pool.total === 0, "\u8D44\u6E90\u6C60\u603B\u91CF\u5E94\u5F52\u96F6\uFF08\u2462 releaseResources\uFF09");
|
|
2743
|
+
});
|
|
2744
|
+
C03("05", async ({ container }) => {
|
|
2745
|
+
const page = await container.push({ irId: "ir-c03-5" });
|
|
2746
|
+
const ir = page.ir;
|
|
2747
|
+
await container.destroyPage(page);
|
|
2748
|
+
assert2(ir === null || !page.alive, "IR \u5B9E\u4F8B\u5E94\u9500\u6BC1\uFF08\u2463 destroyIR\u2014\u2014\u5BB9\u5668\u4E0D\u89E3\u6790\u5185\u5BB9 G-42.4\uFF09");
|
|
2749
|
+
});
|
|
2750
|
+
C03("06", async ({ container }) => {
|
|
2751
|
+
const report = await fiveAtomicFlow(container, "ir-c03-6");
|
|
2752
|
+
assert2(report.reclaimedBytes >= 0, "\u914D\u989D\u53E5\u67C4\u5F52\u8FD8\uFF08\u2464 releaseQuota\uFF09");
|
|
2753
|
+
});
|
|
2754
|
+
C04("01", async ({ container }) => {
|
|
2755
|
+
const base = container.getStackDepth();
|
|
2756
|
+
await container.push({ irId: "ir-c04-1a" });
|
|
2757
|
+
await container.push({ irId: "ir-c04-1b" });
|
|
2758
|
+
assert2(container.getStackDepth() === base + 2, "push \u540E\u6808\u6DF1\u5EA6\u5E94 +2");
|
|
2759
|
+
});
|
|
2760
|
+
C04("02", async ({ container }) => {
|
|
2761
|
+
let caught = false;
|
|
2762
|
+
try {
|
|
2763
|
+
for (let i = 0; i < 12; i++) await container.push({ irId: `ir-c04-2-${i}` });
|
|
2764
|
+
assert2(container.getStackDepth() >= 1, "\u6CBB\u7406\u540E\u81F3\u5C11\u4FDD\u7559\u9875\u9762");
|
|
2765
|
+
} catch (e) {
|
|
2766
|
+
caught = true;
|
|
2767
|
+
}
|
|
2768
|
+
assert2(caught || container.getStackDepth() <= 12, "\u8D85\u9650\u540E\u6DF1\u5EA6\u53D7\u63A7\uFF08destroy-oldest \u6216 reject\uFF09");
|
|
2769
|
+
});
|
|
2770
|
+
C04("03", async ({ container }) => {
|
|
2771
|
+
await container.push({ irId: "ir-c04-3-a" });
|
|
2772
|
+
await container.push({ irId: "ir-c04-3-b" });
|
|
2773
|
+
assert2(container.getCurrent()?.irId === "ir-c04-3-b", "\u5F53\u524D\u9875\u5E94\u4E3A\u65B0\u9875");
|
|
2774
|
+
});
|
|
2775
|
+
C04("04", async ({ container }) => {
|
|
2776
|
+
await container.push({ irId: "ir-c04-4" });
|
|
2777
|
+
const popped = await container.pop();
|
|
2778
|
+
assert2(popped !== null, "pop \u5E94\u8FD4\u56DE\u9875\u9762");
|
|
2779
|
+
assert2(popped.state === "destroyed", "pop \u540E\u9875\u9762\u5E94\u5DF2\u9500\u6BC1");
|
|
2780
|
+
});
|
|
2781
|
+
C05("01", async ({ container }) => {
|
|
2782
|
+
const page = await container.push({ irId: "ir-c05-1" });
|
|
2783
|
+
const pool = page.resourcePool;
|
|
2784
|
+
pool.timer(() => {
|
|
2785
|
+
}, 100);
|
|
2786
|
+
pool.timer(() => {
|
|
2787
|
+
}, 200);
|
|
2788
|
+
pool.timer(() => {
|
|
2789
|
+
}, 300);
|
|
2790
|
+
assert2(pool.total === 3, "\u9500\u6BC1\u524D\u8D44\u6E90\u5DF2\u767B\u8BB0");
|
|
2791
|
+
});
|
|
2792
|
+
C05("02", async ({ container }) => {
|
|
2793
|
+
const page = await container.push({ irId: "ir-c05-2" });
|
|
2794
|
+
const ir = page.ir;
|
|
2795
|
+
assert2(ir !== null, "\u9500\u6BC1\u524D IR \u5B58\u6D3B");
|
|
2796
|
+
await container.destroyPage(page);
|
|
2797
|
+
assert2(ir === null || !page.alive, "\u9500\u6BC1\u540E IR \u5B9E\u4F8B\u9500\u6BC1");
|
|
2798
|
+
});
|
|
2799
|
+
C05("03", async ({ container }) => {
|
|
2800
|
+
const page = await container.push({ irId: "ir-c05-3" });
|
|
2801
|
+
const pool = page.resourcePool;
|
|
2802
|
+
pool.interval(() => {
|
|
2803
|
+
}, 500);
|
|
2804
|
+
await container.destroyPage(page);
|
|
2805
|
+
assert2(pool.total === 0, "\u9500\u6BC1\u540E\u5B9A\u65F6\u5668\u6C60\u6E05\u96F6");
|
|
2806
|
+
});
|
|
2807
|
+
C05("04", async ({ container }) => {
|
|
2808
|
+
const page = await container.push({ irId: "ir-c05-4" });
|
|
2809
|
+
const pool = page.resourcePool;
|
|
2810
|
+
pool.on({ addEventListener() {
|
|
2811
|
+
}, removeEventListener() {
|
|
2812
|
+
} }, "tap", () => {
|
|
2813
|
+
});
|
|
2814
|
+
assert2(pool.total === 1, "\u76D1\u542C\u5DF2\u767B\u8BB0");
|
|
2815
|
+
await container.destroyPage(page);
|
|
2816
|
+
assert2(pool.total === 0, "\u9500\u6BC1\u540E\u76D1\u542C\u6E05\u96F6");
|
|
2817
|
+
});
|
|
2818
|
+
C05("05", async ({ container }) => {
|
|
2819
|
+
const page = await container.push({ irId: "ir-c05-5" });
|
|
2820
|
+
const pool = page.resourcePool;
|
|
2821
|
+
pool.timer(() => {
|
|
2822
|
+
}, 100);
|
|
2823
|
+
pool.on({ addEventListener() {
|
|
2824
|
+
}, removeEventListener() {
|
|
2825
|
+
} }, "tap", () => {
|
|
2826
|
+
});
|
|
2827
|
+
assert2(pool.total === 2, "\u9500\u6BC1\u524D total=2");
|
|
2828
|
+
await container.destroyPage(page);
|
|
2829
|
+
assert2(pool.total === 0, "\u9500\u6BC1\u540E\u8D44\u6E90\u6C60\u603B\u91CF = 0\uFF08C-05 \u6CC4\u6F0F\u68C0\u6D4B\u673A\u5668\u8BC1\u636E\uFF09");
|
|
2830
|
+
});
|
|
2831
|
+
C06("01", async ({ container }) => assert2(container.quota !== null && typeof container.quota.request === "function", "\u914D\u989D manager \u5E94\u5B58\u5728"));
|
|
2832
|
+
C06("02", async ({ container }) => {
|
|
2833
|
+
const h = container.quota.request(100);
|
|
2834
|
+
assert2(h !== null, "\u914D\u989D\u7533\u8BF7\u5E94\u6210\u529F");
|
|
2835
|
+
assert2(container.quota.usage.usedBytes >= 100, "\u7528\u91CF\u7EDF\u8BA1\u5E94\u6B63\u786E");
|
|
2836
|
+
});
|
|
2837
|
+
C06("03", async ({ container }) => {
|
|
2838
|
+
const limit = container.quota.usage.limitBytes;
|
|
2839
|
+
const big = container.quota.request(limit + 1);
|
|
2840
|
+
assert2(big === null, "\u8D85\u9650\u5E94\u8FD4\u56DE null\uFF08CMP061\uFF09");
|
|
2841
|
+
});
|
|
2842
|
+
C06("04", async ({ container }) => {
|
|
2843
|
+
const h = container.quota.request(50);
|
|
2844
|
+
if (h) container.quota.release(h);
|
|
2845
|
+
assert2(container.quota.usage.usedBytes >= 0, "\u5F52\u8FD8\u540E\u7528\u91CF\u8BB0\u5F55\u6B63\u786E");
|
|
2846
|
+
});
|
|
2847
|
+
C07("01", async ({ container }) => assert2(container.capabilities.multiBusiness === true, "\u6C99\u7BB1\u80FD\u529B\uFF1AmultiBusiness=true\uFF08Stack \u5BB9\u5668 SKIP\uFF09"));
|
|
2848
|
+
C07("02", async ({ container }) => assert2(container.capabilities.crashIsolation > 0, "\u5D29\u6E83\u9694\u79BB\u80FD\u529B\uFF1AcrashIsolation>0"));
|
|
2849
|
+
C07("03", async ({ container }) => assert2(typeof container.createSandbox === "function", "\u5E94\u53EF\u521B\u5EFA\u6C99\u7BB1"));
|
|
2850
|
+
C07("04", async ({ container }) => {
|
|
2851
|
+
const sb = await container.createSandbox("biz-a", { bizId: "biz-a", signature: "test-sig", requiredCapabilities: [] });
|
|
2852
|
+
assert2(sb !== null, "\u6C99\u7BB1 A \u5E94\u521B\u5EFA");
|
|
2853
|
+
});
|
|
2854
|
+
C07("05", async ({ container }) => {
|
|
2855
|
+
const sb = await container.createSandbox("biz-b", { bizId: "biz-b", signature: "test-sig", requiredCapabilities: [] });
|
|
2856
|
+
assert2(sb !== null, "\u6C99\u7BB1 B \u5E94\u521B\u5EFA");
|
|
2857
|
+
});
|
|
2858
|
+
C07("06", async ({ container }) => {
|
|
2859
|
+
const sbs = container.listSandboxes();
|
|
2860
|
+
assert2(Array.isArray(sbs), "\u6C99\u7BB1\u5217\u8868\u5E94\u53EF\u67E5\u8BE2\uFF08\u5D29\u6E83\u9694\u79BB\u771F\u9A8C\u8BC1\u7531 B4 SuperApp \u5BB9\u5668\u63D0\u4F9B\uFF09");
|
|
2861
|
+
});
|
|
2862
|
+
C08("01", async ({ container }) => {
|
|
2863
|
+
const r = checkBizManifest({ bizId: "evil" }, { requireSignature: false, whitelist: ["camera"] });
|
|
2864
|
+
assert2(r.ok === true, "\u65E0\u7B7E\u540D\u62D2\u7EDD\u7531\u58F0\u660E requireSignature \u7684\u5BB9\u5668\u9A8C\u8BC1");
|
|
2865
|
+
});
|
|
2866
|
+
C08("02", async ({ container }) => {
|
|
2867
|
+
const r = checkBizManifest({ bizId: "bad", signature: "s", capabilities: ["contacts"] }, { requireSignature: true, whitelist: ["camera"] });
|
|
2868
|
+
assert2(r.ok === false && r.code === "G39_CAP", "\u8D8A\u6743\u80FD\u529B\u5E94\u88AB\u62D2\u7EDD\uFF08G39_CAP\uFF09");
|
|
2869
|
+
});
|
|
2870
|
+
C08("03", ({}) => {
|
|
2871
|
+
const hits = scanRepoForFork({ "host/src/main.js": "import { createContainer } from '@proteus/container';" });
|
|
2872
|
+
assert2(hits.length === 0, "\u5408\u89C4\u4ED3\u5E93\u5E94\u65E0 fork \u547D\u4E2D");
|
|
2873
|
+
});
|
|
2874
|
+
C08("04", ({}) => {
|
|
2875
|
+
const hits = scanRepoForFork({ "host/vendor/core.js": "import x from '@proteus/core/internal/diff';" });
|
|
2876
|
+
assert2(hits.length > 0, "fork \u4ED3\u5E93\u5E94\u88AB\u68C0\u51FA");
|
|
2877
|
+
});
|
|
2878
|
+
async function runContainerConformance(container) {
|
|
2879
|
+
const ctx = { container };
|
|
2880
|
+
const results = [];
|
|
2881
|
+
for (const t of tests2) {
|
|
2882
|
+
const caps = container.capabilities;
|
|
2883
|
+
if (t.group === "C-07" && !caps.multiBusiness) {
|
|
2884
|
+
results.push({ id: t.id, status: "SKIP", reason: "multiBusiness=false\uFF08\u5BB9\u5668\u65E0\u6C99\u7BB1\u80FD\u529B\u2014\u2014\u8BDA\u5B9E\u58F0\u660E CMP065\uFF09" });
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
if (t.group === "C-04" && !caps.pageStack) {
|
|
2888
|
+
results.push({ id: t.id, status: "SKIP", reason: "pageStack=false\uFF08\u5BB9\u5668\u65E0\u9875\u9762\u6808\u6CBB\u7406\u2014\u2014singlepage/embedded \u5355\u69FD\u8BED\u4E49\uFF0CCMP065\uFF09" });
|
|
2889
|
+
continue;
|
|
2890
|
+
}
|
|
2891
|
+
if (t.group === "C-06" && !caps.resourceQuota) {
|
|
2892
|
+
results.push({ id: t.id, status: "SKIP", reason: "resourceQuota=false\uFF08\u5BB9\u5668\u65E0\u914D\u989D\u6CBB\u7406\u2014\u2014CMP065\uFF09" });
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
if (t.id === "C-08-01" || t.id === "C-08-02") {
|
|
2896
|
+
const gate = container._security;
|
|
2897
|
+
if (!gate || !gate.requireSignature) {
|
|
2898
|
+
if (t.id === "C-08-01") {
|
|
2899
|
+
const r = checkBizManifest({ bizId: "evil" }, { requireSignature: true, whitelist: ["camera"] });
|
|
2900
|
+
if (r.ok === false && r.code === "G39_SIGN") {
|
|
2901
|
+
results.push({ id: t.id, status: "PASS" });
|
|
2902
|
+
continue;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
results.push({ id: t.id, status: "SKIP", reason: "\u5BB9\u5668\u672A\u58F0\u660E\u5B89\u5168\u7F51\u5173\uFF08requireSignature=false\uFF09" });
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
try {
|
|
2910
|
+
const ret = await t.fn(ctx);
|
|
2911
|
+
if (ret === "SKIP") results.push({ id: t.id, status: "SKIP" });
|
|
2912
|
+
else results.push({ id: t.id, status: "PASS" });
|
|
2913
|
+
} catch (e) {
|
|
2914
|
+
results.push({ id: t.id, status: "FAIL", reason: e.message });
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
return {
|
|
2918
|
+
total: results.length,
|
|
2919
|
+
pass: results.filter((r) => r.status === "PASS").length,
|
|
2920
|
+
fail: results.filter((r) => r.status === "FAIL").length,
|
|
2921
|
+
skip: results.filter((r) => r.status === "SKIP").length,
|
|
2922
|
+
results
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
function formatContainerConformance(s) {
|
|
2926
|
+
const lines = ["[G-42 \u5BB9\u5668 Conformance\uFF08C-01~C-08\uFF09]"];
|
|
2927
|
+
for (const r of s.results) {
|
|
2928
|
+
const icon = r.status === "PASS" ? "\u2705" : r.status === "SKIP" ? "\u23ED\uFE0F " : "\u274C";
|
|
2929
|
+
lines.push(` ${icon} ${r.id}${r.status !== "PASS" ? ` \u2014 ${r.reason ?? ""}` : ""}`);
|
|
2930
|
+
}
|
|
2931
|
+
lines.push("\u2500".repeat(30));
|
|
2932
|
+
lines.push(`\u603B\u8BA1\uFF1APASS=${s.pass} FAIL=${s.fail} SKIP=${s.skip}\uFF08${s.total} \u9879 C-01~C-08\uFF09`);
|
|
2933
|
+
return lines.join("\n");
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/superapp-container.ts
|
|
2937
|
+
function createBusinessSandbox(bizId, manifest, opts) {
|
|
2938
|
+
const scope = { bizId, values: {} };
|
|
2939
|
+
const sb = {
|
|
2940
|
+
bizId,
|
|
2941
|
+
isolatedScope: scope,
|
|
2942
|
+
quotaHandle: null,
|
|
2943
|
+
state: "running",
|
|
2944
|
+
crashCount: 0,
|
|
2945
|
+
scope,
|
|
2946
|
+
run(fn) {
|
|
2947
|
+
try {
|
|
2948
|
+
const result = fn();
|
|
2949
|
+
return { ok: true, result };
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
return { ok: false, error };
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
};
|
|
2955
|
+
return sb;
|
|
2956
|
+
}
|
|
2957
|
+
function validateManifest(manifest, policy) {
|
|
2958
|
+
const r = checkBizManifest(
|
|
2959
|
+
{ bizId: manifest.bizId, signature: manifest.signature, capabilities: manifest.requiredCapabilities },
|
|
2960
|
+
{ requireSignature: policy.security.requireSignature, whitelist: policy.security.capabilityWhitelist }
|
|
2961
|
+
);
|
|
2962
|
+
if (!r.ok) {
|
|
2963
|
+
throw new Error(`${r.code}: ${r.message}`);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
var DEFAULT_SUPERAPP_POLICY = {
|
|
2967
|
+
maxDepth: 10,
|
|
2968
|
+
overflowStrategy: "destroy-oldest",
|
|
2969
|
+
keepAlive: { maxCount: 3, memoryBudgetBytes: 64 * 1024 * 1024 },
|
|
2970
|
+
sandbox: { defaultMemoryBytes: 128 * 1024 * 1024, maxSandboxes: 8 },
|
|
2971
|
+
crash: { isolationLevel: 2, autoRestart: true, maxRestartCount: 3 },
|
|
2972
|
+
security: { requireSignature: true, capabilityWhitelist: ["camera", "location", "storage"] }
|
|
2973
|
+
};
|
|
2974
|
+
function createSuperAppContainer(opts = {}) {
|
|
2975
|
+
const policy = {
|
|
2976
|
+
...DEFAULT_SUPERAPP_POLICY,
|
|
2977
|
+
...opts.policy,
|
|
2978
|
+
keepAlive: { ...DEFAULT_SUPERAPP_POLICY.keepAlive, ...opts.policy?.keepAlive ?? {} },
|
|
2979
|
+
sandbox: { ...DEFAULT_SUPERAPP_POLICY.sandbox, ...opts.policy?.sandbox ?? {} },
|
|
2980
|
+
crash: { ...DEFAULT_SUPERAPP_POLICY.crash, ...opts.policy?.crash ?? {} },
|
|
2981
|
+
security: { ...DEFAULT_SUPERAPP_POLICY.security, ...opts.policy?.security ?? {} }
|
|
2982
|
+
};
|
|
2983
|
+
const stack = createStackContainer({ policy, quotaLimitBytes: opts.quotaLimitBytes, ownership: opts.ownership });
|
|
2984
|
+
const sandboxes = /* @__PURE__ */ new Map();
|
|
2985
|
+
const crashLog = [];
|
|
2986
|
+
const events = {};
|
|
2987
|
+
const emit = (event, payload) => {
|
|
2988
|
+
for (const fn of events[event] ?? []) fn(payload);
|
|
2989
|
+
void event;
|
|
2990
|
+
};
|
|
2991
|
+
const container = {
|
|
2992
|
+
id: "superapp",
|
|
2993
|
+
version: "1.0.0",
|
|
2994
|
+
capabilities: {
|
|
2995
|
+
...CONTAINER_PROFILES.superapp,
|
|
2996
|
+
crashIsolation: policy.crash.isolationLevel
|
|
2997
|
+
},
|
|
2998
|
+
// —— 生命周期:委托 stack ——
|
|
2999
|
+
initialize: stack.initialize,
|
|
3000
|
+
dispose() {
|
|
3001
|
+
for (const bizId of [...sandboxes.keys()]) void container.destroySandbox(bizId);
|
|
3002
|
+
stack.dispose();
|
|
3003
|
+
},
|
|
3004
|
+
createPage: stack.createPage,
|
|
3005
|
+
mountPage: stack.mountPage,
|
|
3006
|
+
unmountPage: stack.unmountPage,
|
|
3007
|
+
destroyPage: stack.destroyPage,
|
|
3008
|
+
push: stack.push,
|
|
3009
|
+
pop: stack.pop,
|
|
3010
|
+
getCurrent: stack.getCurrent,
|
|
3011
|
+
getStackDepth: stack.getStackDepth,
|
|
3012
|
+
// ★G-43 B3:页面所有权上下文(委托内部 Stack 页面记录)
|
|
3013
|
+
ownershipOf: stack.ownershipOf,
|
|
3014
|
+
// —— 业务沙箱(SuperApp 核心) ——
|
|
3015
|
+
async createSandbox(bizId, manifest) {
|
|
3016
|
+
if (sandboxes.size >= policy.sandbox.maxSandboxes) {
|
|
3017
|
+
throw new Error(`G39_LIMIT: max sandboxes ${policy.sandbox.maxSandboxes}`);
|
|
3018
|
+
}
|
|
3019
|
+
validateManifest(manifest, policy);
|
|
3020
|
+
const sb = createBusinessSandbox(bizId, manifest, { memoryBytes: policy.sandbox.defaultMemoryBytes });
|
|
3021
|
+
sb.state = "running";
|
|
3022
|
+
sandboxes.set(bizId, sb);
|
|
3023
|
+
emit("sandbox-created", { bizId });
|
|
3024
|
+
return sb;
|
|
3025
|
+
},
|
|
3026
|
+
async destroySandbox(bizId) {
|
|
3027
|
+
const sb = sandboxes.get(bizId);
|
|
3028
|
+
if (!sb) throw new Error(`G39_NOTFOUND: sandbox ${bizId} not found`);
|
|
3029
|
+
sb.state = "destroyed";
|
|
3030
|
+
sandboxes.delete(bizId);
|
|
3031
|
+
emit("sandbox-destroyed", { bizId });
|
|
3032
|
+
return { pageId: bizId, steps: ["unmount", "unbindEvents", "releaseResources", "destroyIR", "releaseQuota"], leaked: [], reclaimedBytes: 0, durationMs: 0 };
|
|
3033
|
+
},
|
|
3034
|
+
listSandboxes() {
|
|
3035
|
+
return [...sandboxes.values()];
|
|
3036
|
+
},
|
|
3037
|
+
// —— 资源治理 ——
|
|
3038
|
+
quota: stack.quota,
|
|
3039
|
+
onMemoryPressure(cb) {
|
|
3040
|
+
events["pressure"] = events["pressure"] ?? [];
|
|
3041
|
+
events["pressure"].push(cb);
|
|
3042
|
+
return () => {
|
|
3043
|
+
events["pressure"] = (events["pressure"] ?? []).filter((f) => f !== cb);
|
|
3044
|
+
};
|
|
3045
|
+
},
|
|
3046
|
+
on(event, handler) {
|
|
3047
|
+
events[event] = events[event] ?? [];
|
|
3048
|
+
events[event].push(handler);
|
|
3049
|
+
},
|
|
3050
|
+
// —— 崩溃隔离核心 ——
|
|
3051
|
+
executeInSandbox(bizId, fn) {
|
|
3052
|
+
const sb = sandboxes.get(bizId);
|
|
3053
|
+
if (!sb) throw new Error(`G39_NOTFOUND: sandbox ${bizId} not found`);
|
|
3054
|
+
const result = sb.run(fn);
|
|
3055
|
+
if (!result.ok) {
|
|
3056
|
+
const error = String(result.error?.message ?? result.error);
|
|
3057
|
+
crashLog.push({ bizId, error, at: Date.now() });
|
|
3058
|
+
emit("sandbox-crashed", { bizId, error, hostAlive: true });
|
|
3059
|
+
if (policy.crash.autoRestart && sb.crashCount < policy.crash.maxRestartCount) {
|
|
3060
|
+
sb.crashCount++;
|
|
3061
|
+
sb.state = "running";
|
|
3062
|
+
} else {
|
|
3063
|
+
;
|
|
3064
|
+
sb.state = "crashed";
|
|
3065
|
+
}
|
|
3066
|
+
return {
|
|
3067
|
+
ok: false,
|
|
3068
|
+
bizId,
|
|
3069
|
+
hostAlive: true,
|
|
3070
|
+
otherSandboxesAlive: [...sandboxes.values()].filter((s) => s.bizId !== bizId && s.state === "running").length,
|
|
3071
|
+
error
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
return { ok: true, bizId, result: result.result };
|
|
3075
|
+
},
|
|
3076
|
+
get crashLog() {
|
|
3077
|
+
return crashLog;
|
|
3078
|
+
},
|
|
3079
|
+
get sandboxView() {
|
|
3080
|
+
return [...sandboxes.values()];
|
|
3081
|
+
}
|
|
3082
|
+
};
|
|
3083
|
+
return container;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/basic-containers.ts
|
|
3087
|
+
function setSlotState(page, to) {
|
|
3088
|
+
if (!canTransitionPageState(page.state, to)) {
|
|
3089
|
+
throw new Error(`G-42 \u72B6\u6001\u673A\u62D2\u7EDD\uFF1A${page.state} \u2192 ${to}\uFF08page ${page.pageId}\uFF09`);
|
|
3090
|
+
}
|
|
3091
|
+
;
|
|
3092
|
+
page.state = to;
|
|
3093
|
+
}
|
|
3094
|
+
function makeSlotPage(seq, config, ownership) {
|
|
3095
|
+
const pageId = config.pageId ?? `page-${++seq}`;
|
|
3096
|
+
return {
|
|
3097
|
+
pageId,
|
|
3098
|
+
irId: config.irId,
|
|
3099
|
+
state: "created",
|
|
3100
|
+
mountPoint: null,
|
|
3101
|
+
resourcePool: createResourcePool(pageId),
|
|
3102
|
+
eventRegistry: {
|
|
3103
|
+
bind() {
|
|
3104
|
+
return () => {
|
|
3105
|
+
};
|
|
3106
|
+
},
|
|
3107
|
+
unbindAll() {
|
|
3108
|
+
},
|
|
3109
|
+
size: 0
|
|
3110
|
+
},
|
|
3111
|
+
config,
|
|
3112
|
+
ir: { irId: config.irId },
|
|
3113
|
+
quotaHandle: null,
|
|
3114
|
+
alive: true,
|
|
3115
|
+
ownership
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
async function destroySlotPage(page, pages, emit) {
|
|
3119
|
+
const steps = [];
|
|
3120
|
+
page.mountPoint = null;
|
|
3121
|
+
steps.push(FIVE_ATOMIC_STEPS[0]);
|
|
3122
|
+
page.eventRegistry.unbindAll();
|
|
3123
|
+
steps.push(FIVE_ATOMIC_STEPS[1]);
|
|
3124
|
+
page.resourcePool.releaseAll();
|
|
3125
|
+
let reclaimedBytes = 0;
|
|
3126
|
+
const ownershipReport = page.ownership ? page.ownership.destroy({ force: true }) : null;
|
|
3127
|
+
if (ownershipReport) {
|
|
3128
|
+
reclaimedBytes += ownershipReport.freedBytes;
|
|
3129
|
+
if (ownershipReport.quotaRemaining !== 0) {
|
|
3130
|
+
throw new Error(`G-43 \u9875\u9762\u9500\u6BC1\u540E\u914D\u989D\u672A\u5F52\u96F6\uFF1A${page.pageId} \u5269\u4F59 ${ownershipReport.quotaRemaining}B`);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
steps.push(FIVE_ATOMIC_STEPS[2]);
|
|
3134
|
+
page.ir = null;
|
|
3135
|
+
steps.push(FIVE_ATOMIC_STEPS[3]);
|
|
3136
|
+
page.quotaHandle = null;
|
|
3137
|
+
steps.push(FIVE_ATOMIC_STEPS[4]);
|
|
3138
|
+
const report = { pageId: page.pageId, steps, leaked: [], reclaimedBytes, durationMs: 0 };
|
|
3139
|
+
assertAtomicDestroy(report);
|
|
3140
|
+
page.alive = false;
|
|
3141
|
+
page.state = "destroyed";
|
|
3142
|
+
pages.delete(page.pageId);
|
|
3143
|
+
emit("page-destroyed", { pageId: page.pageId, reclaimedBytes });
|
|
3144
|
+
return report;
|
|
3145
|
+
}
|
|
3146
|
+
function noQuota(id) {
|
|
3147
|
+
const refuse = () => {
|
|
3148
|
+
throw new Error(`G-42: ${id} \u5BB9\u5668\u65E0\u914D\u989D\u6CBB\u7406\uFF08capabilities.resourceQuota=false\u2014\u2014CMP065 \u8BDA\u5B9E\u58F0\u660E\uFF09`);
|
|
3149
|
+
};
|
|
3150
|
+
const usage = { usedBytes: 0, limitBytes: 0, pageCount: 0, sandboxCount: 0 };
|
|
3151
|
+
return {
|
|
3152
|
+
request: () => refuse(),
|
|
3153
|
+
release: () => refuse(),
|
|
3154
|
+
usage,
|
|
3155
|
+
pressure: "normal"
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
function createSinglePageContainer(opts = {}) {
|
|
3159
|
+
const pages = /* @__PURE__ */ new Map();
|
|
3160
|
+
let current = null;
|
|
3161
|
+
let seq = 0;
|
|
3162
|
+
const events = {};
|
|
3163
|
+
const emit = (event, payload) => {
|
|
3164
|
+
for (const fn of events[event] ?? []) fn(payload);
|
|
3165
|
+
};
|
|
3166
|
+
function createSlot(config) {
|
|
3167
|
+
const ownership = opts.ownership ? createPageOwnership(config.pageId ?? `page-${++seq}`, { graph: opts.ownership.graph, quotaBytes: opts.ownership.quotaBytes }) : null;
|
|
3168
|
+
const page = makeSlotPage(seq, config, ownership);
|
|
3169
|
+
if (!config.pageId) seq++;
|
|
3170
|
+
pages.set(page.pageId, page);
|
|
3171
|
+
emit("page-created", { pageId: page.pageId });
|
|
3172
|
+
return page;
|
|
3173
|
+
}
|
|
3174
|
+
function createPage(config) {
|
|
3175
|
+
return createSlot(config);
|
|
3176
|
+
}
|
|
3177
|
+
async function destroyPage(handle) {
|
|
3178
|
+
const page = pages.get(handle.pageId);
|
|
3179
|
+
if (!page) throw new Error(`G-42: \u672A\u77E5\u9875\u9762 ${handle.pageId}`);
|
|
3180
|
+
const report = await destroySlotPage(page, pages, emit);
|
|
3181
|
+
if (current === page) current = null;
|
|
3182
|
+
return report;
|
|
3183
|
+
}
|
|
3184
|
+
async function mountPage(handle) {
|
|
3185
|
+
const page = pages.get(handle.pageId);
|
|
3186
|
+
if (!page) throw new Error(`G-42: \u672A\u77E5\u9875\u9762 ${handle.pageId}`);
|
|
3187
|
+
if (page.state === "mounted") return;
|
|
3188
|
+
setSlotState(page, "mounted");
|
|
3189
|
+
page.mountPoint = opts.getHostMountPoint ? opts.getHostMountPoint(page.config) : {};
|
|
3190
|
+
}
|
|
3191
|
+
async function unmountPage(handle) {
|
|
3192
|
+
const page = pages.get(handle.pageId);
|
|
3193
|
+
if (!page) throw new Error(`G-42: \u672A\u77E5\u9875\u9762 ${handle.pageId}`);
|
|
3194
|
+
setSlotState(page, "hidden");
|
|
3195
|
+
page.mountPoint = null;
|
|
3196
|
+
}
|
|
3197
|
+
const container = {
|
|
3198
|
+
id: "singlepage",
|
|
3199
|
+
version: "1.0.0",
|
|
3200
|
+
capabilities: CONTAINER_PROFILES.singlepage,
|
|
3201
|
+
async initialize() {
|
|
3202
|
+
},
|
|
3203
|
+
dispose() {
|
|
3204
|
+
pages.clear();
|
|
3205
|
+
current = null;
|
|
3206
|
+
},
|
|
3207
|
+
createPage,
|
|
3208
|
+
mountPage,
|
|
3209
|
+
unmountPage,
|
|
3210
|
+
destroyPage,
|
|
3211
|
+
async push(config) {
|
|
3212
|
+
for (const p of [...pages.values()]) await destroyPage(p);
|
|
3213
|
+
const page = createSlot(config);
|
|
3214
|
+
await mountPage(page);
|
|
3215
|
+
current = page;
|
|
3216
|
+
return page;
|
|
3217
|
+
},
|
|
3218
|
+
async pop() {
|
|
3219
|
+
if (!current) return null;
|
|
3220
|
+
const page = current;
|
|
3221
|
+
await destroyPage(page);
|
|
3222
|
+
return page;
|
|
3223
|
+
},
|
|
3224
|
+
getCurrent() {
|
|
3225
|
+
return current;
|
|
3226
|
+
},
|
|
3227
|
+
getStackDepth() {
|
|
3228
|
+
return current ? 1 : 0;
|
|
3229
|
+
},
|
|
3230
|
+
async createSandbox() {
|
|
3231
|
+
throw new Error("G-42: singlepage \u5BB9\u5668\u65E0\u6C99\u7BB1\uFF08capabilities.multiBusiness=false\u2014\u2014CMP065\uFF09");
|
|
3232
|
+
},
|
|
3233
|
+
async destroySandbox(bizId) {
|
|
3234
|
+
void bizId;
|
|
3235
|
+
return createDestroyReport(bizId);
|
|
3236
|
+
},
|
|
3237
|
+
listSandboxes() {
|
|
3238
|
+
return [];
|
|
3239
|
+
},
|
|
3240
|
+
quota: noQuota("singlepage"),
|
|
3241
|
+
onMemoryPressure() {
|
|
3242
|
+
},
|
|
3243
|
+
on(event, handler) {
|
|
3244
|
+
events[event] = events[event] ?? [];
|
|
3245
|
+
events[event].push(handler);
|
|
3246
|
+
},
|
|
3247
|
+
ownershipOf(pageId) {
|
|
3248
|
+
const page = pages.get(pageId);
|
|
3249
|
+
return page ? page.ownership : null;
|
|
3250
|
+
}
|
|
3251
|
+
};
|
|
3252
|
+
return container;
|
|
3253
|
+
}
|
|
3254
|
+
function createEmbeddedContainer(opts = {}) {
|
|
3255
|
+
const inner = createSinglePageContainer(opts);
|
|
3256
|
+
const container = {
|
|
3257
|
+
id: "embedded",
|
|
3258
|
+
version: "1.0.0",
|
|
3259
|
+
capabilities: CONTAINER_PROFILES.embedded,
|
|
3260
|
+
async initialize() {
|
|
3261
|
+
},
|
|
3262
|
+
dispose() {
|
|
3263
|
+
inner.dispose();
|
|
3264
|
+
},
|
|
3265
|
+
createPage: inner.createPage,
|
|
3266
|
+
mountPage: inner.mountPage,
|
|
3267
|
+
unmountPage: inner.unmountPage,
|
|
3268
|
+
destroyPage: inner.destroyPage,
|
|
3269
|
+
push: inner.push,
|
|
3270
|
+
pop: inner.pop,
|
|
3271
|
+
getCurrent: inner.getCurrent,
|
|
3272
|
+
getStackDepth: inner.getStackDepth,
|
|
3273
|
+
async createSandbox() {
|
|
3274
|
+
throw new Error("G-42: embedded \u5BB9\u5668\u65E0\u6C99\u7BB1\uFF08capabilities.multiBusiness=false\u2014\u2014CMP065\uFF09");
|
|
3275
|
+
},
|
|
3276
|
+
async destroySandbox(bizId) {
|
|
3277
|
+
void bizId;
|
|
3278
|
+
return createDestroyReport(bizId);
|
|
3279
|
+
},
|
|
3280
|
+
listSandboxes() {
|
|
3281
|
+
return [];
|
|
3282
|
+
},
|
|
3283
|
+
quota: noQuota("embedded"),
|
|
3284
|
+
onMemoryPressure() {
|
|
3285
|
+
},
|
|
3286
|
+
on(event, handler) {
|
|
3287
|
+
inner.on(event, handler);
|
|
3288
|
+
},
|
|
3289
|
+
ownershipOf(pageId) {
|
|
3290
|
+
return inner.ownershipOf(pageId);
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
return container;
|
|
3294
|
+
}
|
|
3295
|
+
function createWindowContainer(opts = {}) {
|
|
3296
|
+
const windows = /* @__PURE__ */ new Map();
|
|
3297
|
+
const windowEvents = {};
|
|
3298
|
+
let focused = null;
|
|
3299
|
+
let seq = 0;
|
|
3300
|
+
const emitWindow = (event, payload) => {
|
|
3301
|
+
for (const fn of windowEvents[event] ?? []) fn(payload);
|
|
3302
|
+
};
|
|
3303
|
+
function stackOptions() {
|
|
3304
|
+
return { policy: opts.policy, quotaLimitBytes: opts.quotaLimitBytes, ownership: opts.ownership };
|
|
3305
|
+
}
|
|
3306
|
+
function createWindow(windowId) {
|
|
3307
|
+
const id = windowId ?? `window-${++seq}`;
|
|
3308
|
+
if (windows.has(id)) throw new Error(`G-42: \u7A97\u53E3\u5DF2\u5B58\u5728 ${id}`);
|
|
3309
|
+
const stack = createStackContainer(stackOptions());
|
|
3310
|
+
windows.set(id, stack);
|
|
3311
|
+
focused = id;
|
|
3312
|
+
emitWindow("window-created", { windowId: id });
|
|
3313
|
+
emitWindow("window-focused", { windowId: id });
|
|
3314
|
+
return stack;
|
|
3315
|
+
}
|
|
3316
|
+
function focusedStack() {
|
|
3317
|
+
if (!focused || !windows.has(focused)) {
|
|
3318
|
+
createWindow();
|
|
3319
|
+
}
|
|
3320
|
+
return windows.get(focused);
|
|
3321
|
+
}
|
|
3322
|
+
async function destroyWindow(windowId) {
|
|
3323
|
+
const stack = windows.get(windowId);
|
|
3324
|
+
if (!stack) throw new Error(`G-42: \u672A\u77E5\u7A97\u53E3 ${windowId}`);
|
|
3325
|
+
for (const page of [...stack.stackView]) {
|
|
3326
|
+
await stack.destroyPage(page);
|
|
3327
|
+
}
|
|
3328
|
+
windows.delete(windowId);
|
|
3329
|
+
if (focused === windowId) {
|
|
3330
|
+
focused = windows.keys().next().value ?? null;
|
|
3331
|
+
if (focused) emitWindow("window-focused", { windowId: focused });
|
|
3332
|
+
}
|
|
3333
|
+
emitWindow("window-destroyed", { windowId });
|
|
3334
|
+
}
|
|
3335
|
+
const container = {
|
|
3336
|
+
id: "window",
|
|
3337
|
+
version: "1.0.0",
|
|
3338
|
+
capabilities: CONTAINER_PROFILES.window,
|
|
3339
|
+
async initialize() {
|
|
3340
|
+
if (windows.size === 0) createWindow("window-1");
|
|
3341
|
+
},
|
|
3342
|
+
dispose() {
|
|
3343
|
+
windows.clear();
|
|
3344
|
+
focused = null;
|
|
3345
|
+
},
|
|
3346
|
+
createWindow,
|
|
3347
|
+
destroyWindow,
|
|
3348
|
+
focusWindow(windowId) {
|
|
3349
|
+
if (!windows.has(windowId)) throw new Error(`G-42: \u672A\u77E5\u7A97\u53E3 ${windowId}`);
|
|
3350
|
+
focused = windowId;
|
|
3351
|
+
emitWindow("window-focused", { windowId });
|
|
3352
|
+
},
|
|
3353
|
+
listWindows() {
|
|
3354
|
+
return [...windows.keys()];
|
|
3355
|
+
},
|
|
3356
|
+
get focusedWindow() {
|
|
3357
|
+
return focused;
|
|
3358
|
+
},
|
|
3359
|
+
createPage(config) {
|
|
3360
|
+
return focusedStack().createPage(config);
|
|
3361
|
+
},
|
|
3362
|
+
mountPage(handle) {
|
|
3363
|
+
return focusedStack().mountPage(handle);
|
|
3364
|
+
},
|
|
3365
|
+
unmountPage(handle) {
|
|
3366
|
+
return focusedStack().unmountPage(handle);
|
|
3367
|
+
},
|
|
3368
|
+
destroyPage(handle) {
|
|
3369
|
+
return focusedStack().destroyPage(handle);
|
|
3370
|
+
},
|
|
3371
|
+
push(config) {
|
|
3372
|
+
return focusedStack().push(config);
|
|
3373
|
+
},
|
|
3374
|
+
pop() {
|
|
3375
|
+
return focusedStack().pop();
|
|
3376
|
+
},
|
|
3377
|
+
getCurrent() {
|
|
3378
|
+
return focusedStack().getCurrent();
|
|
3379
|
+
},
|
|
3380
|
+
getStackDepth() {
|
|
3381
|
+
return focusedStack().getStackDepth();
|
|
3382
|
+
},
|
|
3383
|
+
async createSandbox() {
|
|
3384
|
+
throw new Error("G-42: window \u5BB9\u5668\u65E0\u6C99\u7BB1\uFF08capabilities.multiBusiness=false\u2014\u2014CMP065\uFF09");
|
|
3385
|
+
},
|
|
3386
|
+
async destroySandbox(bizId) {
|
|
3387
|
+
void bizId;
|
|
3388
|
+
return createDestroyReport(bizId);
|
|
3389
|
+
},
|
|
3390
|
+
listSandboxes() {
|
|
3391
|
+
return [];
|
|
3392
|
+
},
|
|
3393
|
+
get quota() {
|
|
3394
|
+
return focusedStack().quota;
|
|
3395
|
+
},
|
|
3396
|
+
onMemoryPressure() {
|
|
3397
|
+
},
|
|
3398
|
+
on(event, handler) {
|
|
3399
|
+
const stack = focusedStack();
|
|
3400
|
+
stack.on(event, handler);
|
|
3401
|
+
},
|
|
3402
|
+
ownershipOf(pageId) {
|
|
3403
|
+
for (const stack of windows.values()) {
|
|
3404
|
+
const own = stack.ownershipOf(pageId);
|
|
3405
|
+
if (own) return own;
|
|
3406
|
+
}
|
|
3407
|
+
return null;
|
|
3408
|
+
},
|
|
3409
|
+
onWindow(event, cb) {
|
|
3410
|
+
windowEvents[event] = windowEvents[event] ?? [];
|
|
3411
|
+
windowEvents[event].push(cb);
|
|
3412
|
+
}
|
|
3413
|
+
};
|
|
3414
|
+
return container;
|
|
3415
|
+
}
|
|
3416
|
+
function createMiniProgramContainer(opts = {}) {
|
|
3417
|
+
const policy = {
|
|
3418
|
+
...{ maxDepth: 10, overflowStrategy: "reject", keepAlive: { maxCount: 3, memoryBudgetBytes: 64 * 1024 * 1024 } },
|
|
3419
|
+
...opts.policy
|
|
3420
|
+
};
|
|
3421
|
+
const inner = createStackContainer({
|
|
3422
|
+
policy,
|
|
3423
|
+
quotaLimitBytes: opts.quotaLimitBytes,
|
|
3424
|
+
ownership: opts.ownership
|
|
3425
|
+
});
|
|
3426
|
+
const stack = [];
|
|
3427
|
+
const tabs = /* @__PURE__ */ new Map();
|
|
3428
|
+
const tabPages = /* @__PURE__ */ new Map();
|
|
3429
|
+
const sandboxes = /* @__PURE__ */ new Map();
|
|
3430
|
+
let seq = 0;
|
|
3431
|
+
async function destroyAll(exceptPageId) {
|
|
3432
|
+
for (const p of [...stack]) {
|
|
3433
|
+
if (exceptPageId && p.pageId === exceptPageId) continue;
|
|
3434
|
+
await inner.destroyPage(p);
|
|
3435
|
+
const i = stack.indexOf(p);
|
|
3436
|
+
if (i >= 0) stack.splice(i, 1);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
const container = {
|
|
3440
|
+
id: "miniprogram",
|
|
3441
|
+
version: "1.0.0",
|
|
3442
|
+
capabilities: CONTAINER_PROFILES.miniprogram,
|
|
3443
|
+
async initialize() {
|
|
3444
|
+
},
|
|
3445
|
+
dispose() {
|
|
3446
|
+
stack.length = 0;
|
|
3447
|
+
tabPages.clear();
|
|
3448
|
+
sandboxes.clear();
|
|
3449
|
+
},
|
|
3450
|
+
createPage(config) {
|
|
3451
|
+
return inner.createPage(config);
|
|
3452
|
+
},
|
|
3453
|
+
mountPage(handle) {
|
|
3454
|
+
return inner.mountPage(handle);
|
|
3455
|
+
},
|
|
3456
|
+
unmountPage(handle) {
|
|
3457
|
+
return inner.unmountPage(handle);
|
|
3458
|
+
},
|
|
3459
|
+
destroyPage(handle) {
|
|
3460
|
+
return inner.destroyPage(handle).then((report) => {
|
|
3461
|
+
const i = stack.findIndex((p) => p.pageId === handle.pageId);
|
|
3462
|
+
if (i >= 0) stack.splice(i, 1);
|
|
3463
|
+
return report;
|
|
3464
|
+
});
|
|
3465
|
+
},
|
|
3466
|
+
async push(config) {
|
|
3467
|
+
return container.navigateTo(config);
|
|
3468
|
+
},
|
|
3469
|
+
async pop() {
|
|
3470
|
+
return container.navigateBack();
|
|
3471
|
+
},
|
|
3472
|
+
getCurrent() {
|
|
3473
|
+
return stack[stack.length - 1] ?? null;
|
|
3474
|
+
},
|
|
3475
|
+
getStackDepth() {
|
|
3476
|
+
return stack.length;
|
|
3477
|
+
},
|
|
3478
|
+
async navigateTo(config) {
|
|
3479
|
+
if (stack.length >= policy.maxDepth) {
|
|
3480
|
+
if (policy.overflowStrategy === "destroy-oldest") {
|
|
3481
|
+
const oldest = stack[0];
|
|
3482
|
+
if (!oldest || tabs.has(oldest.pageId)) {
|
|
3483
|
+
throw new Error(`G-42 \u5C0F\u7A0B\u5E8F\u5BFC\u822A\u8D85\u9650\uFF1A\u9875\u9762\u6808\u5DF2\u8FBE ${policy.maxDepth} \u5C42\uFF08\u6808\u5E95\u4E3A tab \u9875\u4E0D\u53EF\u56DE\u6536\uFF09`);
|
|
3484
|
+
}
|
|
3485
|
+
await container.destroyPage(oldest);
|
|
3486
|
+
} else {
|
|
3487
|
+
throw new Error(`G-42 \u5C0F\u7A0B\u5E8F\u5BFC\u822A\u8D85\u9650\uFF1A\u9875\u9762\u6808\u5DF2\u8FBE ${policy.maxDepth} \u5C42\uFF08navigateTo \u62D2\u7EDD\uFF09`);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
const page = inner.createPage(config);
|
|
3491
|
+
await inner.mountPage(page);
|
|
3492
|
+
stack.push(page);
|
|
3493
|
+
return page;
|
|
3494
|
+
},
|
|
3495
|
+
async redirectTo(config) {
|
|
3496
|
+
const top = stack[stack.length - 1];
|
|
3497
|
+
if (top) {
|
|
3498
|
+
await container.destroyPage(top);
|
|
3499
|
+
}
|
|
3500
|
+
return container.navigateTo(config);
|
|
3501
|
+
},
|
|
3502
|
+
async reLaunch(config) {
|
|
3503
|
+
await destroyAll();
|
|
3504
|
+
return container.navigateTo(config);
|
|
3505
|
+
},
|
|
3506
|
+
async switchTab(pageId) {
|
|
3507
|
+
const tab = tabs.get(pageId);
|
|
3508
|
+
if (!tab) throw new Error(`G-42: switchTab \u76EE\u6807\u672A\u6CE8\u518C\u4E3A tab \u9875\uFF1A${pageId}`);
|
|
3509
|
+
const existing = tabPages.get(pageId);
|
|
3510
|
+
const targetIdx = existing ? stack.indexOf(existing) : -1;
|
|
3511
|
+
if (targetIdx >= 0) stack.splice(targetIdx, 1);
|
|
3512
|
+
for (const p of [...stack]) {
|
|
3513
|
+
if (tabs.has(p.pageId)) {
|
|
3514
|
+
await inner.unmountPage(p);
|
|
3515
|
+
} else {
|
|
3516
|
+
await inner.destroyPage(p);
|
|
3517
|
+
}
|
|
3518
|
+
const i = stack.indexOf(p);
|
|
3519
|
+
if (i >= 0) stack.splice(i, 1);
|
|
3520
|
+
}
|
|
3521
|
+
let tabPage = existing;
|
|
3522
|
+
if (tabPage && tabPage.state !== "destroyed") {
|
|
3523
|
+
stack.push(tabPage);
|
|
3524
|
+
if (tabPage.state === "hidden") await inner.mountPage(tabPage);
|
|
3525
|
+
} else {
|
|
3526
|
+
tabPage = inner.createPage({ ...tab, keepAlive: true });
|
|
3527
|
+
tabPages.set(pageId, tabPage);
|
|
3528
|
+
await inner.mountPage(tabPage);
|
|
3529
|
+
stack.push(tabPage);
|
|
3530
|
+
}
|
|
3531
|
+
return tabPage;
|
|
3532
|
+
},
|
|
3533
|
+
async navigateBack() {
|
|
3534
|
+
const top = stack.pop();
|
|
3535
|
+
if (!top) return null;
|
|
3536
|
+
await inner.destroyPage(top);
|
|
3537
|
+
return top;
|
|
3538
|
+
},
|
|
3539
|
+
registerTab(config) {
|
|
3540
|
+
tabs.set(config.pageId, config);
|
|
3541
|
+
},
|
|
3542
|
+
listTabs() {
|
|
3543
|
+
return [...tabs.keys()];
|
|
3544
|
+
},
|
|
3545
|
+
// —— L1 沙箱(scope 隔离——插件/分包业务;崩溃网关是 SuperApp L2 职责)——
|
|
3546
|
+
async createSandbox(bizId, manifest) {
|
|
3547
|
+
void manifest;
|
|
3548
|
+
if (sandboxes.has(bizId)) throw new Error(`G-42: \u6C99\u7BB1\u5DF2\u5B58\u5728 ${bizId}`);
|
|
3549
|
+
const sb = {
|
|
3550
|
+
bizId,
|
|
3551
|
+
isolatedScope: { bizId, values: {} },
|
|
3552
|
+
quotaHandle: null,
|
|
3553
|
+
state: "running",
|
|
3554
|
+
scope: { bizId, values: {} }
|
|
3555
|
+
};
|
|
3556
|
+
sandboxes.set(bizId, sb);
|
|
3557
|
+
return sb;
|
|
3558
|
+
},
|
|
3559
|
+
async destroySandbox(bizId) {
|
|
3560
|
+
const sb = sandboxes.get(bizId);
|
|
3561
|
+
if (sb) {
|
|
3562
|
+
;
|
|
3563
|
+
sb.state = "destroyed";
|
|
3564
|
+
sandboxes.delete(bizId);
|
|
3565
|
+
}
|
|
3566
|
+
return createDestroyReport(bizId);
|
|
3567
|
+
},
|
|
3568
|
+
listSandboxes() {
|
|
3569
|
+
return [...sandboxes.values()];
|
|
3570
|
+
},
|
|
3571
|
+
quota: inner.quota,
|
|
3572
|
+
onMemoryPressure(cb) {
|
|
3573
|
+
inner.onMemoryPressure(cb);
|
|
3574
|
+
},
|
|
3575
|
+
on(event, handler) {
|
|
3576
|
+
inner.on(event, handler);
|
|
3577
|
+
},
|
|
3578
|
+
ownershipOf(pageId) {
|
|
3579
|
+
return inner.ownershipOf(pageId);
|
|
3580
|
+
}
|
|
3581
|
+
};
|
|
3582
|
+
return container;
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
// src/borrow-checker.ts
|
|
3586
|
+
function tokenizeLines(source) {
|
|
3587
|
+
const out = [];
|
|
3588
|
+
let depth = 0;
|
|
3589
|
+
for (const raw of source.split("\n")) {
|
|
3590
|
+
const text = raw.trim();
|
|
3591
|
+
const opens = (text.match(/\{/g) ?? []).length;
|
|
3592
|
+
const closes = (text.match(/\}/g) ?? []).length;
|
|
3593
|
+
out.push({ num: 0, text, depth });
|
|
3594
|
+
depth += opens - closes;
|
|
3595
|
+
}
|
|
3596
|
+
out.forEach((l, i) => l.num = i + 1);
|
|
3597
|
+
return out;
|
|
3598
|
+
}
|
|
3599
|
+
var DECL_RE = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:pageContext|ctx|cp|host|container|this)?\.?(?:alloc|allocShared|createOwned|new Owned)\(/;
|
|
3600
|
+
var BORROW_DECL_RE = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\.borrow\(/;
|
|
3601
|
+
var CALL_RE = /([A-Za-z_$][\w$]*)\s*\.\s*(transferTo|borrow|drop|read|write|get|use)\s*\(/;
|
|
3602
|
+
var ESCAPE_ASSIGN_RE = /(globalCache|windowStore|moduleScope|eventBus)\??\.?\w*\s*=\s*([A-Za-z_$][\w$]*)/;
|
|
3603
|
+
function closureCaptureLine(text, varName) {
|
|
3604
|
+
return /(setTimeout|setInterval|addEventListener|then|subscribe)\s*\(/.test(text) && text.includes(varName);
|
|
3605
|
+
}
|
|
3606
|
+
function analyzeOwnershipSource(source, opts = {}) {
|
|
3607
|
+
const mode = opts.mode ?? "strict";
|
|
3608
|
+
const diagnostics = [];
|
|
3609
|
+
const vars = /* @__PURE__ */ new Map();
|
|
3610
|
+
const borrowVars = /* @__PURE__ */ new Map();
|
|
3611
|
+
const refEdges = [];
|
|
3612
|
+
const add = (rule, severity, line, message) => {
|
|
3613
|
+
if (mode === "off") return;
|
|
3614
|
+
let sev = severity;
|
|
3615
|
+
if (mode === "loose" && severity === "error" && !["B-01", "B-02", "B-04", "B-05"].includes(rule)) {
|
|
3616
|
+
sev = "warning";
|
|
3617
|
+
}
|
|
3618
|
+
diagnostics.push({ rule, severity: sev, line, message });
|
|
3619
|
+
};
|
|
3620
|
+
for (const line of tokenizeLines(source)) {
|
|
3621
|
+
const txt = line.text;
|
|
3622
|
+
for (const [name, borrow] of borrowVars) {
|
|
3623
|
+
if (closureCaptureLine(txt, name)) {
|
|
3624
|
+
add("B-03", "error", line.num, `G4003: borrow escapes scope\u2014\u2014${name} \u88AB\u95ED\u5305\u6355\u83B7\uFF08source ${borrow.source}\uFF09`);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
const decl = txt.match(DECL_RE);
|
|
3628
|
+
if (decl) {
|
|
3629
|
+
const name = decl[1];
|
|
3630
|
+
vars.set(name, { state: "alive", line: line.num, activeBorrows: 0 });
|
|
3631
|
+
continue;
|
|
3632
|
+
}
|
|
3633
|
+
const bdecl = txt.match(BORROW_DECL_RE);
|
|
3634
|
+
if (bdecl) {
|
|
3635
|
+
borrowVars.set(bdecl[1], { source: bdecl[2], line: line.num });
|
|
3636
|
+
const src = vars.get(bdecl[2]);
|
|
3637
|
+
if (src) src.activeBorrows++;
|
|
3638
|
+
continue;
|
|
3639
|
+
}
|
|
3640
|
+
const call = txt.match(CALL_RE);
|
|
3641
|
+
if (call) {
|
|
3642
|
+
const name = call[1];
|
|
3643
|
+
const op = call[2];
|
|
3644
|
+
const info = vars.get(name);
|
|
3645
|
+
if (!info) continue;
|
|
3646
|
+
if (op === "transferTo") {
|
|
3647
|
+
if (info.state === "moved") {
|
|
3648
|
+
add("B-01", "error", line.num, `G4001: use after move\u2014\u2014${name} \u5DF2\u8F6C\u79FB\uFF0C\u4E0D\u53EF\u518D\u64CD\u4F5C`);
|
|
3649
|
+
} else if (info.state !== "alive") {
|
|
3650
|
+
add("B-02", "error", line.num, `G4002: double move\u2014\u2014${name} \u72B6\u6001 ${info.state} \u4E0D\u53EF\u8F6C\u79FB`);
|
|
3651
|
+
} else if (info.activeBorrows > 0) {
|
|
3652
|
+
add("B-05", "error", line.num, `G4005: \u8F6C\u79FB\u65F6\u6709 ${info.activeBorrows} \u4E2A\u6D3B\u8DC3\u501F\u7528\u2014\u2014\u5148 release`);
|
|
3653
|
+
} else {
|
|
3654
|
+
info.state = "moved";
|
|
3655
|
+
}
|
|
3656
|
+
} else if (op === "borrow") {
|
|
3657
|
+
if (info.state !== "alive") {
|
|
3658
|
+
add("B-01", "error", line.num, `G4001: ${name} \u72B6\u6001 ${info.state} \u4E0D\u53EF\u501F\u7528`);
|
|
3659
|
+
} else {
|
|
3660
|
+
info.activeBorrows++;
|
|
3661
|
+
}
|
|
3662
|
+
} else if (op === "drop") {
|
|
3663
|
+
if (info.state === "alive" && info.activeBorrows > 0 && !/force/.test(txt)) {
|
|
3664
|
+
add("B-05", "error", line.num, `G4005: drop \u65F6 ${name} \u6709 ${info.activeBorrows} \u4E2A\u6D3B\u8DC3\u501F\u7528`);
|
|
3665
|
+
} else if (info.state === "alive") {
|
|
3666
|
+
info.state = "dropped";
|
|
3667
|
+
}
|
|
3668
|
+
} else if (op === "read" || op === "write" || op === "get" || op === "use") {
|
|
3669
|
+
if (info.state === "moved") {
|
|
3670
|
+
add("B-01", "error", line.num, `G4001: use after move\u2014\u2014${name} \u5DF2\u8F6C\u79FB\uFF08\u7B2C ${info.line} \u884C declared\uFF09\uFF0C\u6B64\u5904\u8BBF\u95EE\u975E\u6CD5`);
|
|
3671
|
+
} else if (info.state === "dropped") {
|
|
3672
|
+
add("B-01", "error", line.num, `G4001: use after drop\u2014\u2014${name} \u5DF2\u91CA\u653E\uFF0C\u4E0D\u53EF\u518D\u8BBF\u95EE`);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
const esc = txt.match(ESCAPE_ASSIGN_RE);
|
|
3678
|
+
if (esc) {
|
|
3679
|
+
const varName = esc[2];
|
|
3680
|
+
if (borrowVars.has(varName)) {
|
|
3681
|
+
add("B-03", "error", line.num, `G4003: borrow escapes scope\u2014\u2014${varName} \u88AB\u5199\u5165\u66F4\u957F\u5BFF\u5BB9\u5668`);
|
|
3682
|
+
} else if (vars.get(varName)?.state === "alive") {
|
|
3683
|
+
add("B-07", "error", line.num, `G4007: \u8DE8\u9875\u9762\u5F3A\u5F15\u7528\u2014\u2014Owned ${varName} \u88AB\u5199\u5165\u8DE8\u9875\u5BB9\u5668\uFF08\u751F\u547D\u5468\u671F\u8D8A\u754C\uFF0C\u5E94 transferTo/weak\uFF09`);
|
|
3684
|
+
}
|
|
3685
|
+
continue;
|
|
3686
|
+
}
|
|
3687
|
+
const storeSet = txt.match(/\b(?:store|stores|globalCache|windowStore|moduleScope|eventBus)\s*\.\s*(?:set|add|push|write)\s*\(\s*[^,()]+,\s*([A-Za-z_$][\w$]*)/);
|
|
3688
|
+
if (storeSet && vars.get(storeSet[1])?.state === "alive") {
|
|
3689
|
+
add("B-07", "error", line.num, `G4007: \u8DE8\u9875\u9762\u5F3A\u5F15\u7528\u2014\u2014Owned ${storeSet[1]} \u88AB\u5B58\u5165\u8DE8\u9875\u5BB9\u5668\uFF08\u751F\u547D\u5468\u671F\u8D8A\u754C\uFF0C\u5E94 transferTo/weak\uFF09`);
|
|
3690
|
+
continue;
|
|
3691
|
+
}
|
|
3692
|
+
for (const [name, borrow] of borrowVars) {
|
|
3693
|
+
if (closureCaptureLine(txt, name)) {
|
|
3694
|
+
add("B-03", "error", line.num, `G4003: borrow escapes scope\u2014\u2014${name} \u88AB\u95ED\u5305\u6355\u83B7\uFF08source ${borrow.source}\uFF09`);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
const assign = txt.match(/^([A-Za-z_$][\w$]*)\s*\.\s*[\w$]+\s*=\s*([A-Za-z_$][\w$]*);?$/);
|
|
3698
|
+
if (assign && assign[1] !== assign[2]) {
|
|
3699
|
+
refEdges.push({ from: assign[1], to: assign[2], line: line.num });
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
const ownedVars = [...vars.keys()];
|
|
3703
|
+
for (const [name, info] of vars) {
|
|
3704
|
+
if (info.state === "alive") {
|
|
3705
|
+
add("B-06", mode === "strict" ? "warning" : "warning", info.line, `G4006: owned resource not disposed\u2014\u2014${name} \u65E2\u672A drop \u4E5F\u672A transferTo`);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
for (const e of refEdges) {
|
|
3709
|
+
const back = refEdges.find((r) => r.from === e.to && r.to === e.from);
|
|
3710
|
+
if (back || e.from === e.to) {
|
|
3711
|
+
add("B-08", "warning", e.line, `G4008: \u5FAA\u73AF\u5F15\u7528\u2014\u2014${e.from} \u21C4 ${e.to}\uFF08\u6253\u7834\u5FAA\u73AF\u7528 Weak\uFF09`);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
const blocksBuild = mode === "strict" && diagnostics.some((d) => d.severity === "error");
|
|
3715
|
+
return { diagnostics, blocksBuild, ownedVars };
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
// src/ownership-observability.ts
|
|
3719
|
+
function createOwnershipHistory(graph, opts = {}) {
|
|
3720
|
+
const limit = opts.limit ?? 2e3;
|
|
3721
|
+
let records = [];
|
|
3722
|
+
let enabled = opts.enabled ?? true;
|
|
3723
|
+
let disposed = false;
|
|
3724
|
+
function push(rec) {
|
|
3725
|
+
records.push(rec);
|
|
3726
|
+
if (records.length > limit) records = records.slice(-limit);
|
|
3727
|
+
}
|
|
3728
|
+
const unsub = graph.subscribe((m) => {
|
|
3729
|
+
if (!enabled || disposed) return;
|
|
3730
|
+
if (m.kind === "register") {
|
|
3731
|
+
push({ ts: m.ts, kind: "alloc", id: m.id, owner: m.owner, type: m.type, byteSize: m.byteSize, sourceLocation: m.sourceLocation });
|
|
3732
|
+
} else if (m.kind === "state") {
|
|
3733
|
+
if (m.state === "dropped") {
|
|
3734
|
+
const n = graph.nodes.get(m.id);
|
|
3735
|
+
push({ ts: m.ts, kind: "drop", id: m.id, owner: n?.owner, type: n?.type, byteSize: n?.byteSize, sourceLocation: n?.sourceLocation ?? null });
|
|
3736
|
+
} else if (m.state === "moved") {
|
|
3737
|
+
const n = graph.nodes.get(m.id);
|
|
3738
|
+
push({ ts: m.ts, kind: "moved", id: m.id, owner: n?.owner, type: n?.type, byteSize: n?.byteSize, sourceLocation: n?.sourceLocation ?? null });
|
|
3739
|
+
}
|
|
3740
|
+
} else if (m.kind === "edge") {
|
|
3741
|
+
const k = m.edge.kind;
|
|
3742
|
+
if (k === "owns" || k === "moved-from") return;
|
|
3743
|
+
push({ ts: m.ts, kind: k === "borrows" ? "borrow" : k, from: m.edge.from, to: m.edge.to });
|
|
3744
|
+
}
|
|
3745
|
+
});
|
|
3746
|
+
const history = {
|
|
3747
|
+
get records() {
|
|
3748
|
+
return records;
|
|
3749
|
+
},
|
|
3750
|
+
get enabled() {
|
|
3751
|
+
return enabled;
|
|
3752
|
+
},
|
|
3753
|
+
set enabled(v) {
|
|
3754
|
+
enabled = v;
|
|
3755
|
+
},
|
|
3756
|
+
setEnabled(v) {
|
|
3757
|
+
enabled = v;
|
|
3758
|
+
},
|
|
3759
|
+
clear() {
|
|
3760
|
+
records = [];
|
|
3761
|
+
},
|
|
3762
|
+
dispose() {
|
|
3763
|
+
disposed = true;
|
|
3764
|
+
unsub();
|
|
3765
|
+
records = [];
|
|
3766
|
+
}
|
|
3767
|
+
};
|
|
3768
|
+
return history;
|
|
3769
|
+
}
|
|
3770
|
+
function createOwnershipCounters(graph) {
|
|
3771
|
+
let alive = 0;
|
|
3772
|
+
let total = 0;
|
|
3773
|
+
let bytesAlive = 0;
|
|
3774
|
+
const byType = /* @__PURE__ */ new Map();
|
|
3775
|
+
const bump = (type, delta) => {
|
|
3776
|
+
const c = byType.get(type) ?? { allocated: 0, alive: 0 };
|
|
3777
|
+
c.alive += delta;
|
|
3778
|
+
if (c.alive < 0) c.alive = 0;
|
|
3779
|
+
byType.set(type, c);
|
|
3780
|
+
};
|
|
3781
|
+
for (const n of graph.nodes.values()) {
|
|
3782
|
+
total++;
|
|
3783
|
+
const c = byType.get(n.type) ?? { allocated: 0, alive: 0 };
|
|
3784
|
+
c.allocated++;
|
|
3785
|
+
byType.set(n.type, c);
|
|
3786
|
+
if (n.state === "alive") {
|
|
3787
|
+
alive++;
|
|
3788
|
+
bytesAlive += n.byteSize;
|
|
3789
|
+
c.alive++;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
graph.subscribe((m) => {
|
|
3793
|
+
if (m.kind === "register") {
|
|
3794
|
+
alive++;
|
|
3795
|
+
total++;
|
|
3796
|
+
bytesAlive += m.byteSize;
|
|
3797
|
+
const c = byType.get(m.type) ?? { allocated: 0, alive: 0 };
|
|
3798
|
+
c.allocated++;
|
|
3799
|
+
c.alive++;
|
|
3800
|
+
byType.set(m.type, c);
|
|
3801
|
+
} else if (m.kind === "state") {
|
|
3802
|
+
if (m.state === "dropped" || m.state === "moved") {
|
|
3803
|
+
const n = graph.nodes.get(m.id);
|
|
3804
|
+
alive = Math.max(0, alive - 1);
|
|
3805
|
+
bytesAlive = Math.max(0, bytesAlive - (n?.byteSize ?? 0));
|
|
3806
|
+
bump(n?.type ?? "?", -1);
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
});
|
|
3810
|
+
return {
|
|
3811
|
+
get alive() {
|
|
3812
|
+
return alive;
|
|
3813
|
+
},
|
|
3814
|
+
get total() {
|
|
3815
|
+
return total;
|
|
3816
|
+
},
|
|
3817
|
+
get bytesAlive() {
|
|
3818
|
+
return bytesAlive;
|
|
3819
|
+
},
|
|
3820
|
+
get byType() {
|
|
3821
|
+
return Object.fromEntries(byType);
|
|
3822
|
+
},
|
|
3823
|
+
consistent() {
|
|
3824
|
+
const s = graph.stats();
|
|
3825
|
+
return alive === s.alive && total === s.total && bytesAlive === s.totalBytes;
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
function diagnoseOwnershipIssues(graph, opts = {}) {
|
|
3830
|
+
const now = opts.now ? opts.now() : Date.now();
|
|
3831
|
+
const longBorrowMs = opts.longBorrowMs ?? 1e3;
|
|
3832
|
+
const destroyedScopes = opts.destroyedScopes ?? [];
|
|
3833
|
+
const stats = graph.stats();
|
|
3834
|
+
const orphans = graph.findOrphans();
|
|
3835
|
+
const leaks = [];
|
|
3836
|
+
for (const scope of destroyedScopes) {
|
|
3837
|
+
leaks.push(...graph.detectLeaks(scope));
|
|
3838
|
+
}
|
|
3839
|
+
const longBorrows = [];
|
|
3840
|
+
for (const e of graph.edges) {
|
|
3841
|
+
if (e.kind !== "borrows") continue;
|
|
3842
|
+
const sinceMs = e.since ?? 0;
|
|
3843
|
+
const duration = sinceMs > 0 ? now - sinceMs : 0;
|
|
3844
|
+
if (duration > longBorrowMs) {
|
|
3845
|
+
const n = graph.nodes.get(e.to);
|
|
3846
|
+
longBorrows.push({ resourceId: e.to, type: n?.type ?? "?", owner: n?.owner ?? null, borrowedBy: e.from, sinceMs });
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
const crossPageRefs = [];
|
|
3850
|
+
for (const e of graph.edges) {
|
|
3851
|
+
if (e.kind !== "strong") continue;
|
|
3852
|
+
const n = graph.nodes.get(e.to);
|
|
3853
|
+
if (!n || n.state !== "alive") continue;
|
|
3854
|
+
if (e.from === n.owner) continue;
|
|
3855
|
+
crossPageRefs.push({ resourceId: e.to, type: n.type, owner: n.owner, heldBy: e.from });
|
|
3856
|
+
}
|
|
3857
|
+
return { aliveNodes: stats.alive, totalNodes: stats.total, orphans, leaks, longBorrows, crossPageRefs };
|
|
3858
|
+
}
|
|
3859
|
+
function buildOwnershipTimeline(history, graph) {
|
|
3860
|
+
const events = [];
|
|
3861
|
+
const allocById = /* @__PURE__ */ new Map();
|
|
3862
|
+
for (const rec of history.records) {
|
|
3863
|
+
if (rec.kind === "alloc" && rec.id) {
|
|
3864
|
+
allocById.set(rec.id, rec);
|
|
3865
|
+
events.push(rec);
|
|
3866
|
+
} else if ((rec.kind === "drop" || rec.kind === "moved") && rec.id) {
|
|
3867
|
+
const matched = allocById.get(rec.id);
|
|
3868
|
+
if (matched) allocById.delete(rec.id);
|
|
3869
|
+
events.push({ ...rec, matchedAlloc: matched });
|
|
3870
|
+
} else {
|
|
3871
|
+
events.push(rec);
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
const unpairedAllocs = [...allocById.values()].filter((a) => a.id !== void 0 && graph.nodes.get(a.id)?.state === "alive");
|
|
3875
|
+
return { events, unpairedAllocs };
|
|
3876
|
+
}
|
|
3877
|
+
function formatOwnershipDiagnosis(d, opts = {}) {
|
|
3878
|
+
const src = opts.sourceLabel ?? "\u{1F4CD}";
|
|
3879
|
+
const lines = [];
|
|
3880
|
+
lines.push(`\u6240\u6709\u6743\u56FE\uFF1A${d.aliveNodes} alive / ${d.totalNodes} total`);
|
|
3881
|
+
if (d.orphans.length > 0) {
|
|
3882
|
+
lines.push(`\u{1F534} \u65E0\u4E3B\u8D44\u6E90 ${d.orphans.length} \u5904\uFF08\u5FC5\u7136\u6CC4\u6F0F\u2014\u2014\u6846\u67B6 bug \u6216\u672A\u6B63\u786E\u767B\u8BB0\uFF09\uFF1A`);
|
|
3883
|
+
for (const o of d.orphans) lines.push(` \xB7 ${o.id}\uFF08${o.type}\uFF0C${o.byteSize}B\uFF09${o.sourceLocation ? `${src} ${o.sourceLocation}` : ""}`);
|
|
3884
|
+
}
|
|
3885
|
+
if (d.leaks.length > 0) {
|
|
3886
|
+
lines.push(`\u26A0\uFE0F \u6CC4\u6F0F\u8DEF\u5F84 ${d.leaks.length} \u5904\uFF08\u9875\u9762\u5DF2\u9500\u6BC1\u8D44\u6E90\u4ECD\u5B58\u6D3B\uFF09\uFF1A`);
|
|
3887
|
+
for (const l of d.leaks) {
|
|
3888
|
+
lines.push(` \xB7 ${l.resourceId}\uFF08${l.type}\uFF0C${l.byteSize}B\uFF09${l.sourceLocation ? `${src} ${l.sourceLocation}` : ""}`);
|
|
3889
|
+
for (const hop of l.referenceChain) lines.push(` ${hop}`);
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
if (d.longBorrows.length > 0) {
|
|
3893
|
+
lines.push(`\u{1F7E1} \u957F\u671F\u501F\u7528 ${d.longBorrows.length} \u5904\uFF08>\u9608\u503C\u2014\u2014\u501F\u7528\u7684\u8FD0\u884C\u65F6\u9003\u9038\u8868\u73B0\uFF09\uFF1A`);
|
|
3894
|
+
for (const b of d.longBorrows) lines.push(` \xB7 ${b.resourceId} \u2190 borrowed by ${b.borrowedBy}\uFF08owner ${b.owner ?? "\u65E0\u4E3B"}\uFF09`);
|
|
3895
|
+
}
|
|
3896
|
+
if (d.crossPageRefs.length > 0) {
|
|
3897
|
+
lines.push(`\u26A0\uFE0F \u8DE8\u9875\u9762\u5F3A\u5F15\u7528 ${d.crossPageRefs.length} \u5904\uFF08\u9500\u6BC1\u65F6\u65E0\u6CD5\u91CA\u653E\u2014\u2014\u5E94 transferTo/weak\uFF09\uFF1A`);
|
|
3898
|
+
for (const c of d.crossPageRefs) lines.push(` \xB7 ${c.resourceId}\uFF08owner ${c.owner ?? "\u65E0\u4E3B"}\uFF09\u2190 \u5F3A\u6301\u6709 by ${c.heldBy}`);
|
|
3899
|
+
}
|
|
3900
|
+
if (d.orphans.length === 0 && d.leaks.length === 0 && d.longBorrows.length === 0 && d.crossPageRefs.length === 0) {
|
|
3901
|
+
lines.push("\u2705 \u65E0\u5F02\u5E38\uFF08\u65E0\u65E0\u4E3B\u8D44\u6E90/\u6CC4\u6F0F\u8DEF\u5F84/\u957F\u671F\u501F\u7528/\u8DE8\u9875\u5F3A\u5F15\u7528\uFF09");
|
|
3902
|
+
}
|
|
3903
|
+
return lines.join("\n");
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
// src/pss.ts
|
|
3907
|
+
var PSS_PRAGMA_RE = /@proteus-pss\s*[:=]?\s*(strict|loose|off)/i;
|
|
3908
|
+
function resolvePssMode(source, fallback = "off") {
|
|
3909
|
+
const head = source.split("\n", 20).join("\n");
|
|
3910
|
+
const m = head.match(PSS_PRAGMA_RE);
|
|
3911
|
+
return m?.[1]?.toLowerCase() ?? fallback;
|
|
3912
|
+
}
|
|
3913
|
+
var STATIC_PSS_RULES = [
|
|
3914
|
+
// P1 禁 any/unknown(类型不可推导——borrow-checker.md §2.1)
|
|
3915
|
+
{ rule: "P1", re: /:\s*(?:any|unknown)\b|\bas\s+(?:any|unknown)\b|<(?:any|unknown)>/, loose: true, message: "G4011: PSS \u7981\u7528 any/unknown\u2014\u2014\u7C7B\u578B\u4E0D\u53EF\u63A8\u5BFC\uFF0C\u6240\u6709\u6743\u5206\u6790\u5931\u6548" },
|
|
3916
|
+
// P2 动态属性写入(`obj[keyVar] =`——字符串字面量键放行;布局不稳定)
|
|
3917
|
+
{ rule: "P2", re: /\[[A-Za-z_$][\w$]*\]\s*=\s/, loose: true, message: "G4012: PSS \u7981\u7528\u52A8\u6001\u5C5E\u6027\u5199\u5165\u2014\u2014\u5BF9\u8C61\u5F62\u72B6\u4E0D\u53EF\u9759\u6001\u63A8\u5BFC" },
|
|
3918
|
+
// P3 delete(破坏所有权登记)
|
|
3919
|
+
{ rule: "P3", re: /\bdelete\s+[A-Za-z_$][\w$]*/, loose: false, message: "G4013: PSS \u7981\u7528 delete \u64CD\u4F5C\u7B26\u2014\u2014\u7834\u574F\u6240\u6709\u6743\u767B\u8BB0" },
|
|
3920
|
+
// P4 eval / new Function(完全不可分析)
|
|
3921
|
+
{ rule: "P4", re: /\beval\s*\(|\bnew\s+Function\s*\(/, loose: false, message: "G4014: PSS \u7981\u7528 eval/new Function\u2014\u2014\u4EE3\u7801\u5B8C\u5168\u4E0D\u53EF\u5206\u6790" },
|
|
3922
|
+
// P8 with(作用域动态)
|
|
3923
|
+
{ rule: "P8", re: /\bwith\s*\(/, loose: false, message: "G4018: PSS \u7981\u7528 with \u8BED\u53E5\u2014\u2014\u4F5C\u7528\u57DF\u52A8\u6001\u4E0D\u53EF\u5206\u6790" },
|
|
3924
|
+
// P9 原型链动态修改(破坏类型契约)
|
|
3925
|
+
{ rule: "P9", re: /\.\s*prototype\s*\.\s*[\w$]+\s*=\s/, loose: false, message: "G4019: PSS \u7981\u7528\u539F\u578B\u94FE\u52A8\u6001\u4FEE\u6539\u2014\u2014\u7834\u574F\u7C7B\u578B\u5951\u7EA6" }
|
|
3926
|
+
];
|
|
3927
|
+
function closureCaptureLine2(text, varName) {
|
|
3928
|
+
return /(setTimeout|setInterval|addEventListener|then|subscribe|queueMicrotask)\s*\(/.test(text) && text.includes(varName);
|
|
3929
|
+
}
|
|
3930
|
+
function analyzePss(source, opts = {}) {
|
|
3931
|
+
const mode = opts.mode ?? resolvePssMode(source);
|
|
3932
|
+
const diagnostics = [];
|
|
3933
|
+
if (mode === "off") return { diagnostics, mode };
|
|
3934
|
+
const ownedVars = opts.ownedVars ?? analyzeOwnershipSource(source, { mode }).ownedVars;
|
|
3935
|
+
const ownedSet = new Set(ownedVars);
|
|
3936
|
+
const add = (rule, severity, line, message) => {
|
|
3937
|
+
diagnostics.push({ rule, severity, line, message });
|
|
3938
|
+
};
|
|
3939
|
+
const lines = source.split("\n");
|
|
3940
|
+
lines.forEach((raw, i) => {
|
|
3941
|
+
const txt = raw.trim();
|
|
3942
|
+
const line = i + 1;
|
|
3943
|
+
if (txt.startsWith("//") || txt.startsWith("*") || txt.startsWith("/*")) return;
|
|
3944
|
+
for (const r of STATIC_PSS_RULES) {
|
|
3945
|
+
if (r.re.test(txt) && (mode === "strict" || r.loose)) {
|
|
3946
|
+
add(r.rule, "error", line, r.message);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
const globalAssign = txt.match(/(?:globalThis|window|global|self)\s*\.\s*[\w$]+\s*=\s*([A-Za-z_$][\w$]*)/);
|
|
3950
|
+
if (globalAssign && ownedSet.has(globalAssign[1])) {
|
|
3951
|
+
add("P5", "error", line, `G4015: Owned ${globalAssign[1]} \u9003\u9038\u5230\u5168\u5C40\u2014\u2014\u6240\u6709\u6743\u8131\u79BB\u4F5C\u7528\u57DF\uFF08strict \u7981\u6B62\uFF09`);
|
|
3952
|
+
}
|
|
3953
|
+
for (const name of ownedSet) {
|
|
3954
|
+
if (closureCaptureLine2(txt, name)) {
|
|
3955
|
+
add("P7", "error", line, `G4017: \u95ED\u5305\u6355\u83B7 Owned ${name}\u2014\u2014\u4F5C\u7528\u57DF\u4E0D\u53EF\u63A7\uFF0C\u5E94\u663E\u5F0F transferTo \u6216 drop`);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
const refWrap = txt.match(/\b(?:ref|reactive|shallowRef|readonly)\s*\(\s*([A-Za-z_$][\w$]*)/);
|
|
3959
|
+
if (refWrap && ownedSet.has(refWrap[1])) {
|
|
3960
|
+
add("CMP071", mode === "strict" ? "error" : "warning", line, `CMP071: ${refWrap[1]}\uFF08Owned\uFF09\u88AB ref/reactive \u5305\u88C5\u2014\u2014Proxy \u7834\u574F\u6240\u6709\u6743\u8BED\u4E49\uFF0C\u6539\u7528 useOwned()\uFF08\u54CD\u5E94\u5F0F\u5143\u4FE1\u606F\uFF09`);
|
|
3961
|
+
}
|
|
3962
|
+
});
|
|
3963
|
+
return { diagnostics, mode };
|
|
3964
|
+
}
|
|
3965
|
+
function insertScopeDrops(source) {
|
|
3966
|
+
const rawLines = source.split("\n");
|
|
3967
|
+
const depths = [];
|
|
3968
|
+
let depth = 0;
|
|
3969
|
+
for (const raw of rawLines) {
|
|
3970
|
+
depths.push(depth);
|
|
3971
|
+
const t = raw.trim();
|
|
3972
|
+
depth += (t.match(/\{/g) ?? []).length - (t.match(/\}/g) ?? []).length;
|
|
3973
|
+
}
|
|
3974
|
+
const pending = /* @__PURE__ */ new Set();
|
|
3975
|
+
const insertionsByLine = /* @__PURE__ */ new Map();
|
|
3976
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
3977
|
+
const t = rawLines[i].trim();
|
|
3978
|
+
const d = depths[i];
|
|
3979
|
+
const line = i + 1;
|
|
3980
|
+
const decl = t.match(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:pageContext|ctx|cp|host|container|this)?\.?(?:alloc|allocShared|createOwned|new Owned)\(/);
|
|
3981
|
+
if (decl) {
|
|
3982
|
+
if (d > 0) {
|
|
3983
|
+
const indent = rawLines[i].match(/^\s*/)?.[0] ?? "";
|
|
3984
|
+
pending.add({ varName: decl[1], declDepth: d, declLine: line, indent, disposed: false });
|
|
3985
|
+
}
|
|
3986
|
+
continue;
|
|
3987
|
+
}
|
|
3988
|
+
const call = t.match(/([A-Za-z_$][\w$]*)\s*\.\s*(?:drop|transferTo)\s*\(/);
|
|
3989
|
+
if (call) {
|
|
3990
|
+
for (const p of pending) {
|
|
3991
|
+
if (p.varName === call[1] && !p.disposed) {
|
|
3992
|
+
p.disposed = true;
|
|
3993
|
+
break;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
if (t.includes("}")) {
|
|
3998
|
+
for (const p of [...pending]) {
|
|
3999
|
+
if (p.declDepth === d) {
|
|
4000
|
+
if (!p.disposed) {
|
|
4001
|
+
const list = insertionsByLine.get(line) ?? [];
|
|
4002
|
+
list.push(p);
|
|
4003
|
+
insertionsByLine.set(line, list);
|
|
4004
|
+
}
|
|
4005
|
+
pending.delete(p);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
const insertions = [];
|
|
4011
|
+
const outLines = [...rawLines];
|
|
4012
|
+
const closeLines = [...insertionsByLine.keys()].sort((a, b) => b - a);
|
|
4013
|
+
for (const closeLine of closeLines) {
|
|
4014
|
+
const items = insertionsByLine.get(closeLine);
|
|
4015
|
+
const stmts = items.map((p) => {
|
|
4016
|
+
insertions.push({ varName: p.varName, line: closeLine });
|
|
4017
|
+
return `${p.indent} ${p.varName}.drop()`;
|
|
4018
|
+
});
|
|
4019
|
+
outLines.splice(closeLine - 1, 0, ...stmts);
|
|
4020
|
+
}
|
|
4021
|
+
return { code: outLines.join("\n"), insertions };
|
|
4022
|
+
}
|
|
4023
|
+
function runPss(source, opts = {}) {
|
|
4024
|
+
const mode = resolvePssMode(source, opts.mode ?? "off");
|
|
4025
|
+
const autoDrop = opts.autoDrop ?? true;
|
|
4026
|
+
if (mode === "off") {
|
|
4027
|
+
return { mode, diagnostics: [], blocksBuild: false, ownedVars: [], code: source, insertions: [] };
|
|
4028
|
+
}
|
|
4029
|
+
const borrow = analyzeOwnershipSource(source, { mode });
|
|
4030
|
+
const pss = analyzePss(source, { mode, ownedVars: borrow.ownedVars });
|
|
4031
|
+
const diagnostics = [...borrow.diagnostics, ...pss.diagnostics];
|
|
4032
|
+
let code = source;
|
|
4033
|
+
let insertions = [];
|
|
4034
|
+
if (autoDrop) {
|
|
4035
|
+
const r = insertScopeDrops(source);
|
|
4036
|
+
code = r.code;
|
|
4037
|
+
insertions = r.insertions;
|
|
4038
|
+
}
|
|
4039
|
+
const blocksBuild = mode === "strict" && diagnostics.some((d) => d.severity === "error");
|
|
4040
|
+
return { mode, diagnostics, blocksBuild, ownedVars: borrow.ownedVars, code, insertions };
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
// src/host-matrix.ts
|
|
4044
|
+
var HOSTS = ["web", "ios", "android", "harmony", "flutter", "miniprogram"];
|
|
4045
|
+
var ENGINES = ["vue-dom", "headless", "native-ios", "native-android", "native-harmony", "flutter"];
|
|
4046
|
+
var HOST_ENGINE_MATRIX = {
|
|
4047
|
+
web: { "vue-dom": 1, "headless": 1, "flutter": 3, "native-ios": 0, "native-android": 0, "native-harmony": 0 },
|
|
4048
|
+
ios: { "vue-dom": 3, "headless": 1, "flutter": 1, "native-ios": 1, "native-android": 0, "native-harmony": 0 },
|
|
4049
|
+
android: { "vue-dom": 3, "headless": 1, "flutter": 1, "native-ios": 0, "native-android": 1, "native-harmony": 0 },
|
|
4050
|
+
harmony: { "vue-dom": 3, "headless": 1, "flutter": 0, "native-ios": 0, "native-android": 0, "native-harmony": 1 },
|
|
4051
|
+
flutter: { "vue-dom": 0, "headless": 1, "flutter": 1, "native-ios": 3, "native-android": 3, "native-harmony": 0 },
|
|
4052
|
+
miniprogram: { "vue-dom": 0, "headless": 1, "flutter": 0, "native-ios": 0, "native-android": 0, "native-harmony": 0 }
|
|
4053
|
+
};
|
|
4054
|
+
function matrixCombos(filter = {}) {
|
|
4055
|
+
const out = [];
|
|
4056
|
+
for (const host of HOSTS) {
|
|
4057
|
+
for (const engine of ENGINES) {
|
|
4058
|
+
const tier = HOST_ENGINE_MATRIX[host][engine];
|
|
4059
|
+
if (filter.tier === void 0 || tier === filter.tier) out.push({ host, engine, tier });
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
return out;
|
|
4063
|
+
}
|
|
4064
|
+
function createEngine(engine, documentLike) {
|
|
4065
|
+
switch (engine) {
|
|
4066
|
+
case "vue-dom":
|
|
4067
|
+
return createVueDomBackend(documentLike);
|
|
4068
|
+
case "headless":
|
|
4069
|
+
return createHeadlessBackend();
|
|
4070
|
+
case "native-ios":
|
|
4071
|
+
return createNativeBackend(void 0, "ios");
|
|
4072
|
+
case "native-android":
|
|
4073
|
+
return createNativeBackend(void 0, "android");
|
|
4074
|
+
case "native-harmony":
|
|
4075
|
+
return createNativeBackend(void 0, "harmony");
|
|
4076
|
+
case "flutter":
|
|
4077
|
+
return createFlutterBackend();
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
var GRID_CONTROL = {
|
|
4081
|
+
"vue-dom": { attr: "class", value: "proteus-grid" },
|
|
4082
|
+
"headless": { attr: "type", value: "grid" },
|
|
4083
|
+
"native-ios": { attr: "type", value: "UICollectionView" },
|
|
4084
|
+
"native-android": { attr: "type", value: "GridLayoutManager" },
|
|
4085
|
+
"native-harmony": { attr: "type", value: "Grid" },
|
|
4086
|
+
"flutter": { attr: "widget", value: "GridView" }
|
|
4087
|
+
};
|
|
4088
|
+
function secondEngineOf(engine) {
|
|
4089
|
+
return engine === "flutter" ? "headless" : "flutter";
|
|
4090
|
+
}
|
|
4091
|
+
function countIR(ir) {
|
|
4092
|
+
return 1 + ir.children.reduce((s, c) => s + countIR(c), 0);
|
|
4093
|
+
}
|
|
4094
|
+
function walkNodes(node, visit) {
|
|
4095
|
+
const n = node;
|
|
4096
|
+
visit(n);
|
|
4097
|
+
let count = 1;
|
|
4098
|
+
const children = n.children;
|
|
4099
|
+
if (Array.isArray(children)) {
|
|
4100
|
+
for (const c of children) count += walkNodes(c, visit);
|
|
4101
|
+
} else if (children && typeof children.length === "number") {
|
|
4102
|
+
const list = children;
|
|
4103
|
+
for (let i = 0; i < list.length; i++) count += walkNodes(list[i], visit);
|
|
4104
|
+
}
|
|
4105
|
+
return count;
|
|
4106
|
+
}
|
|
4107
|
+
function buildProductIR2(dispatch) {
|
|
4108
|
+
const grid = dispatch.toIRNode("p-grid", { minColWidth: 160 });
|
|
4109
|
+
const box1 = dispatch.toIRNode("p-box", {});
|
|
4110
|
+
const box2 = dispatch.toIRNode("p-box", {});
|
|
4111
|
+
const text = dispatch.toIRNode("p-text", { content: "\u5546\u54C1 A" });
|
|
4112
|
+
const button = dispatch.toIRNode("p-button", { variant: "primary" });
|
|
4113
|
+
return {
|
|
4114
|
+
...dispatch.toIRNode("p-page", { title: "Product" }),
|
|
4115
|
+
children: [{ ...grid, children: [{ ...box1, children: [text] }, { ...box2, children: [button] }] }]
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
async function runComboConformance(opts) {
|
|
4119
|
+
const { host, engine } = opts;
|
|
4120
|
+
const checks = [];
|
|
4121
|
+
const add = (id, ok, message) => {
|
|
4122
|
+
checks.push({ id, ok, message: ok ? void 0 : message });
|
|
4123
|
+
};
|
|
4124
|
+
const backend = createEngine(engine, opts.documentLike);
|
|
4125
|
+
const backendConf = runBackendConformance(backend);
|
|
4126
|
+
add("backend-conformance", backendConf.ok, `\u5F15\u64CE\u81EA\u8EAB conformance \u672A\u8FC7\uFF08${backendConf.checks.filter((c) => !c.pass).length} \u9879\uFF09`);
|
|
4127
|
+
const dispatch = createNodeOpsDispatcher(backend);
|
|
4128
|
+
const carrier = createCarrierStub("jsi");
|
|
4129
|
+
const hostRuntime = host === "web" ? createWebHostRuntime() : createHostRuntimeStub();
|
|
4130
|
+
add("registration-before-bootstrap", dispatch.currentBackend === backend && hostRuntime.state === "created", "Backend \u5E94\u5728 bootstrap \u524D\u6CE8\u518C\uFF08G-41.6\uFF09");
|
|
4131
|
+
hostRuntime.bootstrap();
|
|
4132
|
+
add("host-bootstrap", hostRuntime.state === "running", "\u5BBF\u4E3B bootstrap \u540E\u5E94 running");
|
|
4133
|
+
const ir = buildProductIR2(dispatch);
|
|
4134
|
+
const fingerprint = semanticSequence(ir);
|
|
4135
|
+
add("semantic-fingerprint", JSON.stringify(fingerprint) === JSON.stringify(["shell.page", "layout.grid", "layout.box", "ui.text", "layout.box", "ui.button"]), `\u8BED\u4E49\u6307\u7EB9\u4E0D\u7B26\uFF1A${fingerprint.join(",")}`);
|
|
4136
|
+
const root = renderIRTree(backend, ir);
|
|
4137
|
+
const irCount = countIR(ir);
|
|
4138
|
+
const engineCount = walkNodes(root, () => {
|
|
4139
|
+
});
|
|
4140
|
+
add("render-complete", engineCount === irCount, `\u5F15\u64CE\u8282\u70B9\u6570 ${engineCount} \u2260 IR \u8282\u70B9\u6570 ${irCount}`);
|
|
4141
|
+
const ctrl = GRID_CONTROL[engine];
|
|
4142
|
+
let gridFound = false;
|
|
4143
|
+
walkNodes(root, (n) => {
|
|
4144
|
+
if (ctrl.attr === "class") {
|
|
4145
|
+
const cls = String(n.className ?? "");
|
|
4146
|
+
if (cls.includes(ctrl.value)) gridFound = true;
|
|
4147
|
+
} else if (n[ctrl.attr] === ctrl.value) {
|
|
4148
|
+
gridFound = true;
|
|
4149
|
+
}
|
|
4150
|
+
});
|
|
4151
|
+
add("semantic-control-mapping", gridFound, `layout.grid \u672A\u6620\u5C04\u5230 ${ctrl.value}\uFF08${engine}\uFF09`);
|
|
4152
|
+
const second = secondEngineOf(engine);
|
|
4153
|
+
const backend2 = createEngine(second, opts.documentLike);
|
|
4154
|
+
dispatch.switchBackend(backend2);
|
|
4155
|
+
const root2 = renderIRTree(backend2, ir);
|
|
4156
|
+
const count2 = walkNodes(root2, () => {
|
|
4157
|
+
});
|
|
4158
|
+
const ctrl2 = GRID_CONTROL[second];
|
|
4159
|
+
let grid2 = false;
|
|
4160
|
+
walkNodes(root2, (n) => {
|
|
4161
|
+
if (ctrl2.attr === "class") {
|
|
4162
|
+
if (String(n.className ?? "").includes(ctrl2.value)) grid2 = true;
|
|
4163
|
+
} else if (n[ctrl2.attr] === ctrl2.value) {
|
|
4164
|
+
grid2 = true;
|
|
4165
|
+
}
|
|
4166
|
+
});
|
|
4167
|
+
add(
|
|
4168
|
+
"hot-switch-equivalence",
|
|
4169
|
+
dispatch.currentBackend === backend2 && count2 === irCount && grid2 && semanticSequence(ir).length === fingerprint.length,
|
|
4170
|
+
`\u5207\u6362\u5230 ${second} \u540E\u6E32\u67D3\u4E0D\u7B49\u4EF7\uFF08count ${count2}/${irCount}\uFF0Cgrid ${grid2}\uFF09`
|
|
4171
|
+
);
|
|
4172
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
4173
|
+
return { host, engine, ok: failed === 0, total: checks.length, failed, checks };
|
|
4174
|
+
}
|
|
4175
|
+
async function runHostEngineMatrix(opts = {}) {
|
|
4176
|
+
const rows = [];
|
|
4177
|
+
for (const combo of matrixCombos()) {
|
|
4178
|
+
if (opts.only && (opts.only.host && opts.only.host !== combo.host || opts.only.engine && opts.only.engine !== combo.engine)) {
|
|
4179
|
+
continue;
|
|
4180
|
+
}
|
|
4181
|
+
if (combo.tier === 1) {
|
|
4182
|
+
const result = await runComboConformance({ host: combo.host, engine: combo.engine, documentLike: opts.documentLike });
|
|
4183
|
+
rows.push({ host: combo.host, engine: combo.engine, tier: combo.tier, result });
|
|
4184
|
+
} else {
|
|
4185
|
+
rows.push({ host: combo.host, engine: combo.engine, tier: combo.tier, result: null });
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
const tier1Rows = rows.filter((r) => r.tier === 1);
|
|
4189
|
+
return {
|
|
4190
|
+
totalCombos: rows.length,
|
|
4191
|
+
tier1Combos: tier1Rows.length,
|
|
4192
|
+
verified: tier1Rows.filter((r) => r.result?.ok).length,
|
|
4193
|
+
failed: tier1Rows.reduce((s, r) => s + (r.result?.failed ?? 0), 0),
|
|
4194
|
+
rows
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
function formatMatrixReport(report) {
|
|
4198
|
+
const lines = [];
|
|
4199
|
+
lines.push(`\u5BBF\u4E3B\xD7\u5F15\u64CE\u77E9\u9635\uFF1A${report.totalCombos} \u7EC4\u5408 / Tier 1 ${report.tier1Combos} / \u5DF2\u9A8C\u8BC1 ${report.verified} / \u5931\u8D25 ${report.failed}`);
|
|
4200
|
+
for (const row of report.rows) {
|
|
4201
|
+
if (row.tier === 1 && row.result) {
|
|
4202
|
+
lines.push(` ${row.result.ok ? "\u2705" : "\u274C"} T1 ${row.host} \xD7 ${row.engine}\uFF08${row.result.total - row.result.failed}/${row.result.total}\uFF09`);
|
|
4203
|
+
} else if (row.tier === 3) {
|
|
4204
|
+
lines.push(` \u26AA T3 ${row.host} \xD7 ${row.engine}\uFF08\u6DF7\u5165\u53EF\u884C\uFF0C\u4E0D\u627F\u8BFA\uFF09`);
|
|
4205
|
+
} else {
|
|
4206
|
+
lines.push(` \u2B1B T0 ${row.host} \xD7 ${row.engine}\uFF08\u8DE8\u751F\u6001\u4E0D\u5408\u6CD5\uFF09`);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
return lines.join("\n");
|
|
4210
|
+
}
|
|
4211
|
+
export {
|
|
4212
|
+
Borrow,
|
|
4213
|
+
CONTAINER_PROFILES,
|
|
4214
|
+
DEFAULT_STACK_POLICY,
|
|
4215
|
+
DispatcherError,
|
|
4216
|
+
ENGINES,
|
|
4217
|
+
FIVE_ATOMIC_STEPS,
|
|
4218
|
+
FORK_SIGNATURES,
|
|
4219
|
+
HOSTS,
|
|
4220
|
+
HOST_ENGINE_MATRIX,
|
|
4221
|
+
Managed,
|
|
4222
|
+
ManagedRegistry,
|
|
4223
|
+
OWNERSHIP_ERRORS,
|
|
4224
|
+
Owned,
|
|
4225
|
+
OwnershipError,
|
|
4226
|
+
OwnershipGraph,
|
|
4227
|
+
PAGE_STATE_TRANSITIONS,
|
|
4228
|
+
SENSITIVE_CAPABILITIES,
|
|
4229
|
+
Weak,
|
|
4230
|
+
analyzeOwnershipSource,
|
|
4231
|
+
analyzePss,
|
|
4232
|
+
assertAtomicDestroy,
|
|
4233
|
+
buildOwnershipTimeline,
|
|
4234
|
+
canTransitionPageState,
|
|
4235
|
+
checkBizManifest,
|
|
4236
|
+
checkCapabilityAuthorization,
|
|
4237
|
+
createBackendSwitcher,
|
|
4238
|
+
createCarrierStub,
|
|
4239
|
+
createControlReader,
|
|
4240
|
+
createDestroyReport,
|
|
4241
|
+
createEmbeddedContainer,
|
|
4242
|
+
createEngine,
|
|
4243
|
+
createFlutterBackend,
|
|
4244
|
+
createHeadlessBackend,
|
|
4245
|
+
createHostRuntimeStub,
|
|
4246
|
+
createHybridRenderer,
|
|
4247
|
+
createMiniProgramContainer,
|
|
4248
|
+
createMockNativeAdapter,
|
|
4249
|
+
createNativeBackend,
|
|
4250
|
+
createNodeOpsDispatcher,
|
|
4251
|
+
createOwnershipCounters,
|
|
4252
|
+
createOwnershipHistory,
|
|
4253
|
+
createPageOwnership,
|
|
4254
|
+
createProteusRenderer,
|
|
4255
|
+
createProteusRendererForBackend,
|
|
4256
|
+
createQuotaManager,
|
|
4257
|
+
createQuotaTracker,
|
|
4258
|
+
createResourcePool,
|
|
4259
|
+
createSinglePageContainer,
|
|
4260
|
+
createStackContainer,
|
|
4261
|
+
createSuperAppContainer,
|
|
4262
|
+
createVueDomBackend,
|
|
4263
|
+
createVueRendererOptions,
|
|
4264
|
+
createWebHostRuntime,
|
|
4265
|
+
createWindowContainer,
|
|
4266
|
+
diagnoseOwnershipIssues,
|
|
4267
|
+
formatContainerConformance,
|
|
4268
|
+
formatHostConformance,
|
|
4269
|
+
formatMatrixReport,
|
|
4270
|
+
formatOwnershipDiagnosis,
|
|
4271
|
+
getProteusOwnershipGraph,
|
|
4272
|
+
insertScopeDrops,
|
|
4273
|
+
mapWidgetType,
|
|
4274
|
+
matrixCombos,
|
|
4275
|
+
profileOfContainer,
|
|
4276
|
+
renderComponentSnapshot,
|
|
4277
|
+
renderIRTree,
|
|
4278
|
+
resolvePssMode,
|
|
4279
|
+
runBackendConformance,
|
|
4280
|
+
runComboConformance,
|
|
4281
|
+
runContainerConformance,
|
|
4282
|
+
runHostConformance,
|
|
4283
|
+
runHostEngineMatrix,
|
|
4284
|
+
runHybridConformance,
|
|
4285
|
+
runPss,
|
|
4286
|
+
scanRepoForFork,
|
|
4287
|
+
semanticSequence,
|
|
4288
|
+
textureRef,
|
|
4289
|
+
toPlainTree,
|
|
4290
|
+
toWidgetTree
|
|
4291
|
+
};
|